@virex-tech/paywallo-sdk 1.1.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -145,9 +145,6 @@ var SecureStorage = class {
145
145
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
146
146
  constructor(_debug = false) {
147
147
  }
148
- /**
149
- * Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
150
- */
151
148
  async get(key) {
152
149
  const keychainValue = await this.getFromKeychain(key);
153
150
  if (keychainValue) {
@@ -163,9 +160,6 @@ var SecureStorage = class {
163
160
  return null;
164
161
  }
165
162
  }
166
- /**
167
- * Set a value in both Keychain (if available) and AsyncStorage.
168
- */
169
163
  async set(key, value) {
170
164
  try {
171
165
  await Promise.all([
@@ -229,11 +223,13 @@ var secureStorage = new SecureStorage();
229
223
 
230
224
  // src/domains/identity/IdentityManager.ts
231
225
  var DEVICE_ID_KEY = "device_id";
226
+ var ANON_ID_KEY = "anon_id";
232
227
  var USER_EMAIL_KEY = "user_email";
233
228
  var USER_PROPERTIES_KEY = "user_properties";
234
229
  var IdentityManager = class {
235
230
  constructor() {
236
231
  this.deviceId = null;
232
+ this.anonId = null;
237
233
  this.email = null;
238
234
  this.properties = {};
239
235
  this.apiClient = null;
@@ -260,23 +256,23 @@ var IdentityManager = class {
260
256
  try {
261
257
  if (DeviceInfo) {
262
258
  this.deviceId = await DeviceInfo.getUniqueId();
263
- this.log("Got device ID from DeviceInfo:", this.deviceId);
264
259
  } else {
265
260
  this.deviceId = generateUUID();
266
- this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
267
261
  }
268
262
  } catch {
269
263
  this.deviceId = generateUUID();
270
- this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
271
264
  }
272
265
  const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
273
266
  if (!stored) {
274
- this.log("Failed to persist device ID to secure storage, using fallback");
275
267
  await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
276
- this.log("Fallback also failed \u2014 device ID only in memory");
277
268
  });
278
269
  }
279
270
  }
271
+ if (!this.anonId) {
272
+ this.anonId = "$paywallo_anon:" + generateUUID();
273
+ await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
274
+ });
275
+ }
280
276
  this.initialized = true;
281
277
  return this.deviceId ?? "";
282
278
  }
@@ -300,21 +296,18 @@ var IdentityManager = class {
300
296
  this.properties = { ...this.properties, ...properties };
301
297
  await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
302
298
  }
303
- if (this.apiClient && this.deviceId) {
299
+ if (this.apiClient && this.anonId) {
304
300
  try {
305
- await this.apiClient.identify(this.deviceId, properties, email);
306
- this.log("Identity updated on server");
307
- } catch (error) {
308
- this.log("Failed to update identity on server:", error);
301
+ await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
302
+ } catch {
309
303
  }
310
304
  }
311
305
  }
312
306
  getDistinctId() {
313
- if (!this.initialized || !this.deviceId) {
314
- if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
307
+ if (!this.initialized || !this.anonId) {
315
308
  return "";
316
309
  }
317
- return this.deviceId;
310
+ return this.anonId;
318
311
  }
319
312
  getDeviceId() {
320
313
  return this.deviceId;
@@ -329,24 +322,22 @@ var IdentityManager = class {
329
322
  this.ensureInitialized();
330
323
  this.properties = { ...this.properties, ...properties };
331
324
  await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
332
- if (this.apiClient && this.deviceId) {
325
+ if (this.apiClient && this.anonId) {
333
326
  try {
334
- await this.apiClient.identify(this.deviceId, this.properties, this.email ?? void 0);
335
- } catch (error) {
336
- this.log("Failed to update properties on server:", error);
327
+ await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
328
+ } catch {
337
329
  }
338
330
  }
339
331
  }
340
332
  async reset() {
333
+ const newAnonId = "$paywallo_anon:" + generateUUID();
334
+ this.anonId = newAnonId;
341
335
  this.email = null;
342
336
  this.properties = {};
343
- this.initialized = false;
344
- this.apiClient = null;
345
- this.deviceId = null;
346
337
  await Promise.all([
338
+ this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
347
339
  this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
348
- this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false),
349
- this.secureStorage?.remove("device_id_fallback") ?? Promise.resolve(false)
340
+ this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
350
341
  ]);
351
342
  }
352
343
  getState() {
@@ -363,21 +354,23 @@ var IdentityManager = class {
363
354
  if (!this.secureStorage) {
364
355
  return;
365
356
  }
366
- const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
357
+ const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
367
358
  this.secureStorage.get(DEVICE_ID_KEY),
359
+ this.secureStorage.get(ANON_ID_KEY),
368
360
  this.secureStorage.get(USER_EMAIL_KEY),
369
361
  this.secureStorage.get(USER_PROPERTIES_KEY)
370
362
  ]);
371
363
  if (storedDeviceId) {
372
364
  this.deviceId = storedDeviceId;
373
- this.log("Loaded device ID from storage:", this.deviceId);
374
365
  } else {
375
366
  const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
376
367
  if (fallbackDeviceId) {
377
368
  this.deviceId = fallbackDeviceId;
378
- this.log("Loaded device ID from fallback storage:", this.deviceId);
379
369
  }
380
370
  }
371
+ if (storedAnonId) {
372
+ this.anonId = storedAnonId;
373
+ }
381
374
  if (storedEmail) {
382
375
  this.email = storedEmail;
383
376
  }
@@ -397,11 +390,6 @@ var IdentityManager = class {
397
390
  );
398
391
  }
399
392
  }
400
- log(...args) {
401
- if (this.debug) {
402
- console.warn("[Paywallo Identity]", ...args);
403
- }
404
- }
405
393
  };
406
394
  var identityManager = new IdentityManager();
407
395
 
@@ -486,7 +474,6 @@ var nativeStoreKit = {
486
474
  isAvailable,
487
475
  async getProducts(productIds) {
488
476
  if (!PaywalloStoreKitNative) {
489
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
490
477
  return [];
491
478
  }
492
479
  const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
@@ -557,7 +544,6 @@ var IAPService = class {
557
544
  }
558
545
  return products;
559
546
  } catch (error) {
560
- if (this.debug) console.warn("[Paywallo][Products] StoreKit error:", error);
561
547
  return [];
562
548
  }
563
549
  }
@@ -567,35 +553,46 @@ var IAPService = class {
567
553
  getCachedProducts() {
568
554
  return new Map(this.productsCache);
569
555
  }
556
+ async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
557
+ let lastError;
558
+ for (let i = 0; i <= maxRetries; i++) {
559
+ try {
560
+ return await attempt();
561
+ } catch (error) {
562
+ lastError = error;
563
+ if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
564
+ }
565
+ }
566
+ throw lastError;
567
+ }
570
568
  async validateWithServer(purchase, productId, options) {
571
569
  const apiClient = this.getApiClient();
572
570
  const product = this.productsCache.get(productId);
573
571
  const platform = Platform2.OS;
574
572
  const distinctId = PaywalloClient.getDistinctId();
575
- const validation = await apiClient.validatePurchase(
576
- platform,
577
- purchase.receipt,
578
- purchase.productId,
579
- purchase.transactionId,
580
- product?.priceValue ?? 0,
581
- product?.currency ?? "USD",
582
- this.getDeviceCountry(),
583
- distinctId,
584
- options?.paywallPlacement,
585
- options?.variantKey
573
+ return this.validateWithRetry(
574
+ () => apiClient.validatePurchase(
575
+ platform,
576
+ purchase.receipt,
577
+ purchase.productId,
578
+ purchase.transactionId,
579
+ product?.priceValue ?? 0,
580
+ product?.currency ?? "USD",
581
+ this.getDeviceCountry(),
582
+ distinctId,
583
+ options?.paywallPlacement,
584
+ options?.variantKey
585
+ )
586
586
  );
587
- return validation;
588
587
  }
589
588
  async finishTransaction(transactionId) {
590
589
  try {
591
590
  await nativeStoreKit.finishTransaction(transactionId);
592
591
  } catch (finishError) {
593
- if (this.debug) console.warn("[Paywallo][Purchase] Failed to finish transaction:", finishError instanceof Error ? finishError.message : finishError);
594
592
  }
595
593
  }
596
594
  async purchase(productId, options) {
597
595
  if (!nativeStoreKit.isAvailable()) {
598
- if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
599
596
  return {
600
597
  success: false,
601
598
  error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
@@ -606,11 +603,11 @@ var IAPService = class {
606
603
  if (!purchase) {
607
604
  return { success: false };
608
605
  }
606
+ options?.onValidating?.();
609
607
  let validation = null;
610
608
  try {
611
609
  validation = await this.validateWithServer(purchase, productId, options);
612
610
  } catch (validationError) {
613
- if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
614
611
  return {
615
612
  success: false,
616
613
  error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
@@ -626,7 +623,6 @@ var IAPService = class {
626
623
  return { success: true, purchase };
627
624
  } catch (error) {
628
625
  const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
629
- if (this.debug) console.warn("[Paywallo][Purchase] FAILED:", e.message);
630
626
  return { success: false, error: e };
631
627
  }
632
628
  }
@@ -662,14 +658,12 @@ var IAPService = class {
662
658
  try {
663
659
  await nativeStoreKit.finishTransaction(tx.transactionId);
664
660
  } catch (finishError) {
665
- if (this.debug) console.warn("[Paywallo][Restore] Failed to finish tx:", tx.transactionId, finishError instanceof Error ? finishError.message : finishError);
666
661
  }
667
662
  validated.push(tx);
668
663
  }
669
664
  }
670
665
  return validated;
671
666
  } catch (error) {
672
- if (this.debug) console.warn("[Paywallo][Restore] \u274C FAILED:", error instanceof Error ? error.message : error);
673
667
  return [];
674
668
  }
675
669
  }
@@ -737,7 +731,7 @@ function usePaywallTracking() {
737
731
 
738
732
  // src/domains/paywall/usePaywallActions.ts
739
733
  var SCREEN_HEIGHT = Dimensions.get("window").height;
740
- function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
734
+ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
741
735
  const [isPurchasing, setIsPurchasing] = useState(false);
742
736
  const [isClosing, setIsClosing] = useState(false);
743
737
  const slideAnim = useRef(new Animated.Value(0)).current;
@@ -746,7 +740,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
746
740
  const handleClose = useCallback2(() => {
747
741
  if (isClosing) return;
748
742
  setIsClosing(true);
749
- Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 300, useNativeDriver: true }).start(() => {
743
+ const animTarget = openAnim ?? slideAnim;
744
+ Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
750
745
  if (paywallConfig) {
751
746
  const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
752
747
  void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
@@ -755,7 +750,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
755
750
  setIsClosing(false);
756
751
  onResult({ presented: true, purchased: false, cancelled: true, restored: false });
757
752
  });
758
- }, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
753
+ }, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
759
754
  const handlePurchase = useCallback2(async (productId) => {
760
755
  if (isPurchasing) return;
761
756
  setIsPurchasing(true);
@@ -773,7 +768,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
773
768
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
774
769
  }
775
770
  } catch (error) {
776
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
777
771
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
778
772
  } finally {
779
773
  setIsPurchasing(false);
@@ -794,7 +788,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
794
788
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
795
789
  }
796
790
  } catch (error) {
797
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
798
791
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
799
792
  } finally {
800
793
  setIsPurchasing(false);
@@ -889,7 +882,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
889
882
  }
890
883
  } catch (err) {
891
884
  const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
892
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
893
885
  setError(e);
894
886
  }
895
887
  };
@@ -898,18 +890,103 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
898
890
  return { paywallConfig, products, error };
899
891
  }
900
892
 
893
+ // src/utils/localization.ts
894
+ import { NativeModules as NativeModules2, Platform as Platform3 } from "react-native";
895
+ var currentLanguage = "pt-BR";
896
+ var defaultLanguage = "pt-BR";
897
+ function setCurrentLanguage(language) {
898
+ currentLanguage = language;
899
+ }
900
+ function setDefaultLanguage(language) {
901
+ defaultLanguage = language;
902
+ }
903
+ function getCurrentLanguage() {
904
+ return currentLanguage;
905
+ }
906
+ function getDefaultLanguage() {
907
+ return defaultLanguage;
908
+ }
909
+ function getLocalizedString(text) {
910
+ if (!text) return "";
911
+ if (typeof text === "string") return text;
912
+ return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
913
+ }
914
+ function detectDeviceLanguage() {
915
+ try {
916
+ if (Platform3.OS === "ios") {
917
+ const settings = NativeModules2.SettingsManager?.settings;
918
+ return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
919
+ }
920
+ return NativeModules2.I18nManager?.localeIdentifier ?? "en-US";
921
+ } catch {
922
+ return "en-US";
923
+ }
924
+ }
925
+ var SDK_STRINGS = {
926
+ paywallErrorTitle: {
927
+ "pt-BR": "Algo deu errado",
928
+ "pt": "Algo deu errado",
929
+ "en": "Something went wrong",
930
+ "en-US": "Something went wrong",
931
+ "en-GB": "Something went wrong",
932
+ "es": "Algo sali\xF3 mal",
933
+ "es-419": "Algo sali\xF3 mal"
934
+ },
935
+ paywallRetryButton: {
936
+ "pt-BR": "Tentar novamente",
937
+ "pt": "Tentar novamente",
938
+ "en": "Try again",
939
+ "en-US": "Try again",
940
+ "en-GB": "Try again",
941
+ "es": "Reintentar",
942
+ "es-419": "Reintentar"
943
+ },
944
+ paywallCloseButton: {
945
+ "pt-BR": "Fechar",
946
+ "pt": "Fechar",
947
+ "en": "Close",
948
+ "en-US": "Close",
949
+ "en-GB": "Close",
950
+ "es": "Cerrar",
951
+ "es-419": "Cerrar"
952
+ }
953
+ };
954
+ function getSdkString(key) {
955
+ const map = SDK_STRINGS[key];
956
+ const lang = currentLanguage;
957
+ if (lang in map) return map[lang];
958
+ const base = lang.split("-")[0];
959
+ if (base && base in map) return map[base];
960
+ if (defaultLanguage in map) return map[defaultLanguage];
961
+ return Object.values(map)[0];
962
+ }
963
+ function initLocalization(options) {
964
+ if (options?.defaultLanguage) {
965
+ defaultLanguage = options.defaultLanguage;
966
+ }
967
+ if (options?.detectDevice !== false) {
968
+ const deviceLanguage = detectDeviceLanguage();
969
+ currentLanguage = deviceLanguage;
970
+ }
971
+ }
972
+
901
973
  // src/domains/paywall/PaywallWebView.tsx
902
974
  import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState3 } from "react";
903
975
  import {
904
976
  Linking,
905
977
  NativeEventEmitter,
906
- NativeModules as NativeModules2,
978
+ NativeModules as NativeModules3,
907
979
  requireNativeComponent,
908
980
  StyleSheet,
909
981
  View
910
982
  } from "react-native";
911
983
 
912
984
  // src/domains/paywall/parseWebViewMessage.ts
985
+ function deriveMessageId(message) {
986
+ if (message.id) return message.id;
987
+ const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
988
+ return `${message.type}:${payloadKey}`;
989
+ }
913
990
  function parseWebViewMessage(raw) {
914
991
  try {
915
992
  const parsed = JSON.parse(raw);
@@ -933,7 +1010,7 @@ function buildPaywallInjectionScript(data) {
933
1010
  secondaryProductId: data.secondaryProductId
934
1011
  }
935
1012
  };
936
- return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}else{console.warn('[Paywallo WV] FAILED to inject after '+m+' attempts');}}t();})();true;`;
1013
+ return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}}t();})();true;`;
937
1014
  }
938
1015
  function buildPurchaseStateScript(isPurchasing) {
939
1016
  return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
@@ -951,7 +1028,7 @@ function getNativeWebView() {
951
1028
  var webViewEmitter = null;
952
1029
  function getWebViewEmitter() {
953
1030
  if (!webViewEmitter) {
954
- webViewEmitter = new NativeEventEmitter(NativeModules2.PaywalloWebViewModule);
1031
+ webViewEmitter = new NativeEventEmitter(NativeModules3.PaywalloWebViewModule);
955
1032
  }
956
1033
  return webViewEmitter;
957
1034
  }
@@ -969,6 +1046,7 @@ function PaywallWebView({
969
1046
  primaryProductId,
970
1047
  secondaryProductId,
971
1048
  isPurchasing,
1049
+ webUrl: webUrlProp,
972
1050
  onPurchase,
973
1051
  onClose,
974
1052
  onRestore,
@@ -980,8 +1058,39 @@ function PaywallWebView({
980
1058
  const [scriptToInject, setScriptToInject] = useState3(void 0);
981
1059
  const onErrorRef = useRef3(onError);
982
1060
  onErrorRef.current = onError;
1061
+ const onReadyRef = useRef3(onReady);
1062
+ onReadyRef.current = onReady;
983
1063
  const NativeWebView = useMemo(() => getNativeWebView(), []);
984
- const webUrl = useMemo(() => PaywalloClient.getWebUrl() ?? "http://localhost:3000", []);
1064
+ const [resolvedWebUrl, setResolvedWebUrl] = useState3(
1065
+ () => webUrlProp ?? PaywalloClient.getWebUrl()
1066
+ );
1067
+ useEffect2(() => {
1068
+ if (webUrlProp !== void 0) {
1069
+ setResolvedWebUrl(webUrlProp ?? null);
1070
+ return;
1071
+ }
1072
+ const url = PaywalloClient.getWebUrl();
1073
+ if (url) {
1074
+ setResolvedWebUrl(url);
1075
+ return;
1076
+ }
1077
+ const interval = setInterval(() => {
1078
+ const u = PaywalloClient.getWebUrl();
1079
+ if (u) {
1080
+ setResolvedWebUrl(u);
1081
+ clearInterval(interval);
1082
+ }
1083
+ }, 200);
1084
+ return () => clearInterval(interval);
1085
+ }, [webUrlProp]);
1086
+ useEffect2(() => {
1087
+ if (!resolvedWebUrl) {
1088
+ onErrorRef.current?.({
1089
+ code: -1,
1090
+ description: "Paywall URL not configured. SDK may not be initialized."
1091
+ });
1092
+ }
1093
+ }, []);
985
1094
  const buildAndSetInjectionScript = useCallback3(() => {
986
1095
  if (!craftData || paywallDataSent.current) return;
987
1096
  paywallDataSent.current = true;
@@ -992,19 +1101,21 @@ function PaywallWebView({
992
1101
  secondaryProductId
993
1102
  });
994
1103
  setScriptToInject(script);
995
- onReady?.();
996
- }, [craftData, products, primaryProductId, secondaryProductId, onReady]);
997
- const lastProcessedRef = useRef3({ type: "", time: 0 });
1104
+ }, [craftData, products, primaryProductId, secondaryProductId]);
1105
+ const processedIdsRef = useRef3(/* @__PURE__ */ new Set());
998
1106
  const handleParsedMessage = useCallback3(
999
1107
  (message) => {
1000
- const now = Date.now();
1001
- if (message.type === lastProcessedRef.current.type && now - lastProcessedRef.current.time < 500) {
1002
- return;
1003
- }
1004
- lastProcessedRef.current = { type: message.type, time: now };
1108
+ const msgId = deriveMessageId(message);
1109
+ if (processedIdsRef.current.has(msgId)) return;
1110
+ processedIdsRef.current.add(msgId);
1111
+ setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
1005
1112
  switch (message.type) {
1006
1113
  case "ready":
1007
- if (!paywallDataSent.current) buildAndSetInjectionScript();
1114
+ if (!paywallDataSent.current) {
1115
+ buildAndSetInjectionScript();
1116
+ } else {
1117
+ onReadyRef.current?.();
1118
+ }
1008
1119
  break;
1009
1120
  case "purchase":
1010
1121
  if (message.payload?.productId) onPurchase(message.payload.productId);
@@ -1095,7 +1206,8 @@ function PaywallWebView({
1095
1206
  if (!paywallDataSent.current) return;
1096
1207
  setScriptToInject(buildPurchaseStateScript(isPurchasing));
1097
1208
  }, [isPurchasing]);
1098
- const paywallUrl = `${webUrl}/paywall/${paywallId}`;
1209
+ if (!resolvedWebUrl) return null;
1210
+ const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
1099
1211
  return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsx(
1100
1212
  NativeWebView,
1101
1213
  {
@@ -1116,6 +1228,18 @@ var styles = StyleSheet.create({
1116
1228
  // src/domains/paywall/PaywallModal.tsx
1117
1229
  import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
1118
1230
  var SCREEN_HEIGHT2 = Dimensions2.get("window").height;
1231
+ function ErrorFallback({ description, onRetry, onClose }) {
1232
+ const overrides = PaywalloClient.getConfig()?.errorStrings;
1233
+ const title = overrides?.title ?? getSdkString("paywallErrorTitle");
1234
+ const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
1235
+ const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
1236
+ return /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
1237
+ /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: title }),
1238
+ /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: description }),
1239
+ /* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: retryLabel }) }),
1240
+ /* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: closeLabel }) })
1241
+ ] }) });
1242
+ }
1119
1243
  function PaywallModal({
1120
1244
  placement,
1121
1245
  visible,
@@ -1133,7 +1257,13 @@ function PaywallModal({
1133
1257
  variantKey,
1134
1258
  campaignId
1135
1259
  );
1136
- const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId);
1260
+ const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
1261
+ const [modalVisible, setModalVisible] = useState4(false);
1262
+ const handleResult = useCallback4((result) => {
1263
+ setModalVisible(false);
1264
+ onResult(result);
1265
+ }, [onResult]);
1266
+ const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
1137
1267
  const [webViewError, setWebViewError] = useState4(null);
1138
1268
  const [retryCount, setRetryCount] = useState4(0);
1139
1269
  const retryCountRef = useRef4(0);
@@ -1153,9 +1283,9 @@ function PaywallModal({
1153
1283
  setWebViewError(null);
1154
1284
  setRetryCount(0);
1155
1285
  }, []);
1156
- const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
1157
1286
  useEffect3(() => {
1158
1287
  if (visible) {
1288
+ setModalVisible(true);
1159
1289
  openAnim.setValue(SCREEN_HEIGHT2);
1160
1290
  Animated2.timing(openAnim, {
1161
1291
  toValue: 0,
@@ -1165,11 +1295,11 @@ function PaywallModal({
1165
1295
  }).start();
1166
1296
  }
1167
1297
  }, [visible, openAnim]);
1168
- if (!visible) return null;
1298
+ if (!modalVisible && !visible) return null;
1169
1299
  return /* @__PURE__ */ jsx2(
1170
1300
  Modal,
1171
1301
  {
1172
- visible,
1302
+ visible: modalVisible,
1173
1303
  animationType: "none",
1174
1304
  presentationStyle: "overFullScreen",
1175
1305
  onRequestClose: handleClose,
@@ -1177,12 +1307,14 @@ function PaywallModal({
1177
1307
  statusBarTranslucent: true,
1178
1308
  children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsx2(Animated2.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.container, children: [
1179
1309
  error && /* @__PURE__ */ jsx2(View2, { style: styles2.errorContainer, children: /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: error.message }) }),
1180
- !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
1181
- /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: "Algo deu errado" }),
1182
- /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: webViewError.description }),
1183
- /* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: handleRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: "Tentar novamente" }) }),
1184
- /* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: handleClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: "Fechar" }) })
1185
- ] }) }) : /* @__PURE__ */ jsx2(
1310
+ !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
1311
+ ErrorFallback,
1312
+ {
1313
+ description: webViewError.description,
1314
+ onRetry: handleRetry,
1315
+ onClose: handleClose
1316
+ }
1317
+ ) : /* @__PURE__ */ jsx2(
1186
1318
  PaywallWebView,
1187
1319
  {
1188
1320
  paywallId: paywallConfig.id,
@@ -1266,8 +1398,7 @@ var PaywallErrorBoundary = class extends Component {
1266
1398
  static getDerivedStateFromError() {
1267
1399
  return { hasError: true };
1268
1400
  }
1269
- componentDidCatch(error) {
1270
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
1401
+ componentDidCatch(_error) {
1271
1402
  }
1272
1403
  render() {
1273
1404
  return this.state.hasError ? null : this.props.children;
@@ -1290,7 +1421,7 @@ function usePaywallContext() {
1290
1421
 
1291
1422
  // src/domains/paywall/usePaywallPresenter.ts
1292
1423
  import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
1293
- function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreloadedWebView) {
1424
+ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1294
1425
  const [showingPreloadedWebView, setShowingPreloadedWebView] = useState5(false);
1295
1426
  const resolverRef = useRef5(null);
1296
1427
  const presentedAtRef = useRef5(Date.now());
@@ -1339,9 +1470,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
1339
1470
  };
1340
1471
  void track();
1341
1472
  setShowingPreloadedWebView(false);
1342
- clearPreloadedWebView();
1343
1473
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
1344
- }, [preloadedWebView, clearPreloadedWebView, resolvePreloadedPaywall]);
1474
+ }, [preloadedWebView, resolvePreloadedPaywall]);
1345
1475
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1346
1476
  }
1347
1477
 
@@ -1548,10 +1678,7 @@ var AffiliateManager = class {
1548
1678
  );
1549
1679
  }
1550
1680
  }
1551
- log(...args) {
1552
- if (this.debug) {
1553
- console.warn("[AffiliateManager]", ...args);
1554
- }
1681
+ log(..._args) {
1555
1682
  }
1556
1683
  };
1557
1684
  var affiliateManager = new AffiliateManager();
@@ -1569,26 +1696,42 @@ var PaywalloAffiliate = {
1569
1696
  };
1570
1697
 
1571
1698
  // src/domains/subscription/SubscriptionCache.ts
1572
- var CACHE_KEY = "subscription_cache";
1699
+ var LEGACY_CACHE_KEY = "subscription_cache";
1700
+ var CACHE_KEY_PREFIX = "subscription_cache:";
1573
1701
  var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
1702
+ function keyFor(distinctId) {
1703
+ return `${CACHE_KEY_PREFIX}${distinctId}`;
1704
+ }
1574
1705
  var SubscriptionCache = class {
1575
1706
  constructor(ttl = DEFAULT_TTL) {
1576
- this.memoryCache = null;
1707
+ this.memoryCache = /* @__PURE__ */ new Map();
1708
+ this.legacyCleanupDone = false;
1577
1709
  this.ttl = ttl;
1578
1710
  this.storage = new SecureStorage();
1579
1711
  }
1580
- async get() {
1581
- if (this.memoryCache && !this.isExpired(this.memoryCache)) {
1582
- return this.memoryCache;
1712
+ async cleanupLegacyCache() {
1713
+ if (this.legacyCleanupDone) return;
1714
+ this.legacyCleanupDone = true;
1715
+ try {
1716
+ await this.storage.remove(LEGACY_CACHE_KEY);
1717
+ } catch {
1718
+ }
1719
+ }
1720
+ async get(distinctId) {
1721
+ await this.cleanupLegacyCache();
1722
+ if (!distinctId) return null;
1723
+ const memEntry = this.memoryCache.get(distinctId);
1724
+ if (memEntry && !this.isExpired(memEntry)) {
1725
+ return memEntry;
1583
1726
  }
1584
1727
  try {
1585
- const stored = await this.storage.get(CACHE_KEY);
1728
+ const stored = await this.storage.get(keyFor(distinctId));
1586
1729
  if (!stored) {
1587
1730
  return null;
1588
1731
  }
1589
1732
  const parsed = JSON.parse(stored);
1590
1733
  if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
1591
- await this.storage.remove(CACHE_KEY);
1734
+ await this.storage.remove(keyFor(distinctId));
1592
1735
  return null;
1593
1736
  }
1594
1737
  const cached = parsed;
@@ -1617,32 +1760,39 @@ var SubscriptionCache = class {
1617
1760
  }
1618
1761
  cached.isStale = this.isExpired(cached);
1619
1762
  if (!cached.isStale) {
1620
- this.memoryCache = cached;
1763
+ this.memoryCache.set(distinctId, cached);
1621
1764
  }
1622
1765
  return cached;
1623
1766
  } catch {
1624
1767
  return null;
1625
1768
  }
1626
1769
  }
1627
- async set(data) {
1770
+ async set(distinctId, data) {
1771
+ if (!distinctId) return;
1628
1772
  const cached = {
1629
1773
  data,
1630
1774
  cachedAt: Date.now(),
1631
1775
  isStale: false
1632
1776
  };
1633
- this.memoryCache = cached;
1777
+ this.memoryCache.set(distinctId, cached);
1634
1778
  try {
1635
- await this.storage.set(CACHE_KEY, JSON.stringify(cached));
1636
- } catch (error) {
1637
- if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
1779
+ await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
1780
+ } catch {
1638
1781
  }
1639
1782
  }
1640
- async invalidate() {
1641
- this.memoryCache = null;
1783
+ async invalidate(distinctId) {
1784
+ if (!distinctId) return;
1785
+ this.memoryCache.delete(distinctId);
1642
1786
  try {
1643
- await this.storage.remove(CACHE_KEY);
1644
- } catch (error) {
1645
- if (__DEV__) console.warn("[SubscriptionCache] invalidate() failed:", error instanceof Error ? error.message : String(error));
1787
+ await this.storage.remove(keyFor(distinctId));
1788
+ } catch {
1789
+ }
1790
+ }
1791
+ async invalidateAll() {
1792
+ this.memoryCache.clear();
1793
+ try {
1794
+ await this.storage.remove(LEGACY_CACHE_KEY);
1795
+ } catch {
1646
1796
  }
1647
1797
  }
1648
1798
  isExpired(cached) {
@@ -1654,8 +1804,155 @@ var SubscriptionCache = class {
1654
1804
  };
1655
1805
  var subscriptionCache = new SubscriptionCache();
1656
1806
 
1807
+ // src/domains/subscription/SubscriptionManager.ts
1808
+ import { Platform as Platform4 } from "react-native";
1809
+
1810
+ // src/domains/session/SessionError.ts
1811
+ var SessionError = class extends PaywalloError {
1812
+ constructor(code, message) {
1813
+ super("session", code, message);
1814
+ this.name = "SessionError";
1815
+ }
1816
+ };
1817
+ var SESSION_ERROR_CODES = {
1818
+ NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
1819
+ START_FAILED: "SESSION_START_FAILED",
1820
+ END_FAILED: "SESSION_END_FAILED",
1821
+ RESTORE_FAILED: "SESSION_RESTORE_FAILED"
1822
+ };
1823
+
1824
+ // src/domains/subscription/SubscriptionManager.ts
1825
+ var SubscriptionManagerClass = class {
1826
+ constructor() {
1827
+ this.config = null;
1828
+ this.listeners = /* @__PURE__ */ new Set();
1829
+ this.userId = null;
1830
+ this.cache = new SubscriptionCache();
1831
+ }
1832
+ cacheKey() {
1833
+ return this.userId ?? "__anonymous__";
1834
+ }
1835
+ init(config) {
1836
+ this.config = config;
1837
+ if (config.cacheTTL) {
1838
+ this.cache.setTTL(config.cacheTTL);
1839
+ }
1840
+ }
1841
+ setUserId(userId) {
1842
+ if (this.userId !== userId) {
1843
+ this.userId = userId;
1844
+ void this.cache.invalidateAll();
1845
+ }
1846
+ }
1847
+ async hasActiveSubscription(forceRefresh = false) {
1848
+ const status = await this.getSubscriptionStatus(forceRefresh);
1849
+ return status.hasActiveSubscription;
1850
+ }
1851
+ async getSubscription(forceRefresh = false) {
1852
+ const status = await this.getSubscriptionStatus(forceRefresh);
1853
+ return status.subscription;
1854
+ }
1855
+ async getEntitlements(forceRefresh = false) {
1856
+ const status = await this.getSubscriptionStatus(forceRefresh);
1857
+ return status.entitlements;
1858
+ }
1859
+ async getSubscriptionStatus(forceRefresh = false) {
1860
+ if (!this.config) {
1861
+ return this.getEmptyStatus();
1862
+ }
1863
+ if (!forceRefresh) {
1864
+ const cached = await this.cache.get(this.cacheKey());
1865
+ if (cached && !cached.isStale) {
1866
+ return cached.data;
1867
+ }
1868
+ }
1869
+ try {
1870
+ const status = await this.fetchSubscriptionStatus();
1871
+ await this.cache.set(this.cacheKey(), status);
1872
+ this.notifyListeners(status);
1873
+ return status;
1874
+ } catch (error) {
1875
+ this.log("Failed to fetch subscription status", error);
1876
+ const cached = await this.cache.get(this.cacheKey());
1877
+ if (cached) {
1878
+ return cached.data;
1879
+ }
1880
+ return this.getEmptyStatus();
1881
+ }
1882
+ }
1883
+ async restorePurchases() {
1884
+ if (!this.config) {
1885
+ throw new SessionError(
1886
+ SESSION_ERROR_CODES.NOT_INITIALIZED,
1887
+ "SubscriptionManager not initialized"
1888
+ );
1889
+ }
1890
+ await this.cache.invalidate(this.cacheKey());
1891
+ return this.getSubscriptionStatus(true);
1892
+ }
1893
+ async fetchSubscriptionStatus() {
1894
+ if (!this.config) {
1895
+ return this.getEmptyStatus();
1896
+ }
1897
+ const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
1898
+ if (this.userId) {
1899
+ url.searchParams.set("userId", this.userId);
1900
+ }
1901
+ url.searchParams.set("platform", Platform4.OS);
1902
+ const response = await fetch(url.toString(), {
1903
+ method: "GET",
1904
+ headers: {
1905
+ "Content-Type": "application/json",
1906
+ "X-App-Key": this.config.appKey
1907
+ }
1908
+ });
1909
+ if (!response.ok) {
1910
+ throw new SessionError(
1911
+ SESSION_ERROR_CODES.RESTORE_FAILED,
1912
+ `Failed to fetch subscription status: ${response.status}`
1913
+ );
1914
+ }
1915
+ const data = await response.json();
1916
+ if (data.subscription) {
1917
+ data.subscription.expiresAt = new Date(data.subscription.expiresAt);
1918
+ data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
1919
+ data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
1920
+ if (data.subscription.cancellationDate) {
1921
+ data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
1922
+ }
1923
+ if (data.subscription.gracePeriodExpiresAt) {
1924
+ data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
1925
+ }
1926
+ }
1927
+ return data;
1928
+ }
1929
+ getEmptyStatus() {
1930
+ return {
1931
+ hasActiveSubscription: false,
1932
+ subscription: null,
1933
+ entitlements: []
1934
+ };
1935
+ }
1936
+ addListener(listener) {
1937
+ this.listeners.add(listener);
1938
+ return () => this.listeners.delete(listener);
1939
+ }
1940
+ notifyListeners(status) {
1941
+ for (const listener of this.listeners) {
1942
+ listener(status);
1943
+ }
1944
+ }
1945
+ async onPurchaseComplete() {
1946
+ await this.cache.invalidate(this.cacheKey());
1947
+ await this.getSubscriptionStatus(true);
1948
+ }
1949
+ log(..._args) {
1950
+ }
1951
+ };
1952
+ var subscriptionManager = new SubscriptionManagerClass();
1953
+
1657
1954
  // src/core/ApiClient.ts
1658
- import { Platform as Platform3 } from "react-native";
1955
+ import { Platform as Platform5 } from "react-native";
1659
1956
 
1660
1957
  // src/core/apiUrlUtils.ts
1661
1958
  var DEFAULT_WEB_URL = "https://paywallo.com.br";
@@ -1887,10 +2184,7 @@ var HttpClient = class {
1887
2184
  sleep(ms) {
1888
2185
  return new Promise((resolve) => setTimeout(resolve, ms));
1889
2186
  }
1890
- log(message, data) {
1891
- if (this.config.debug) {
1892
- console.warn("[HttpClient]", message, data ?? "");
1893
- }
2187
+ log(..._args) {
1894
2188
  }
1895
2189
  setDebug(debug) {
1896
2190
  this.config.debug = debug;
@@ -2034,10 +2328,7 @@ var NetworkMonitorClass = class {
2034
2328
  this.initialized = false;
2035
2329
  this.currentState = "unknown";
2036
2330
  }
2037
- log(message, data) {
2038
- if (this.config.debug) {
2039
- console.warn("[NetworkMonitor]", message, data ?? "");
2040
- }
2331
+ log(..._args) {
2041
2332
  }
2042
2333
  setDebug(debug) {
2043
2334
  this.config.debug = debug;
@@ -2067,6 +2358,8 @@ function normalizeForComparison(obj) {
2067
2358
  return "{" + pairs.join(",") + "}";
2068
2359
  }
2069
2360
  function findDuplicateIndex(queue, item) {
2361
+ const byId = queue.findIndex((existing) => existing.id === item.id);
2362
+ if (byId !== -1) return byId;
2070
2363
  const normalizedBody = normalizeForComparison(item.body);
2071
2364
  return queue.findIndex(
2072
2365
  (existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
@@ -2135,9 +2428,9 @@ var OfflineQueueClass = class {
2135
2428
  void this.saveToStorage();
2136
2429
  }
2137
2430
  }
2138
- async enqueue(method, url, body, headers) {
2431
+ async enqueue(method, url, body, headers, id) {
2139
2432
  const item = {
2140
- id: generateUUID(),
2433
+ id: id ?? generateUUID(),
2141
2434
  method,
2142
2435
  url,
2143
2436
  body,
@@ -2221,7 +2514,6 @@ var OfflineQueueClass = class {
2221
2514
  this.emit({ type: "processing:end", queueSize: this.queue.length });
2222
2515
  return { processed, failed };
2223
2516
  }
2224
- /** Returns true if the item was permanently removed (counted as failed). */
2225
2517
  async handleItemFailure(item, error, permanent = false) {
2226
2518
  if (permanent || item.attempts >= item.maxAttempts) {
2227
2519
  await this.removeItem(item.id);
@@ -2283,10 +2575,7 @@ var OfflineQueueClass = class {
2283
2575
  }
2284
2576
  }
2285
2577
  }
2286
- log(message, data) {
2287
- if (this.config.debug) {
2288
- console.warn("[OfflineQueue]", message, data ?? "");
2289
- }
2578
+ log(..._args) {
2290
2579
  }
2291
2580
  setDebug(debug) {
2292
2581
  this.config.debug = debug;
@@ -2428,10 +2717,7 @@ var QueueProcessorClass = class {
2428
2717
  this.httpClient = null;
2429
2718
  this.initialized = false;
2430
2719
  }
2431
- log(message, data) {
2432
- if (this.config.debug) {
2433
- console.warn("[QueueProcessor]", message, data ?? "");
2434
- }
2720
+ log(..._args) {
2435
2721
  }
2436
2722
  setDebug(debug) {
2437
2723
  this.config.debug = debug;
@@ -2443,10 +2729,9 @@ var queueProcessor = new QueueProcessorClass();
2443
2729
  var ApiCache = class {
2444
2730
  constructor() {
2445
2731
  this.CACHE_TTL = 3e5;
2446
- // 5 minutes
2447
2732
  this.CACHE_NULL_TTL = 3e4;
2448
- // 30 seconds — for null/404 variants
2449
2733
  this.MAX_SIZE = 200;
2734
+ this.STALE_WINDOW_MS = 864e5;
2450
2735
  this.variantCache = /* @__PURE__ */ new Map();
2451
2736
  this.paywallCache = /* @__PURE__ */ new Map();
2452
2737
  this.campaignCache = /* @__PURE__ */ new Map();
@@ -2484,7 +2769,7 @@ var ApiCache = class {
2484
2769
  }
2485
2770
  getStaleVariant(key) {
2486
2771
  const entry = this.variantCache.get(key);
2487
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2772
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2488
2773
  return void 0;
2489
2774
  }
2490
2775
  getPaywall(key) {
@@ -2496,19 +2781,19 @@ var ApiCache = class {
2496
2781
  }
2497
2782
  getStalePaywall(key) {
2498
2783
  const entry = this.paywallCache.get(key);
2499
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2784
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2500
2785
  return void 0;
2501
2786
  }
2502
2787
  getCampaign(key) {
2503
2788
  return this.getCached(this.campaignCache, key);
2504
2789
  }
2505
- setCampaign(key, value) {
2506
- this.setCached(this.campaignCache, key, value);
2790
+ setCampaign(key, value, ttlOverride) {
2791
+ this.setCached(this.campaignCache, key, value, ttlOverride);
2507
2792
  this.evictExpiredEntries(this.campaignCache);
2508
2793
  }
2509
2794
  getStaleCampaign(key) {
2510
2795
  const entry = this.campaignCache.get(key);
2511
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2796
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2512
2797
  return void 0;
2513
2798
  }
2514
2799
  };
@@ -2527,11 +2812,12 @@ var _ApiClient = class _ApiClient {
2527
2812
  this.offlineQueueEnabled = offlineQueueEnabled;
2528
2813
  this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
2529
2814
  }
2530
- // ─── Infrastructure ──────────────────────────────────────────────────────────
2531
- /** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
2532
2815
  getHttpClient() {
2533
2816
  return this.httpClient;
2534
2817
  }
2818
+ getBaseUrl() {
2819
+ return this.baseUrl;
2820
+ }
2535
2821
  getWebUrl() {
2536
2822
  return this.webUrl;
2537
2823
  }
@@ -2557,7 +2843,6 @@ var _ApiClient = class _ApiClient {
2557
2843
  async clearQueue() {
2558
2844
  await offlineQueue.clear();
2559
2845
  }
2560
- // ─── Public HTTP facade (headers injected automatically) ─────────────────────
2561
2846
  async get(path) {
2562
2847
  return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
2563
2848
  }
@@ -2571,29 +2856,28 @@ var _ApiClient = class _ApiClient {
2571
2856
  message,
2572
2857
  context,
2573
2858
  sdkVersion: SDK_VERSION,
2574
- platform: Platform3.OS
2859
+ platform: Platform5.OS
2575
2860
  }, true);
2576
2861
  } catch {
2577
2862
  }
2578
2863
  }
2579
- // ─── Business methods ────────────────────────────────────────────────────────
2580
2864
  async trackEvent(eventName, distinctId, properties, timestamp) {
2581
2865
  await this.postWithQueue("/api/v1/events", {
2582
2866
  eventName,
2583
2867
  distinctId,
2584
- properties: { ...properties, platform: Platform3.OS },
2868
+ properties: { ...properties, platform: Platform5.OS },
2585
2869
  timestamp: timestamp ?? Date.now(),
2586
2870
  environment: this.environment.toLowerCase()
2587
2871
  }, `event:${eventName}`);
2588
2872
  }
2589
- async identify(distinctId, properties, email) {
2590
- await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform3.OS }, "identify");
2873
+ async identify(distinctId, properties, email, deviceId) {
2874
+ await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
2591
2875
  }
2592
2876
  async startSession(distinctId, sessionId, platform) {
2593
2877
  await this.postWithQueue("/api/v1/sessions/start", {
2594
2878
  distinctId,
2595
2879
  sessionId,
2596
- platform: platform ?? Platform3.OS,
2880
+ platform: platform ?? Platform5.OS,
2597
2881
  environment: this.environment.toLowerCase()
2598
2882
  }, `session.start:${sessionId}`);
2599
2883
  return { success: true };
@@ -2671,7 +2955,7 @@ var _ApiClient = class _ApiClient {
2671
2955
  const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
2672
2956
  if (!result) {
2673
2957
  this.log("No campaign found for placement:", placement);
2674
- this.cache.setCampaign(key, null);
2958
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
2675
2959
  return null;
2676
2960
  }
2677
2961
  this.log("Got campaign:", placement, result.variantKey);
@@ -2684,18 +2968,44 @@ var _ApiClient = class _ApiClient {
2684
2968
  return null;
2685
2969
  }
2686
2970
  }
2687
- async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
2688
- const result = await validatePurchase(
2689
- this,
2690
- this.appKey,
2691
- platform,
2692
- receipt,
2693
- productId,
2694
- transactionId,
2695
- priceLocal,
2696
- currency,
2697
- country,
2698
- distinctId,
2971
+ async getPrimaryCampaign(distinctId) {
2972
+ const key = `primary:${distinctId}`;
2973
+ const hit = this.cache.getCampaign(key);
2974
+ if (hit !== void 0) return hit;
2975
+ try {
2976
+ const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
2977
+ if (res.status === 404) {
2978
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
2979
+ return null;
2980
+ }
2981
+ if (!res.ok) {
2982
+ this.log("Non-OK response for primary campaign:", res.status);
2983
+ const stale = this.cache.getStaleCampaign(key);
2984
+ if (stale !== void 0) return stale;
2985
+ return null;
2986
+ }
2987
+ this.log("Got primary campaign:", res.data?.campaignId);
2988
+ this.cache.setCampaign(key, res.data);
2989
+ return res.data;
2990
+ } catch (error) {
2991
+ this.log("Failed to get primary campaign:", error);
2992
+ const stale = this.cache.getStaleCampaign(key);
2993
+ if (stale !== void 0) return stale;
2994
+ return null;
2995
+ }
2996
+ }
2997
+ async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
2998
+ const result = await validatePurchase(
2999
+ this,
3000
+ this.appKey,
3001
+ platform,
3002
+ receipt,
3003
+ productId,
3004
+ transactionId,
3005
+ priceLocal,
3006
+ currency,
3007
+ country,
3008
+ distinctId,
2699
3009
  paywallPlacement,
2700
3010
  variantKey
2701
3011
  );
@@ -2752,12 +3062,6 @@ var _ApiClient = class _ApiClient {
2752
3062
  return { value: false, flagKey };
2753
3063
  }
2754
3064
  }
2755
- // ─── Cache-only reads ────────────────────────────────────────────────────────
2756
- /**
2757
- * Reads a flag variant from cache layers only — no network call.
2758
- * Checks in-memory cache first, then SecureStorage.
2759
- * Returns null when both layers miss.
2760
- */
2761
3065
  async getVariantFromCache(flagKey, distinctId) {
2762
3066
  const key = `${flagKey}:${distinctId}`;
2763
3067
  const hit = this.cache.getVariant(key);
@@ -2789,10 +3093,6 @@ var _ApiClient = class _ApiClient {
2789
3093
  } catch {
2790
3094
  }
2791
3095
  }
2792
- /**
2793
- * POST with offline queue fallback. Enqueues when offline or on network failure.
2794
- * Throws only when offlineQueueEnabled is false and the request fails.
2795
- */
2796
3096
  async postWithQueue(url, payload, label) {
2797
3097
  if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
2798
3098
  await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
@@ -2811,11 +3111,9 @@ var _ApiClient = class _ApiClient {
2811
3111
  throw error;
2812
3112
  }
2813
3113
  }
2814
- log(...args) {
2815
- if (this.debug) console.warn("[Paywallo API]", ...args);
3114
+ log(..._args) {
2816
3115
  }
2817
3116
  };
2818
- // ─── Private helpers ─────────────────────────────────────────────────────────
2819
3117
  _ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2820
3118
  var ApiClient = _ApiClient;
2821
3119
 
@@ -2836,42 +3134,72 @@ var CAMPAIGN_ERROR_CODES = {
2836
3134
 
2837
3135
  // src/domains/campaign/CampaignGateService.ts
2838
3136
  var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
3137
+ var PRELOAD_WAIT_MAX_MS = 2e3;
3138
+ var PRELOAD_WAIT_INTERVAL_MS = 100;
2839
3139
  var CampaignGateService = class {
2840
3140
  constructor() {
2841
3141
  this.preloadedCampaigns = /* @__PURE__ */ new Map();
3142
+ this.pendingPreloads = /* @__PURE__ */ new Set();
2842
3143
  }
2843
3144
  async getCampaign(apiClient, placement, context) {
2844
3145
  const distinctId = identityManager.getDistinctId();
2845
3146
  if (!distinctId) return null;
2846
3147
  return apiClient.getCampaign(placement, distinctId, context);
2847
3148
  }
3149
+ async waitForPreload(placement) {
3150
+ if (!this.pendingPreloads.has(placement)) return;
3151
+ const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
3152
+ await new Promise((resolve) => {
3153
+ const check = () => {
3154
+ if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
3155
+ resolve();
3156
+ return;
3157
+ }
3158
+ setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
3159
+ };
3160
+ check();
3161
+ });
3162
+ }
2848
3163
  async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
2849
- const campaign = await this.getCampaign(apiClient, placement, context);
3164
+ await this.waitForPreload(placement);
3165
+ const preloaded = this.getPreloadedCampaign(placement);
3166
+ const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
2850
3167
  if (!campaign) {
2851
3168
  return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
2852
3169
  }
2853
- if (!context?.forceShow && await hasActive()) {
2854
- return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
3170
+ if (!context?.forceShow) {
3171
+ const isActive = await hasActive();
3172
+ if (isActive) {
3173
+ return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
3174
+ }
2855
3175
  }
2856
3176
  if (campaignPresenter) return campaignPresenter(campaign);
2857
3177
  if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
2858
- return { ...await paywallPresenter(campaign.paywall.placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
3178
+ return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
2859
3179
  }
2860
3180
  async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
2861
- if (await hasActive()) return true;
3181
+ const isActive = await hasActive();
3182
+ if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3183
+ if (isActive) return true;
2862
3184
  const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
2863
3185
  return r.purchased || r.restored;
2864
3186
  }
2865
3187
  async preloadCampaign(apiClient, placement, context) {
3188
+ this.pendingPreloads.add(placement);
2866
3189
  try {
2867
3190
  const campaign = await this.getCampaign(apiClient, placement, context);
2868
3191
  if (!campaign?.paywall) {
2869
3192
  return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
2870
3193
  }
2871
3194
  this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
3195
+ if (PaywalloClient.getConfig()?.debug) {
3196
+ console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
3197
+ }
2872
3198
  return { success: true };
2873
3199
  } catch (error) {
2874
3200
  return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
3201
+ } finally {
3202
+ this.pendingPreloads.delete(placement);
2875
3203
  }
2876
3204
  }
2877
3205
  getPreloadedCampaign(placement) {
@@ -2885,12 +3213,13 @@ var CampaignGateService = class {
2885
3213
  }
2886
3214
  clearPreloaded() {
2887
3215
  this.preloadedCampaigns.clear();
3216
+ this.pendingPreloads.clear();
2888
3217
  }
2889
3218
  };
2890
3219
  var campaignGateService = new CampaignGateService();
2891
3220
 
2892
3221
  // src/domains/identity/AdvertisingIdManager.ts
2893
- import { Platform as Platform4 } from "react-native";
3222
+ import { Platform as Platform6 } from "react-native";
2894
3223
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
2895
3224
  var AdvertisingIdManager = class {
2896
3225
  constructor() {
@@ -2901,7 +3230,7 @@ var AdvertisingIdManager = class {
2901
3230
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
2902
3231
  try {
2903
3232
  const tracking = await import("expo-tracking-transparency");
2904
- if (Platform4.OS === "ios") {
3233
+ if (Platform6.OS === "ios") {
2905
3234
  if (!tracking.isAvailable()) {
2906
3235
  const idfv2 = await this.collectIdfv();
2907
3236
  this.cached = { ...fallback, idfv: idfv2 };
@@ -2920,7 +3249,7 @@ var AdvertisingIdManager = class {
2920
3249
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
2921
3250
  return this.cached;
2922
3251
  }
2923
- if (Platform4.OS === "android") {
3252
+ if (Platform6.OS === "android") {
2924
3253
  const androidStatus = await tracking.getTrackingPermissionsAsync();
2925
3254
  const optedIn = androidStatus.granted;
2926
3255
  const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
@@ -2979,14 +3308,14 @@ var FacebookIdManager = class {
2979
3308
  var facebookIdManager = new FacebookIdManager();
2980
3309
 
2981
3310
  // src/domains/identity/InstallTracker.ts
2982
- import { Dimensions as Dimensions3, Platform as Platform6 } from "react-native";
3311
+ import { Dimensions as Dimensions3, Platform as Platform8 } from "react-native";
2983
3312
 
2984
3313
  // src/domains/identity/InstallReferrerManager.ts
2985
- import { Platform as Platform5 } from "react-native";
3314
+ import { Platform as Platform7 } from "react-native";
2986
3315
  var REFERRER_TIMEOUT_MS = 5e3;
2987
3316
  var InstallReferrerManager = class {
2988
3317
  async getReferrer() {
2989
- if (Platform5.OS !== "android") {
3318
+ if (Platform7.OS !== "android") {
2990
3319
  return this.emptyResult();
2991
3320
  }
2992
3321
  let timerId;
@@ -3048,20 +3377,6 @@ var installReferrerManager = new InstallReferrerManager();
3048
3377
  // src/domains/session/SessionManager.ts
3049
3378
  import { AppState as AppState2 } from "react-native";
3050
3379
 
3051
- // src/domains/session/SessionError.ts
3052
- var SessionError = class extends PaywalloError {
3053
- constructor(code, message) {
3054
- super("session", code, message);
3055
- this.name = "SessionError";
3056
- }
3057
- };
3058
- var SESSION_ERROR_CODES = {
3059
- NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
3060
- START_FAILED: "SESSION_START_FAILED",
3061
- END_FAILED: "SESSION_END_FAILED",
3062
- RESTORE_FAILED: "SESSION_RESTORE_FAILED"
3063
- };
3064
-
3065
3380
  // src/domains/session/sessionTracking.ts
3066
3381
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
3067
3382
  const distinctId = identityManager.getDistinctId();
@@ -3289,10 +3604,7 @@ var SessionManager = class {
3289
3604
  );
3290
3605
  }
3291
3606
  }
3292
- log(...args) {
3293
- if (this.debug) {
3294
- console.warn("[Paywallo Session]", ...args);
3295
- }
3607
+ log(..._args) {
3296
3608
  }
3297
3609
  };
3298
3610
  var sessionManager = new SessionManager();
@@ -3347,9 +3659,6 @@ var InstallTracker = class {
3347
3659
  locale
3348
3660
  };
3349
3661
  } catch {
3350
- if (this.debug) {
3351
- console.warn("[Paywallo] Failed to collect device data for install event");
3352
- }
3353
3662
  }
3354
3663
  const [fbAnonId, referrer, adIds] = await Promise.all([
3355
3664
  facebookIdManager.getAnonId(),
@@ -3359,7 +3668,7 @@ var InstallTracker = class {
3359
3668
  try {
3360
3669
  await apiClient.trackEvent("$app_installed", distinctId, {
3361
3670
  installedAt,
3362
- platform: Platform6.OS,
3671
+ platform: Platform8.OS,
3363
3672
  ...sessionId && { sessionId },
3364
3673
  ...deviceData,
3365
3674
  ...fbAnonId && { fbAnonId },
@@ -3380,13 +3689,7 @@ var InstallTracker = class {
3380
3689
  await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
3381
3690
  } catch {
3382
3691
  }
3383
- if (this.debug && !stored) {
3384
- console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
3385
- }
3386
- } catch (error) {
3387
- if (this.debug) {
3388
- console.warn("[Paywallo] Failed to track install:", error);
3389
- }
3692
+ } catch {
3390
3693
  }
3391
3694
  }
3392
3695
  };
@@ -3396,25 +3699,13 @@ function isValidEventName(name) {
3396
3699
  if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3397
3700
  return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3398
3701
  }
3399
- var OFFLINE_ACTIVE_STATUSES = ["active", "grace_period"];
3400
- async function resolveClientOfflineFromCache() {
3401
- try {
3402
- const cached = await subscriptionCache.get();
3403
- if (!cached) return false;
3404
- const sub = cached.data.subscription;
3405
- if (!sub) return false;
3406
- if (!OFFLINE_ACTIVE_STATUSES.includes(sub.status)) return false;
3407
- if (!sub.expiresAt) return true;
3408
- return sub.expiresAt > /* @__PURE__ */ new Date();
3409
- } catch {
3410
- return false;
3411
- }
3412
- }
3413
3702
  var _PaywalloClientClass = class _PaywalloClientClass {
3414
3703
  constructor() {
3415
3704
  this.config = null;
3416
3705
  this.apiClient = null;
3417
3706
  this.initPromise = null;
3707
+ this.readyPromise = null;
3708
+ this.resolveReady = null;
3418
3709
  this.paywallPresenter = null;
3419
3710
  this.campaignPresenter = null;
3420
3711
  this.subscriptionGetter = null;
@@ -3424,29 +3715,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3424
3715
  this.pendingConfig = null;
3425
3716
  this.networkCleanup = null;
3426
3717
  this.initAttempts = 0;
3718
+ this.autoPreloadedPlacement = null;
3719
+ this.autoPreloadPlacementPromise = null;
3720
+ this.sessionFlagsMap = /* @__PURE__ */ new Map();
3427
3721
  }
3428
3722
  async init(config) {
3429
- if (config.debug) console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
3723
+ if (!this.readyPromise) {
3724
+ this.readyPromise = new Promise((resolve) => {
3725
+ this.resolveReady = resolve;
3726
+ });
3727
+ }
3430
3728
  if (this.config && this.apiClient) {
3431
- if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
3432
3729
  return;
3433
3730
  }
3434
3731
  if (this.initPromise) {
3435
- if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
3436
3732
  await this.initPromise;
3437
- if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
3438
3733
  return;
3439
3734
  }
3440
3735
  if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
3441
- if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
3442
3736
  this.pendingConfig = config;
3443
3737
  this.initAttempts = 0;
3444
3738
  this.initPromise = this.initWithRetry(config);
3445
3739
  try {
3446
3740
  await this.initPromise;
3447
- if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
3448
3741
  } catch (error) {
3449
- if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
3450
3742
  this.initPromise = null;
3451
3743
  this.config = null;
3452
3744
  this.apiClient = null;
@@ -3455,6 +3747,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3455
3747
  throw error;
3456
3748
  }
3457
3749
  }
3750
+ isReady() {
3751
+ return this.config !== null && this.apiClient !== null;
3752
+ }
3753
+ waitUntilReady() {
3754
+ if (this.isReady()) return Promise.resolve();
3755
+ if (!this.readyPromise) {
3756
+ this.readyPromise = new Promise((resolve) => {
3757
+ this.resolveReady = resolve;
3758
+ });
3759
+ }
3760
+ return this.readyPromise;
3761
+ }
3458
3762
  async initWithRetry(config) {
3459
3763
  while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
3460
3764
  try {
@@ -3469,7 +3773,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3469
3773
  this.initAttempts++;
3470
3774
  if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
3471
3775
  const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
3472
- if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
3473
3776
  await new Promise((resolve) => setTimeout(resolve, delay));
3474
3777
  }
3475
3778
  }
@@ -3480,11 +3783,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3480
3783
  this.networkCleanup = networkMonitor.addListener((state) => {
3481
3784
  if (state !== "online") return;
3482
3785
  if (this.config && this.apiClient) return;
3483
- if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
3484
3786
  this.initPromise = this.initWithRetry(config);
3485
- this.initPromise.then(() => {
3486
- if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
3487
- }).catch(() => {
3787
+ this.initPromise.catch(() => {
3488
3788
  this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
3489
3789
  });
3490
3790
  });
@@ -3513,38 +3813,79 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3513
3813
  } catch {
3514
3814
  }
3515
3815
  }
3816
+ async resolveSessionFlags(keys, apiClient, distinctId) {
3817
+ try {
3818
+ const results = await apiClient.evaluateFlags(keys, distinctId);
3819
+ this.sessionFlagsMap.clear();
3820
+ for (const key of keys) {
3821
+ this.sessionFlagsMap.set(key, key in results ? results[key] : null);
3822
+ }
3823
+ } catch (error) {
3824
+ for (const key of keys) {
3825
+ this.sessionFlagsMap.set(key, null);
3826
+ }
3827
+ }
3828
+ }
3516
3829
  async doInit(config) {
3517
3830
  const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
3518
3831
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
3519
- if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
3520
3832
  await Promise.all([
3521
3833
  networkMonitor.initialize({ debug: config.debug }),
3522
3834
  offlineQueue.initialize({ debug: config.debug }).then(
3523
3835
  () => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
3524
3836
  )
3525
3837
  ]);
3526
- if (config.debug) console.warn("[Paywallo INIT]", "Step 1 DONE");
3527
- if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
3528
3838
  this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
3529
3839
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3530
3840
  affiliateManager.initialize(this.apiClient, config.debug);
3531
- if (config.debug) console.warn("[Paywallo INIT]", "Step 2 DONE");
3532
- if (config.debug) console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
3841
+ subscriptionManager.init({
3842
+ serverUrl: this.apiClient.getBaseUrl(),
3843
+ appKey: config.appKey,
3844
+ cacheTTL: config.subscriptionCacheTTL,
3845
+ debug: config.debug
3846
+ });
3533
3847
  await Promise.all([
3534
3848
  identityManager.initialize(this.apiClient, config.debug),
3535
3849
  sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
3536
3850
  ]);
3537
3851
  const distinctId = identityManager.getDistinctId();
3538
- if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
3539
3852
  if (config.autoStartSession !== false) {
3540
3853
  sessionManager.startSession().catch(() => {
3541
- if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
3542
3854
  });
3543
3855
  }
3544
3856
  new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
3545
3857
  });
3858
+ if (config.sessionFlags && config.sessionFlags.length > 0) {
3859
+ await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
3860
+ }
3546
3861
  this.config = config;
3547
- if (config.debug) console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
3862
+ if (this.resolveReady) {
3863
+ this.resolveReady();
3864
+ this.resolveReady = null;
3865
+ }
3866
+ if (config.debug) console.info("[Paywallo INIT] complete");
3867
+ if (this.apiClient) {
3868
+ const apiClientRef = this.apiClient;
3869
+ if (config.autoPreloadCampaign) {
3870
+ this.autoPreloadedPlacement = config.autoPreloadCampaign;
3871
+ this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
3872
+ if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
3873
+ void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
3874
+ });
3875
+ } else {
3876
+ this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
3877
+ const placement = primary?.placement ?? primary?.paywall?.placement;
3878
+ if (placement) {
3879
+ this.autoPreloadedPlacement = placement;
3880
+ if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
3881
+ void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
3882
+ });
3883
+ return placement;
3884
+ }
3885
+ return null;
3886
+ }).catch(() => null);
3887
+ }
3888
+ }
3548
3889
  }
3549
3890
  async identify(options) {
3550
3891
  this.ensureInitialized();
@@ -3554,7 +3895,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3554
3895
  const apiClient = this.getApiClientOrThrow();
3555
3896
  const distinctId = identityManager.getDistinctId();
3556
3897
  if (!distinctId) {
3557
- if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
3558
3898
  return;
3559
3899
  }
3560
3900
  if (!isValidEventName(eventName)) {
@@ -3562,25 +3902,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3562
3902
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
3563
3903
  }
3564
3904
  const sessionId = sessionManager.getSessionId();
3565
- if (eventName === "$purchase_completed") {
3566
- if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
3567
- distinctId: distinctId.substring(0, 8) + "...",
3568
- productId: options?.properties?.productId,
3569
- priceUsd: options?.properties?.priceUsd,
3570
- price: options?.properties?.price,
3571
- currency: options?.properties?.currency,
3572
- placement: options?.properties?.placement
3573
- }));
3574
- } else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
3575
- if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
3576
- distinctId: distinctId.substring(0, 8) + "...",
3577
- placement: options?.properties?.placement,
3578
- variantKey: options?.properties?.variantKey,
3579
- timeOnPaywall: options?.properties?.timeOnPaywall
3580
- }));
3581
- } else {
3582
- if (this.config?.debug) console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
3583
- }
3584
3905
  await apiClient.trackEvent(eventName, distinctId, {
3585
3906
  ...identityManager.getProperties(),
3586
3907
  ...options?.properties,
@@ -3591,7 +3912,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3591
3912
  const apiClient = this.getApiClientOrThrow();
3592
3913
  const distinctId = identityManager.getDistinctId();
3593
3914
  if (!distinctId) {
3594
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3595
3915
  return;
3596
3916
  }
3597
3917
  if (!Number.isInteger(index) || index < 0) {
@@ -3615,7 +3935,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3615
3935
  const apiClient = this.getApiClientOrThrow();
3616
3936
  const distinctId = identityManager.getDistinctId();
3617
3937
  if (!distinctId) {
3618
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3619
3938
  return;
3620
3939
  }
3621
3940
  if (!actionName || typeof actionName !== "string") {
@@ -3644,14 +3963,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3644
3963
  try {
3645
3964
  const distinctId = identityManager.getDistinctId();
3646
3965
  if (!distinctId) {
3647
- if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
3648
3966
  return { variant: null };
3649
3967
  }
3650
- const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3651
- if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
3652
- return result;
3968
+ return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3653
3969
  } catch (error) {
3654
- if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
3655
3970
  if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
3656
3971
  return { variant: null };
3657
3972
  }
@@ -3667,7 +3982,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3667
3982
  const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3668
3983
  return result.value;
3669
3984
  } catch (error) {
3670
- if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
3671
3985
  if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
3672
3986
  return false;
3673
3987
  }
@@ -3690,15 +4004,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3690
4004
  async hasActiveSubscription() {
3691
4005
  this.ensureInitialized();
3692
4006
  try {
3693
- if (this.activeChecker) return await this.activeChecker();
4007
+ if (this.activeChecker) {
4008
+ return await this.activeChecker();
4009
+ }
3694
4010
  const distinctId = identityManager.getDistinctId();
3695
- if (!distinctId || !this.apiClient) return false;
4011
+ if (!distinctId || !this.apiClient) {
4012
+ return false;
4013
+ }
3696
4014
  const status = await this.apiClient.getSubscriptionStatus(distinctId);
3697
4015
  return status.hasActiveSubscription;
3698
- } catch {
3699
- if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014 using persistent cache");
3700
- if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", "hasActiveSubscription failed");
3701
- return resolveClientOfflineFromCache();
4016
+ } catch (error) {
4017
+ if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
4018
+ return false;
3702
4019
  }
3703
4020
  }
3704
4021
  async getSubscription() {
@@ -3711,7 +4028,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3711
4028
  if (!status.subscription) return null;
3712
4029
  return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
3713
4030
  } catch {
3714
- if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
3715
4031
  return null;
3716
4032
  }
3717
4033
  }
@@ -3722,20 +4038,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3722
4038
  }
3723
4039
  async reset() {
3724
4040
  await identityManager.reset();
4041
+ await subscriptionCache.invalidateAll();
3725
4042
  }
3726
4043
  async fullReset() {
3727
4044
  const wasDebug = this.config?.debug ?? false;
3728
4045
  await sessionManager.endSession();
3729
4046
  await identityManager.reset();
3730
- await subscriptionCache.invalidate();
4047
+ await subscriptionCache.invalidateAll();
3731
4048
  queueProcessor.dispose();
3732
4049
  offlineQueue.dispose();
3733
4050
  networkMonitor.dispose();
3734
4051
  campaignGateService.clearPreloaded();
3735
4052
  this.cleanupNetworkRecovery();
4053
+ this.sessionFlagsMap.clear();
3736
4054
  this.apiClient = null;
3737
4055
  this.config = null;
3738
4056
  this.initPromise = null;
4057
+ this.readyPromise = null;
4058
+ this.resolveReady = null;
3739
4059
  this.pendingConfig = null;
3740
4060
  this.initAttempts = 0;
3741
4061
  this.paywallPresenter = null;
@@ -3744,7 +4064,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3744
4064
  this.activeChecker = null;
3745
4065
  this.restoreHandler = null;
3746
4066
  this.lastOnboardingStepTimestamp = null;
3747
- if (wasDebug) console.warn("[Paywallo]", "Full reset complete");
4067
+ this.autoPreloadedPlacement = null;
4068
+ this.autoPreloadPlacementPromise = null;
4069
+ }
4070
+ getSessionFlag(key) {
4071
+ if (!this.config || !this.apiClient) return null;
4072
+ const value = this.sessionFlagsMap.get(key);
4073
+ return value !== void 0 ? value : null;
3748
4074
  }
3749
4075
  getConfig() {
3750
4076
  return this.config;
@@ -3833,6 +4159,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3833
4159
  getPreloadedCampaign(placement) {
3834
4160
  return campaignGateService.getPreloadedCampaign(placement);
3835
4161
  }
4162
+ isPreloaded(placement) {
4163
+ return campaignGateService.getPreloadedCampaign(placement) !== null;
4164
+ }
4165
+ getAutoPreloadedPlacement() {
4166
+ return this.autoPreloadedPlacement;
4167
+ }
4168
+ waitForAutoPreloadedPlacement() {
4169
+ return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
4170
+ }
3836
4171
  isOnline() {
3837
4172
  return networkMonitor.isOnline();
3838
4173
  }
@@ -3862,48 +4197,6 @@ var PaywalloClient = new PaywalloClientClass();
3862
4197
  import { createContext as createContext2 } from "react";
3863
4198
  var PaywalloContext = createContext2(null);
3864
4199
 
3865
- // src/utils/localization.ts
3866
- import { NativeModules as NativeModules3, Platform as Platform7 } from "react-native";
3867
- var currentLanguage = "pt-BR";
3868
- var defaultLanguage = "pt-BR";
3869
- function setCurrentLanguage(language) {
3870
- currentLanguage = language;
3871
- }
3872
- function setDefaultLanguage(language) {
3873
- defaultLanguage = language;
3874
- }
3875
- function getCurrentLanguage() {
3876
- return currentLanguage;
3877
- }
3878
- function getDefaultLanguage() {
3879
- return defaultLanguage;
3880
- }
3881
- function getLocalizedString(text) {
3882
- if (!text) return "";
3883
- if (typeof text === "string") return text;
3884
- return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
3885
- }
3886
- function detectDeviceLanguage() {
3887
- try {
3888
- if (Platform7.OS === "ios") {
3889
- const settings = NativeModules3.SettingsManager?.settings;
3890
- return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
3891
- }
3892
- return NativeModules3.I18nManager?.localeIdentifier ?? "en-US";
3893
- } catch {
3894
- return "en-US";
3895
- }
3896
- }
3897
- function initLocalization(options) {
3898
- if (options?.defaultLanguage) {
3899
- defaultLanguage = options.defaultLanguage;
3900
- }
3901
- if (options?.detectDevice !== false) {
3902
- const deviceLanguage = detectDeviceLanguage();
3903
- currentLanguage = deviceLanguage;
3904
- }
3905
- }
3906
-
3907
4200
  // src/hooks/usePaywallo.ts
3908
4201
  import { useContext as useContext2 } from "react";
3909
4202
  function usePaywallo() {
@@ -3929,7 +4222,8 @@ function mapToIAPProduct(product) {
3929
4222
  type: product.type === "subscription" ? "subs" : "inapp",
3930
4223
  subscriptionPeriodAndroid: product.subscriptionPeriod,
3931
4224
  introductoryPrice: product.introductoryPrice,
3932
- freeTrialPeriodAndroid: product.freeTrialPeriod
4225
+ freeTrialPeriodAndroid: product.freeTrialPeriod,
4226
+ trialDays: product.trialDays ?? 0
3933
4227
  };
3934
4228
  }
3935
4229
  function mapToFormattedProduct(product) {
@@ -3944,6 +4238,7 @@ function mapToFormattedProduct(product) {
3944
4238
  subscriptionPeriod: product.subscriptionPeriod,
3945
4239
  introductoryPrice: product.introductoryPrice,
3946
4240
  freeTrialPeriod: product.freeTrialPeriod,
4241
+ trialDays: product.trialDays ?? 0,
3947
4242
  pricePerMonth: product.pricePerMonth,
3948
4243
  pricePerWeek: product.pricePerWeek,
3949
4244
  pricePerDay: product.pricePerDay
@@ -4025,27 +4320,37 @@ function usePurchase() {
4025
4320
  setError(null);
4026
4321
  try {
4027
4322
  const iapService = getIAPService();
4028
- const result = await iapService.purchase(productId);
4323
+ const result = await iapService.purchase(productId, {
4324
+ onValidating: () => setState("validating")
4325
+ });
4029
4326
  if (result.success && result.purchase) {
4030
4327
  setState("idle");
4031
4328
  return {
4032
- productId: result.purchase.productId,
4033
- transactionId: result.purchase.transactionId,
4034
- transactionDate: result.purchase.transactionDate,
4035
- transactionReceipt: result.purchase.receipt
4329
+ status: "success",
4330
+ purchase: {
4331
+ productId: result.purchase.productId,
4332
+ transactionId: result.purchase.transactionId,
4333
+ transactionDate: result.purchase.transactionDate,
4334
+ transactionReceipt: result.purchase.receipt
4335
+ }
4036
4336
  };
4037
4337
  }
4038
4338
  if (result.error) {
4039
- const purchaseError = createPurchaseError("PURCHASE_FAILED", result.error.message);
4040
- setError(purchaseError);
4339
+ setError(result.error);
4041
4340
  setState("error");
4042
- throw result.error;
4341
+ return { status: "failed", error: result.error };
4043
4342
  }
4044
4343
  setState("idle");
4045
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, "Purchase cancelled by user", true);
4344
+ return { status: "cancelled" };
4046
4345
  } catch (err) {
4346
+ const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
4347
+ if (purchaseError.userCancelled) {
4348
+ setState("idle");
4349
+ return { status: "cancelled" };
4350
+ }
4351
+ setError(purchaseError);
4047
4352
  setState("error");
4048
- throw err;
4353
+ return { status: "failed", error: purchaseError };
4049
4354
  }
4050
4355
  }, []);
4051
4356
  const restore = useCallback7(async () => {
@@ -4074,146 +4379,14 @@ function usePurchase() {
4074
4379
  restore,
4075
4380
  error,
4076
4381
  isLoading: state === "loading",
4077
- isPurchasing: state === "purchasing",
4382
+ isPurchasing: state === "purchasing" || state === "validating",
4383
+ isValidating: state === "validating",
4078
4384
  isRestoring: state === "restoring"
4079
4385
  };
4080
4386
  }
4081
4387
 
4082
4388
  // src/hooks/useSubscription.ts
4083
4389
  import { useCallback as useCallback8, useEffect as useEffect6, useState as useState8 } from "react";
4084
-
4085
- // src/domains/subscription/SubscriptionManager.ts
4086
- import { Platform as Platform8 } from "react-native";
4087
- var SubscriptionManagerClass = class {
4088
- constructor() {
4089
- this.config = null;
4090
- this.listeners = /* @__PURE__ */ new Set();
4091
- this.userId = null;
4092
- this.cache = new SubscriptionCache();
4093
- }
4094
- init(config) {
4095
- this.config = config;
4096
- if (config.cacheTTL) {
4097
- this.cache.setTTL(config.cacheTTL);
4098
- }
4099
- }
4100
- setUserId(userId) {
4101
- if (this.userId !== userId) {
4102
- this.userId = userId;
4103
- void this.cache.invalidate();
4104
- }
4105
- }
4106
- async hasActiveSubscription(forceRefresh = false) {
4107
- const status = await this.getSubscriptionStatus(forceRefresh);
4108
- return status.hasActiveSubscription;
4109
- }
4110
- async getSubscription(forceRefresh = false) {
4111
- const status = await this.getSubscriptionStatus(forceRefresh);
4112
- return status.subscription;
4113
- }
4114
- async getEntitlements(forceRefresh = false) {
4115
- const status = await this.getSubscriptionStatus(forceRefresh);
4116
- return status.entitlements;
4117
- }
4118
- async getSubscriptionStatus(forceRefresh = false) {
4119
- if (!this.config) {
4120
- return this.getEmptyStatus();
4121
- }
4122
- if (!forceRefresh) {
4123
- const cached = await this.cache.get();
4124
- if (cached && !cached.isStale) {
4125
- return cached.data;
4126
- }
4127
- }
4128
- try {
4129
- const status = await this.fetchSubscriptionStatus();
4130
- await this.cache.set(status);
4131
- this.notifyListeners(status);
4132
- return status;
4133
- } catch (error) {
4134
- this.log("Failed to fetch subscription status", error);
4135
- const cached = await this.cache.get();
4136
- if (cached) {
4137
- return cached.data;
4138
- }
4139
- return this.getEmptyStatus();
4140
- }
4141
- }
4142
- async restorePurchases() {
4143
- if (!this.config) {
4144
- throw new SessionError(
4145
- SESSION_ERROR_CODES.NOT_INITIALIZED,
4146
- "SubscriptionManager not initialized"
4147
- );
4148
- }
4149
- await this.cache.invalidate();
4150
- return this.getSubscriptionStatus(true);
4151
- }
4152
- async fetchSubscriptionStatus() {
4153
- if (!this.config) {
4154
- return this.getEmptyStatus();
4155
- }
4156
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
4157
- if (this.userId) {
4158
- url.searchParams.set("userId", this.userId);
4159
- }
4160
- url.searchParams.set("platform", Platform8.OS);
4161
- const response = await fetch(url.toString(), {
4162
- method: "GET",
4163
- headers: {
4164
- "Content-Type": "application/json",
4165
- "X-App-Key": this.config.appKey
4166
- }
4167
- });
4168
- if (!response.ok) {
4169
- throw new SessionError(
4170
- SESSION_ERROR_CODES.RESTORE_FAILED,
4171
- `Failed to fetch subscription status: ${response.status}`
4172
- );
4173
- }
4174
- const data = await response.json();
4175
- if (data.subscription) {
4176
- data.subscription.expiresAt = new Date(data.subscription.expiresAt);
4177
- data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
4178
- data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
4179
- if (data.subscription.cancellationDate) {
4180
- data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
4181
- }
4182
- if (data.subscription.gracePeriodExpiresAt) {
4183
- data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
4184
- }
4185
- }
4186
- return data;
4187
- }
4188
- getEmptyStatus() {
4189
- return {
4190
- hasActiveSubscription: false,
4191
- subscription: null,
4192
- entitlements: []
4193
- };
4194
- }
4195
- addListener(listener) {
4196
- this.listeners.add(listener);
4197
- return () => this.listeners.delete(listener);
4198
- }
4199
- notifyListeners(status) {
4200
- for (const listener of this.listeners) {
4201
- listener(status);
4202
- }
4203
- }
4204
- async onPurchaseComplete() {
4205
- await this.cache.invalidate();
4206
- await this.getSubscriptionStatus(true);
4207
- }
4208
- log(message, data) {
4209
- if (this.config?.debug) {
4210
- console.warn(`[SubscriptionManager] ${message}`, data ?? "");
4211
- }
4212
- }
4213
- };
4214
- var subscriptionManager = new SubscriptionManagerClass();
4215
-
4216
- // src/hooks/useSubscription.ts
4217
4390
  function useSubscription() {
4218
4391
  const [status, setStatus] = useState8(null);
4219
4392
  const [isLoading, setIsLoading] = useState8(true);
@@ -4268,7 +4441,7 @@ function useSubscription() {
4268
4441
  }
4269
4442
 
4270
4443
  // src/PaywalloProvider.tsx
4271
- import { useCallback as useCallback13, useEffect as useEffect7, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
4444
+ import { useCallback as useCallback13, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
4272
4445
  import { Animated as Animated3, Dimensions as Dimensions4, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
4273
4446
 
4274
4447
  // src/hooks/useCampaignPreload.ts
@@ -4330,10 +4503,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
4330
4503
  function isCacheEntryValid(entry) {
4331
4504
  return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
4332
4505
  }
4333
- function consumeCacheEntry(cache, placement) {
4506
+ function getCacheEntry(cache, placement) {
4334
4507
  const entry = cache.get(placement);
4335
4508
  if (!isCacheEntryValid(entry)) return null;
4336
- cache.delete(placement);
4337
4509
  return entry ?? null;
4338
4510
  }
4339
4511
 
@@ -4346,7 +4518,7 @@ function useCampaignPreload() {
4346
4518
  []
4347
4519
  );
4348
4520
  const consumePreloadedCampaign = useCallback9(
4349
- (placement) => consumeCacheEntry(preloadedCampaignsRef.current, placement),
4521
+ (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
4350
4522
  []
4351
4523
  );
4352
4524
  const preloadCampaign = useCallback9(
@@ -4394,6 +4566,7 @@ function useCampaignPreload() {
4394
4566
  campaignId: campaign.campaignId,
4395
4567
  variantKey: campaign.variantKey
4396
4568
  });
4569
+ if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
4397
4570
  return { success: true };
4398
4571
  } catch (error) {
4399
4572
  return {
@@ -4428,7 +4601,6 @@ function useProductLoader() {
4428
4601
  for (const p of loaded) map.set(p.productId, p);
4429
4602
  setProducts(map);
4430
4603
  } catch (error) {
4431
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
4432
4604
  } finally {
4433
4605
  setIsLoadingProducts(false);
4434
4606
  }
@@ -4438,7 +4610,7 @@ function useProductLoader() {
4438
4610
 
4439
4611
  // src/hooks/usePurchaseOrchestrator.ts
4440
4612
  import { useCallback as useCallback11 } from "react";
4441
- function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
4613
+ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
4442
4614
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
4443
4615
  const handlePreloadedPurchase = useCallback11(
4444
4616
  async (productId) => {
@@ -4463,17 +4635,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4463
4635
  await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4464
4636
  await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4465
4637
  }
4638
+ invalidateSubscriptionCache();
4466
4639
  setShowingPreloadedWebView(false);
4467
4640
  clearPreloadedWebView();
4468
4641
  resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
4469
4642
  } catch (error) {
4470
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
4471
4643
  setShowingPreloadedWebView(false);
4472
4644
  clearPreloadedWebView();
4473
4645
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
4474
4646
  }
4475
4647
  },
4476
- [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
4648
+ [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
4477
4649
  );
4478
4650
  const handlePreloadedRestore = useCallback11(async () => {
4479
4651
  try {
@@ -4483,16 +4655,16 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4483
4655
  const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
4484
4656
  await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4485
4657
  }
4658
+ invalidateSubscriptionCache();
4486
4659
  setShowingPreloadedWebView(false);
4487
4660
  clearPreloadedWebView();
4488
4661
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
4489
4662
  } catch (error) {
4490
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
4491
4663
  setShowingPreloadedWebView(false);
4492
4664
  clearPreloadedWebView();
4493
4665
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
4494
4666
  }
4495
- }, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
4667
+ }, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
4496
4668
  return { handlePreloadedPurchase, handlePreloadedRestore };
4497
4669
  }
4498
4670
 
@@ -4504,7 +4676,6 @@ function toDomainSubscriptionInfo(sub) {
4504
4676
  return {
4505
4677
  id: "",
4506
4678
  productId: sub.productId,
4507
- // The hook's SubscriptionStatus is a subset of the domain's — cast is safe
4508
4679
  status: sub.status,
4509
4680
  expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
4510
4681
  originalPurchaseDate: /* @__PURE__ */ new Date(0),
@@ -4522,31 +4693,32 @@ function toDomainResponse(status) {
4522
4693
  entitlements: []
4523
4694
  };
4524
4695
  }
4525
- async function resolveOfflineFromCache() {
4696
+ function isSubscriptionStillActive(sub) {
4697
+ if (!sub) return false;
4698
+ if (!ACTIVE_STATUSES.includes(sub.status)) return false;
4699
+ if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
4700
+ return true;
4701
+ }
4702
+ async function resolveOfflineFromCache(distinctId) {
4526
4703
  try {
4527
- const cached = await subscriptionCache.get();
4704
+ const cached = await subscriptionCache.get(distinctId);
4528
4705
  if (!cached) return false;
4529
4706
  const sub = cached.data.subscription;
4530
- if (!sub) return false;
4531
- if (!ACTIVE_STATUSES.includes(sub.status)) return false;
4532
- const hasValidExpiry = sub.expiresAt.getTime() === 0 || sub.expiresAt > /* @__PURE__ */ new Date();
4533
- return hasValidExpiry;
4707
+ return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
4534
4708
  } catch {
4535
4709
  return false;
4536
4710
  }
4537
4711
  }
4538
4712
  function isActiveCacheEntry(entry) {
4539
- if (!entry.data) return false;
4540
- if (!ACTIVE_STATUSES.includes(entry.data.status)) return false;
4541
- if (entry.data.expiresAt && entry.data.expiresAt < /* @__PURE__ */ new Date()) return false;
4542
- return true;
4713
+ return isSubscriptionStillActive(entry.data);
4543
4714
  }
4544
4715
  function useSubscriptionSync() {
4545
4716
  const [subscription, setSubscription] = useState11(null);
4546
4717
  const cacheRef = useRef7(null);
4547
4718
  const invalidateCache = useCallback12(() => {
4548
4719
  cacheRef.current = null;
4549
- void subscriptionCache.invalidate();
4720
+ const distinctId = PaywalloClient.getDistinctId();
4721
+ if (distinctId) void subscriptionCache.invalidate(distinctId);
4550
4722
  }, []);
4551
4723
  const fetchAndCache = useCallback12(async () => {
4552
4724
  const apiClient = PaywalloClient.getApiClient();
@@ -4559,7 +4731,7 @@ function useSubscriptionSync() {
4559
4731
  } : null;
4560
4732
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4561
4733
  setSubscription(sub);
4562
- void subscriptionCache.set(toDomainResponse(status));
4734
+ void subscriptionCache.set(distinctId, toDomainResponse(status));
4563
4735
  return sub;
4564
4736
  }, []);
4565
4737
  const hasActiveSubscription = useCallback12(async () => {
@@ -4571,23 +4743,23 @@ function useSubscriptionSync() {
4571
4743
  return isActiveCacheEntry(cached);
4572
4744
  }
4573
4745
  try {
4574
- const persisted = await subscriptionCache.get();
4575
- if (persisted) {
4576
- if (!persisted.isStale) {
4577
- const sub = persisted.data.subscription ? {
4578
- productId: persisted.data.subscription.productId,
4579
- status: persisted.data.subscription.status,
4580
- expiresAt: persisted.data.subscription.expiresAt ?? null,
4581
- platform: persisted.data.subscription.platform,
4582
- autoRenewEnabled: persisted.data.subscription.willRenew,
4583
- inGracePeriod: persisted.data.subscription.status === "grace_period"
4584
- } : null;
4746
+ const persisted = await subscriptionCache.get(distinctId);
4747
+ if (persisted && !persisted.isStale) {
4748
+ const persistedSub = persisted.data.subscription;
4749
+ const sub = persistedSub ? {
4750
+ productId: persistedSub.productId,
4751
+ status: persistedSub.status,
4752
+ expiresAt: persistedSub.expiresAt ?? null,
4753
+ platform: persistedSub.platform,
4754
+ autoRenewEnabled: persistedSub.willRenew,
4755
+ inGracePeriod: persistedSub.status === "grace_period"
4756
+ } : null;
4757
+ const stillActive = isSubscriptionStillActive(sub);
4758
+ if (stillActive) {
4585
4759
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4586
4760
  setSubscription(sub);
4587
- return persisted.data.hasActiveSubscription;
4761
+ return true;
4588
4762
  }
4589
- void fetchAndCache();
4590
- return persisted.data.hasActiveSubscription;
4591
4763
  }
4592
4764
  } catch {
4593
4765
  }
@@ -4599,12 +4771,12 @@ function useSubscriptionSync() {
4599
4771
  } : null;
4600
4772
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4601
4773
  setSubscription(sub);
4602
- void subscriptionCache.set(toDomainResponse(status));
4774
+ void subscriptionCache.set(distinctId, toDomainResponse(status));
4603
4775
  return status.hasActiveSubscription;
4604
4776
  } catch {
4605
- return await resolveOfflineFromCache();
4777
+ return await resolveOfflineFromCache(distinctId);
4606
4778
  }
4607
- }, [fetchAndCache]);
4779
+ }, []);
4608
4780
  const getSubscription = useCallback12(async () => {
4609
4781
  const apiClient = PaywalloClient.getApiClient();
4610
4782
  const distinctId = PaywalloClient.getDistinctId();
@@ -4615,7 +4787,7 @@ function useSubscriptionSync() {
4615
4787
  return await fetchAndCache();
4616
4788
  } catch {
4617
4789
  try {
4618
- const persisted = await subscriptionCache.get();
4790
+ const persisted = await subscriptionCache.get(distinctId);
4619
4791
  if (!persisted?.data.subscription) return null;
4620
4792
  const s = persisted.data.subscription;
4621
4793
  const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
@@ -4646,13 +4818,38 @@ function PaywalloProvider({ children, config }) {
4646
4818
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
4647
4819
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
4648
4820
  const clearPreloadedWebView = useCallback13(() => setPreloadedWebView(null), [setPreloadedWebView]);
4649
- const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreloadedWebView);
4821
+ const autoPreloadTriggeredRef = useRef8(false);
4822
+ const preloadedWebViewRef = useRef8(preloadedWebView);
4823
+ useEffect7(() => {
4824
+ preloadedWebViewRef.current = preloadedWebView;
4825
+ }, [preloadedWebView]);
4826
+ const triggerRepreload = useCallback13((placement) => {
4827
+ void PaywalloClient.preloadCampaign(placement).catch(() => {
4828
+ });
4829
+ }, []);
4830
+ const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
4831
+ const SCREEN_HEIGHT3 = useMemo2(() => Dimensions4.get("window").height, []);
4832
+ const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
4833
+ const handlePreloadedWebViewReady = useCallback13(() => {
4834
+ if (PaywalloClient.getConfig()?.debug) {
4835
+ console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
4836
+ }
4837
+ baseHandlePreloadedWebViewReady();
4838
+ }, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
4839
+ const handlePreloadedClose = useCallback13(() => {
4840
+ const placement = preloadedWebView?.placement;
4841
+ Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
4842
+ baseHandlePreloadedClose();
4843
+ if (placement) triggerRepreload(placement);
4844
+ });
4845
+ }, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
4650
4846
  const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
4651
4847
  preloadedWebView,
4652
4848
  resolvePreloadedPaywall,
4653
4849
  clearPreloadedWebView,
4654
4850
  setShowingPreloadedWebView,
4655
- presentedAtRef
4851
+ presentedAtRef,
4852
+ invalidateCache
4656
4853
  );
4657
4854
  const [isPreloadPurchasing, setIsPreloadPurchasing] = useState12(false);
4658
4855
  const handlePreloadedPurchase = useCallback13(async (productId) => {
@@ -4665,13 +4862,19 @@ function PaywalloProvider({ children, config }) {
4665
4862
  }, [rawPreloadedPurchase]);
4666
4863
  useEffect7(() => {
4667
4864
  initLocalization({ detectDevice: true });
4668
- PaywalloClient.init(config).then(() => {
4865
+ PaywalloClient.init(config).then(async () => {
4669
4866
  setDistinctId(PaywalloClient.getDistinctId());
4670
4867
  setIsInitialized(true);
4868
+ if (!autoPreloadTriggeredRef.current) {
4869
+ autoPreloadTriggeredRef.current = true;
4870
+ const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
4871
+ if (placement) void preloadCampaign(placement).catch(() => {
4872
+ });
4873
+ }
4671
4874
  }).catch((err) => {
4672
4875
  setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
4673
4876
  });
4674
- }, [config]);
4877
+ }, [config, preloadCampaign]);
4675
4878
  const getPaywallConfig = useCallback13(async (p) => {
4676
4879
  const api = PaywalloClient.getApiClient();
4677
4880
  return api ? api.getPaywall(p) : null;
@@ -4770,28 +4973,54 @@ function PaywalloProvider({ children, config }) {
4770
4973
  setActivePaywall(null);
4771
4974
  }
4772
4975
  }, [activePaywall]);
4976
+ const bridgePaywallPresenter = useCallback13((placement) => {
4977
+ if (PaywalloClient.getConfig()?.debug) {
4978
+ console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
4979
+ }
4980
+ if (preloadedWebViewRef.current?.placement === placement) {
4981
+ if (preloadedWebViewRef.current.loaded) {
4982
+ return presentPreloadedWebView();
4983
+ }
4984
+ return new Promise((resolve) => {
4985
+ let elapsed = 0;
4986
+ const poll = () => {
4987
+ if (preloadedWebViewRef.current?.loaded) {
4988
+ resolve(presentPreloadedWebView());
4989
+ return;
4990
+ }
4991
+ elapsed += 100;
4992
+ if (elapsed >= 500) {
4993
+ resolve(presentCampaign(placement));
4994
+ return;
4995
+ }
4996
+ setTimeout(poll, 100);
4997
+ };
4998
+ setTimeout(poll, 100);
4999
+ });
5000
+ }
5001
+ return presentCampaign(placement);
5002
+ }, [presentPreloadedWebView, presentCampaign]);
4773
5003
  useEffect7(() => {
4774
5004
  if (!isInitialized) return;
4775
5005
  const onEmergency = async (id) => {
4776
5006
  try {
4777
- await presentPaywall(id);
5007
+ await bridgePaywallPresenter(id);
4778
5008
  } catch {
4779
5009
  }
4780
5010
  };
4781
- PaywalloClient.registerPaywallPresenter(presentPaywall);
5011
+ PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
4782
5012
  PaywalloClient.registerSubscriptionGetter(getSubscription);
4783
5013
  PaywalloClient.registerActiveChecker(hasActiveSubscription);
4784
5014
  PaywalloClient.registerRestoreHandler(restorePurchases);
4785
5015
  PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
4786
- }, [isInitialized, presentPaywall, getSubscription, hasActiveSubscription, restorePurchases]);
4787
- useEffect7(() => {
4788
- if (activePaywall) setPreloadedWebView(null);
4789
- }, [activePaywall, setPreloadedWebView]);
5016
+ }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
5017
+ const isReady = isInitialized && !isLoadingProducts;
4790
5018
  const noOp = useCallback13(() => {
4791
5019
  }, []);
4792
5020
  const contextValue = useMemo2(() => ({
4793
5021
  config: PaywalloClient.getConfig(),
4794
5022
  isInitialized,
5023
+ isReady,
4795
5024
  initError,
4796
5025
  distinctId,
4797
5026
  subscription,
@@ -4807,6 +5036,7 @@ function PaywalloProvider({ children, config }) {
4807
5036
  refreshProducts
4808
5037
  }), [
4809
5038
  isInitialized,
5039
+ isReady,
4810
5040
  initError,
4811
5041
  distinctId,
4812
5042
  subscription,
@@ -4821,20 +5051,16 @@ function PaywalloProvider({ children, config }) {
4821
5051
  getPaywallConfig,
4822
5052
  refreshProducts
4823
5053
  ]);
4824
- const SCREEN_HEIGHT3 = useMemo2(() => Dimensions4.get("window").height, []);
4825
- const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
4826
- useEffect7(() => {
5054
+ useLayoutEffect(() => {
4827
5055
  if (showingPreloadedWebView) {
4828
5056
  slideAnim.setValue(SCREEN_HEIGHT3);
4829
- Animated3.timing(slideAnim, { toValue: 0, duration: 320, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
4830
- } else {
4831
- slideAnim.setValue(SCREEN_HEIGHT3);
5057
+ Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
4832
5058
  }
4833
5059
  }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
4834
5060
  const preloadStyle = [
4835
5061
  styles3.preloadOverlay,
4836
5062
  showingPreloadedWebView ? styles3.visible : styles3.hidden,
4837
- { transform: [{ translateY: showingPreloadedWebView ? slideAnim : SCREEN_HEIGHT3 }] }
5063
+ showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
4838
5064
  ];
4839
5065
  return /* @__PURE__ */ jsxs2(PaywalloContext.Provider, { value: contextValue, children: [
4840
5066
  children,
@@ -4847,6 +5073,7 @@ function PaywalloProvider({ children, config }) {
4847
5073
  primaryProductId: preloadedWebView.primaryProductId,
4848
5074
  secondaryProductId: preloadedWebView.secondaryProductId,
4849
5075
  isPurchasing: isPreloadPurchasing,
5076
+ webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
4850
5077
  onReady: handlePreloadedWebViewReady,
4851
5078
  onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
4852
5079
  onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
@@ -4869,8 +5096,8 @@ function PaywalloProvider({ children, config }) {
4869
5096
  }
4870
5097
  var styles3 = StyleSheet3.create({
4871
5098
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
4872
- hidden: { zIndex: -1 },
4873
- visible: { zIndex: 9999 }
5099
+ hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
5100
+ visible: { zIndex: 9999, opacity: 1 }
4874
5101
  });
4875
5102
 
4876
5103
  // src/utils/productVariableResolver.ts