@virex-tech/paywallo-sdk 1.1.3 → 1.4.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.js CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,25 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/utils/uuid.ts
34
+ var uuid_exports = {};
35
+ __export(uuid_exports, {
36
+ generateUUID: () => generateUUID
37
+ });
38
+ function generateUUID() {
39
+ const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
40
+ return template.replace(/[xy]/g, (c) => {
41
+ const r = Math.random() * 16 | 0;
42
+ const v = c === "x" ? r : r & 3 | 8;
43
+ return v.toString(16);
44
+ });
45
+ }
46
+ var init_uuid = __esm({
47
+ "src/utils/uuid.ts"() {
48
+ "use strict";
49
+ }
50
+ });
51
+
30
52
  // src/index.ts
31
53
  var index_exports = {};
32
54
  __export(index_exports, {
@@ -44,6 +66,7 @@ __export(index_exports, {
44
66
  IDENTITY_ERROR_CODES: () => IDENTITY_ERROR_CODES,
45
67
  IdentityError: () => IdentityError,
46
68
  IdentityManager: () => IdentityManager,
69
+ MetaBridge: () => MetaBridge,
47
70
  PAYWALL_ERROR_CODES: () => PAYWALL_ERROR_CODES,
48
71
  PURCHASE_ERROR_CODES: () => PURCHASE_ERROR_CODES,
49
72
  PaywallContext: () => PaywallContext,
@@ -71,6 +94,7 @@ __export(index_exports, {
71
94
  hasVariables: () => hasVariables,
72
95
  identityManager: () => identityManager,
73
96
  initLocalization: () => initLocalization,
97
+ metaBridge: () => metaBridge,
74
98
  nativeStoreKit: () => nativeStoreKit,
75
99
  networkMonitor: () => networkMonitor,
76
100
  offlineQueue: () => offlineQueue,
@@ -130,7 +154,7 @@ var CLIENT_ERROR_CODES = {
130
154
 
131
155
  // src/domains/paywall/PaywallModal.tsx
132
156
  var import_react5 = require("react");
133
- var import_react_native5 = require("react-native");
157
+ var import_react_native6 = require("react-native");
134
158
 
135
159
  // src/domains/paywall/usePaywallActions.ts
136
160
  var import_react2 = require("react");
@@ -184,6 +208,9 @@ function createPurchaseError(code, message) {
184
208
  // src/domains/iap/NativeStoreKit.ts
185
209
  var import_react_native = require("react-native");
186
210
 
211
+ // src/domains/identity/IdentityManager.ts
212
+ var import_react_native_device_info = __toESM(require("react-native-device-info"));
213
+
187
214
  // src/domains/identity/IdentityError.ts
188
215
  var IdentityError = class extends PaywalloError {
189
216
  constructor(code, message) {
@@ -199,37 +226,17 @@ var IDENTITY_ERROR_CODES = {
199
226
  IDENTIFY_FAILED: "IDENTITY_IDENTIFY_FAILED"
200
227
  };
201
228
 
202
- // src/utils/uuid.ts
203
- function generateUUID() {
204
- const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
205
- return template.replace(/[xy]/g, (c) => {
206
- const r = Math.random() * 16 | 0;
207
- const v = c === "x" ? r : r & 3 | 8;
208
- return v.toString(16);
209
- });
210
- }
229
+ // src/domains/identity/IdentityManager.ts
230
+ init_uuid();
211
231
 
212
232
  // src/core/storage/SecureStorage.ts
213
233
  var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
214
- var _keychain = void 0;
215
- async function loadKeychain() {
216
- if (_keychain !== void 0) return _keychain;
217
- try {
218
- const mod = await import("react-native-keychain");
219
- _keychain = mod.default;
220
- } catch {
221
- _keychain = null;
222
- }
223
- return _keychain;
224
- }
234
+ var import_react_native_keychain = __toESM(require("react-native-keychain"));
225
235
  var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
226
236
  var SecureStorage = class {
227
237
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
228
238
  constructor(_debug = false) {
229
239
  }
230
- /**
231
- * Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
232
- */
233
240
  async get(key) {
234
241
  const keychainValue = await this.getFromKeychain(key);
235
242
  if (keychainValue) {
@@ -245,9 +252,6 @@ var SecureStorage = class {
245
252
  return null;
246
253
  }
247
254
  }
248
- /**
249
- * Set a value in both Keychain (if available) and AsyncStorage.
250
- */
251
255
  async set(key, value) {
252
256
  try {
253
257
  await Promise.all([
@@ -271,13 +275,11 @@ var SecureStorage = class {
271
275
  }
272
276
  }
273
277
  hasKeychainSupport() {
274
- return _keychain !== null && _keychain !== void 0;
278
+ return true;
275
279
  }
276
280
  async getFromKeychain(key) {
277
- const keychain = await loadKeychain();
278
- if (!keychain) return null;
279
281
  try {
280
- const result = await keychain.getGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
282
+ const result = await import_react_native_keychain.default.getGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
281
283
  if (result && result.password) {
282
284
  return result.password;
283
285
  }
@@ -287,20 +289,16 @@ var SecureStorage = class {
287
289
  }
288
290
  }
289
291
  async setToKeychain(key, value) {
290
- const keychain = await loadKeychain();
291
- if (!keychain) return false;
292
292
  try {
293
- await keychain.setGenericPassword("panel", value, { service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
293
+ await import_react_native_keychain.default.setGenericPassword("panel", value, { service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
294
294
  return true;
295
295
  } catch {
296
296
  return false;
297
297
  }
298
298
  }
299
299
  async removeFromKeychain(key) {
300
- const keychain = await loadKeychain();
301
- if (!keychain) return false;
302
300
  try {
303
- await keychain.resetGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
301
+ await import_react_native_keychain.default.resetGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
304
302
  return true;
305
303
  } catch {
306
304
  return false;
@@ -311,11 +309,13 @@ var secureStorage = new SecureStorage();
311
309
 
312
310
  // src/domains/identity/IdentityManager.ts
313
311
  var DEVICE_ID_KEY = "device_id";
312
+ var ANON_ID_KEY = "anon_id";
314
313
  var USER_EMAIL_KEY = "user_email";
315
314
  var USER_PROPERTIES_KEY = "user_properties";
316
315
  var IdentityManager = class {
317
316
  constructor() {
318
317
  this.deviceId = null;
318
+ this.anonId = null;
319
319
  this.email = null;
320
320
  this.properties = {};
321
321
  this.apiClient = null;
@@ -332,33 +332,22 @@ var IdentityManager = class {
332
332
  this.secureStorage = new SecureStorage(debug);
333
333
  await this.loadPersistedState();
334
334
  if (!this.deviceId) {
335
- let DeviceInfo = null;
336
- try {
337
- const mod = await import("react-native-device-info");
338
- DeviceInfo = mod.default;
339
- } catch {
340
- DeviceInfo = null;
341
- }
342
335
  try {
343
- if (DeviceInfo) {
344
- this.deviceId = await DeviceInfo.getUniqueId();
345
- this.log("Got device ID from DeviceInfo:", this.deviceId);
346
- } else {
347
- this.deviceId = generateUUID();
348
- this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
349
- }
336
+ this.deviceId = await import_react_native_device_info.default.getUniqueId();
350
337
  } catch {
351
338
  this.deviceId = generateUUID();
352
- this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
353
339
  }
354
340
  const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
355
341
  if (!stored) {
356
- this.log("Failed to persist device ID to secure storage, using fallback");
357
342
  await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
358
- this.log("Fallback also failed \u2014 device ID only in memory");
359
343
  });
360
344
  }
361
345
  }
346
+ if (!this.anonId) {
347
+ this.anonId = "$paywallo_anon:" + generateUUID();
348
+ await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
349
+ });
350
+ }
362
351
  this.initialized = true;
363
352
  return this.deviceId ?? "";
364
353
  }
@@ -382,21 +371,18 @@ var IdentityManager = class {
382
371
  this.properties = { ...this.properties, ...properties };
383
372
  await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
384
373
  }
385
- if (this.apiClient && this.deviceId) {
374
+ if (this.apiClient && this.anonId) {
386
375
  try {
387
- await this.apiClient.identify(this.deviceId, properties, email);
388
- this.log("Identity updated on server");
389
- } catch (error) {
390
- this.log("Failed to update identity on server:", error);
376
+ await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
377
+ } catch {
391
378
  }
392
379
  }
393
380
  }
394
381
  getDistinctId() {
395
- if (!this.initialized || !this.deviceId) {
396
- if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
382
+ if (!this.initialized || !this.anonId) {
397
383
  return "";
398
384
  }
399
- return this.deviceId;
385
+ return this.anonId;
400
386
  }
401
387
  getDeviceId() {
402
388
  return this.deviceId;
@@ -411,24 +397,22 @@ var IdentityManager = class {
411
397
  this.ensureInitialized();
412
398
  this.properties = { ...this.properties, ...properties };
413
399
  await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
414
- if (this.apiClient && this.deviceId) {
400
+ if (this.apiClient && this.anonId) {
415
401
  try {
416
- await this.apiClient.identify(this.deviceId, this.properties, this.email ?? void 0);
417
- } catch (error) {
418
- this.log("Failed to update properties on server:", error);
402
+ await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
403
+ } catch {
419
404
  }
420
405
  }
421
406
  }
422
407
  async reset() {
408
+ const newAnonId = "$paywallo_anon:" + generateUUID();
409
+ this.anonId = newAnonId;
423
410
  this.email = null;
424
411
  this.properties = {};
425
- this.initialized = false;
426
- this.apiClient = null;
427
- this.deviceId = null;
428
412
  await Promise.all([
413
+ this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
429
414
  this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
430
- this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false),
431
- this.secureStorage?.remove("device_id_fallback") ?? Promise.resolve(false)
415
+ this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
432
416
  ]);
433
417
  }
434
418
  getState() {
@@ -445,21 +429,23 @@ var IdentityManager = class {
445
429
  if (!this.secureStorage) {
446
430
  return;
447
431
  }
448
- const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
432
+ const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
449
433
  this.secureStorage.get(DEVICE_ID_KEY),
434
+ this.secureStorage.get(ANON_ID_KEY),
450
435
  this.secureStorage.get(USER_EMAIL_KEY),
451
436
  this.secureStorage.get(USER_PROPERTIES_KEY)
452
437
  ]);
453
438
  if (storedDeviceId) {
454
439
  this.deviceId = storedDeviceId;
455
- this.log("Loaded device ID from storage:", this.deviceId);
456
440
  } else {
457
441
  const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
458
442
  if (fallbackDeviceId) {
459
443
  this.deviceId = fallbackDeviceId;
460
- this.log("Loaded device ID from fallback storage:", this.deviceId);
461
444
  }
462
445
  }
446
+ if (storedAnonId) {
447
+ this.anonId = storedAnonId;
448
+ }
463
449
  if (storedEmail) {
464
450
  this.email = storedEmail;
465
451
  }
@@ -479,11 +465,6 @@ var IdentityManager = class {
479
465
  );
480
466
  }
481
467
  }
482
- log(...args) {
483
- if (this.debug) {
484
- console.warn("[Paywallo Identity]", ...args);
485
- }
486
- }
487
468
  };
488
469
  var identityManager = new IdentityManager();
489
470
 
@@ -568,7 +549,6 @@ var nativeStoreKit = {
568
549
  isAvailable,
569
550
  async getProducts(productIds) {
570
551
  if (!PaywalloStoreKitNative) {
571
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
572
552
  return [];
573
553
  }
574
554
  const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
@@ -582,20 +562,41 @@ var nativeStoreKit = {
582
562
  );
583
563
  }
584
564
  const distinctId = identityManager.getDistinctId();
585
- const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
586
- if (!result || result.pending) {
587
- return null;
565
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
566
+ if (distinctId && !uuidRegex.test(distinctId) && PaywalloClient.getConfig()?.debug) {
567
+ console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
568
+ }
569
+ try {
570
+ const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
571
+ if (!result || result.pending) {
572
+ return null;
573
+ }
574
+ return mapNativePurchase(result);
575
+ } catch (err) {
576
+ const message = err instanceof Error ? err.message : String(err);
577
+ if (message.toLowerCase().includes("cancel")) {
578
+ throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
579
+ }
580
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
588
581
  }
589
- return mapNativePurchase(result);
590
582
  },
591
583
  async finishTransaction(transactionId) {
592
584
  if (!PaywalloStoreKitNative) return;
593
- await PaywalloStoreKitNative.finishTransaction(transactionId);
585
+ try {
586
+ await PaywalloStoreKitNative.finishTransaction(transactionId);
587
+ } catch (err) {
588
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction native error", err);
589
+ }
594
590
  },
595
591
  async getActiveTransactions() {
596
592
  if (!PaywalloStoreKitNative) return [];
597
- const results = await PaywalloStoreKitNative.getActiveTransactions();
598
- return results.map(mapNativePurchase);
593
+ try {
594
+ const results = await PaywalloStoreKitNative.getActiveTransactions();
595
+ return results.map(mapNativePurchase);
596
+ } catch (err) {
597
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] getActiveTransactions failed", err);
598
+ return [];
599
+ }
599
600
  }
600
601
  };
601
602
 
@@ -638,8 +639,8 @@ var IAPService = class {
638
639
  this.productsCache.set(product.productId, product);
639
640
  }
640
641
  return products;
641
- } catch (error) {
642
- if (this.debug) console.warn("[Paywallo][Products] StoreKit error:", error);
642
+ } catch (err) {
643
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts failed", err);
643
644
  return [];
644
645
  }
645
646
  }
@@ -649,35 +650,47 @@ var IAPService = class {
649
650
  getCachedProducts() {
650
651
  return new Map(this.productsCache);
651
652
  }
653
+ async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
654
+ let lastError;
655
+ for (let i = 0; i <= maxRetries; i++) {
656
+ try {
657
+ return await attempt();
658
+ } catch (error) {
659
+ lastError = error;
660
+ if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
661
+ }
662
+ }
663
+ throw lastError;
664
+ }
652
665
  async validateWithServer(purchase, productId, options) {
653
666
  const apiClient = this.getApiClient();
654
667
  const product = this.productsCache.get(productId);
655
668
  const platform = import_react_native2.Platform.OS;
656
669
  const distinctId = PaywalloClient.getDistinctId();
657
- const validation = await apiClient.validatePurchase(
658
- platform,
659
- purchase.receipt,
660
- purchase.productId,
661
- purchase.transactionId,
662
- product?.priceValue ?? 0,
663
- product?.currency ?? "USD",
664
- this.getDeviceCountry(),
665
- distinctId,
666
- options?.paywallPlacement,
667
- options?.variantKey
670
+ return this.validateWithRetry(
671
+ () => apiClient.validatePurchase(
672
+ platform,
673
+ purchase.receipt,
674
+ purchase.productId,
675
+ purchase.transactionId,
676
+ product?.priceValue ?? 0,
677
+ product?.currency ?? "USD",
678
+ this.getDeviceCountry(),
679
+ distinctId,
680
+ options?.paywallPlacement,
681
+ options?.variantKey
682
+ )
668
683
  );
669
- return validation;
670
684
  }
671
685
  async finishTransaction(transactionId) {
672
686
  try {
673
687
  await nativeStoreKit.finishTransaction(transactionId);
674
- } catch (finishError) {
675
- if (this.debug) console.warn("[Paywallo][Purchase] Failed to finish transaction:", finishError instanceof Error ? finishError.message : finishError);
688
+ } catch (err) {
689
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
676
690
  }
677
691
  }
678
692
  async purchase(productId, options) {
679
693
  if (!nativeStoreKit.isAvailable()) {
680
- if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
681
694
  return {
682
695
  success: false,
683
696
  error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
@@ -688,27 +701,13 @@ var IAPService = class {
688
701
  if (!purchase) {
689
702
  return { success: false };
690
703
  }
691
- let validation = null;
692
- try {
693
- validation = await this.validateWithServer(purchase, productId, options);
694
- } catch (validationError) {
695
- if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
696
- return {
697
- success: false,
698
- error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
699
- };
700
- }
701
- if (!validation?.success) {
702
- return {
703
- success: false,
704
- error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validation?.error ? `Server validation: ${validation.error}` : "Server validation failed")
705
- };
706
- }
707
704
  await this.finishTransaction(purchase.transactionId);
705
+ this.validateWithServer(purchase, productId, options).catch((err) => {
706
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] background validation failed", err);
707
+ });
708
708
  return { success: true, purchase };
709
709
  } catch (error) {
710
710
  const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
711
- if (this.debug) console.warn("[Paywallo][Purchase] FAILED:", e.message);
712
711
  return { success: false, error: e };
713
712
  }
714
713
  }
@@ -743,15 +742,15 @@ var IAPService = class {
743
742
  if (r.status === "fulfilled" && r.value.success) {
744
743
  try {
745
744
  await nativeStoreKit.finishTransaction(tx.transactionId);
746
- } catch (finishError) {
747
- if (this.debug) console.warn("[Paywallo][Restore] Failed to finish tx:", tx.transactionId, finishError instanceof Error ? finishError.message : finishError);
745
+ } catch (err) {
746
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
748
747
  }
749
748
  validated.push(tx);
750
749
  }
751
750
  }
752
751
  return validated;
753
- } catch (error) {
754
- if (this.debug) console.warn("[Paywallo][Restore] \u274C FAILED:", error instanceof Error ? error.message : error);
752
+ } catch (err) {
753
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] restore failed", err);
755
754
  return [];
756
755
  }
757
756
  }
@@ -781,7 +780,8 @@ function usePaywallTracking() {
781
780
  ...opts.campaignId && { campaignId: opts.campaignId },
782
781
  ...sessionId && { sessionId }
783
782
  });
784
- } catch {
783
+ } catch (err) {
784
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackDismissed failed", err);
785
785
  }
786
786
  }, []);
787
787
  const trackProductSelected = (0, import_react.useCallback)((opts) => {
@@ -795,7 +795,8 @@ function usePaywallTracking() {
795
795
  ...opts.variantKey && { variantKey: opts.variantKey },
796
796
  ...opts.campaignId && { campaignId: opts.campaignId },
797
797
  ...sessionId && { sessionId }
798
- }).catch(() => {
798
+ }).catch((err) => {
799
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackProductSelected failed", err);
799
800
  });
800
801
  }, []);
801
802
  const trackPurchased = (0, import_react.useCallback)(async (opts) => {
@@ -811,7 +812,8 @@ function usePaywallTracking() {
811
812
  ...opts.campaignId && { campaignId: opts.campaignId },
812
813
  ...sessionId && { sessionId }
813
814
  });
814
- } catch {
815
+ } catch (err) {
816
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackPurchased failed", err);
815
817
  }
816
818
  }, []);
817
819
  return { trackDismissed, trackProductSelected, trackPurchased };
@@ -819,7 +821,7 @@ function usePaywallTracking() {
819
821
 
820
822
  // src/domains/paywall/usePaywallActions.ts
821
823
  var SCREEN_HEIGHT = import_react_native3.Dimensions.get("window").height;
822
- function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
824
+ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
823
825
  const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
824
826
  const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
825
827
  const slideAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
@@ -828,7 +830,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
828
830
  const handleClose = (0, import_react2.useCallback)(() => {
829
831
  if (isClosing) return;
830
832
  setIsClosing(true);
831
- import_react_native3.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 300, useNativeDriver: true }).start(() => {
833
+ const animTarget = openAnim ?? slideAnim;
834
+ import_react_native3.Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
832
835
  if (paywallConfig) {
833
836
  const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
834
837
  void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
@@ -837,7 +840,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
837
840
  setIsClosing(false);
838
841
  onResult({ presented: true, purchased: false, cancelled: true, restored: false });
839
842
  });
840
- }, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
843
+ }, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
841
844
  const handlePurchase = (0, import_react2.useCallback)(async (productId) => {
842
845
  if (isPurchasing) return;
843
846
  setIsPurchasing(true);
@@ -855,7 +858,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
855
858
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
856
859
  }
857
860
  } catch (error) {
858
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
859
861
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
860
862
  } finally {
861
863
  setIsPurchasing(false);
@@ -876,7 +878,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
876
878
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
877
879
  }
878
880
  } catch (error) {
879
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
880
881
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
881
882
  } finally {
882
883
  setIsPurchasing(false);
@@ -952,7 +953,8 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
952
953
  const map = /* @__PURE__ */ new Map();
953
954
  for (const p of storeProducts) map.set(p.productId, p);
954
955
  setProducts(map);
955
- } catch {
956
+ } catch (err) {
957
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts in paywall failed", err);
956
958
  setProducts(/* @__PURE__ */ new Map());
957
959
  }
958
960
  } else {
@@ -971,7 +973,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
971
973
  }
972
974
  } catch (err) {
973
975
  const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
974
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
975
976
  setError(e);
976
977
  }
977
978
  };
@@ -980,11 +981,96 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
980
981
  return { paywallConfig, products, error };
981
982
  }
982
983
 
984
+ // src/utils/localization.ts
985
+ var import_react_native4 = require("react-native");
986
+ var currentLanguage = "pt-BR";
987
+ var defaultLanguage = "pt-BR";
988
+ function setCurrentLanguage(language) {
989
+ currentLanguage = language;
990
+ }
991
+ function setDefaultLanguage(language) {
992
+ defaultLanguage = language;
993
+ }
994
+ function getCurrentLanguage() {
995
+ return currentLanguage;
996
+ }
997
+ function getDefaultLanguage() {
998
+ return defaultLanguage;
999
+ }
1000
+ function getLocalizedString(text) {
1001
+ if (!text) return "";
1002
+ if (typeof text === "string") return text;
1003
+ return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
1004
+ }
1005
+ function detectDeviceLanguage() {
1006
+ try {
1007
+ if (import_react_native4.Platform.OS === "ios") {
1008
+ const settings = import_react_native4.NativeModules.SettingsManager?.settings;
1009
+ return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
1010
+ }
1011
+ return import_react_native4.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
1012
+ } catch {
1013
+ return "en-US";
1014
+ }
1015
+ }
1016
+ var SDK_STRINGS = {
1017
+ paywallErrorTitle: {
1018
+ "pt-BR": "Algo deu errado",
1019
+ "pt": "Algo deu errado",
1020
+ "en": "Something went wrong",
1021
+ "en-US": "Something went wrong",
1022
+ "en-GB": "Something went wrong",
1023
+ "es": "Algo sali\xF3 mal",
1024
+ "es-419": "Algo sali\xF3 mal"
1025
+ },
1026
+ paywallRetryButton: {
1027
+ "pt-BR": "Tentar novamente",
1028
+ "pt": "Tentar novamente",
1029
+ "en": "Try again",
1030
+ "en-US": "Try again",
1031
+ "en-GB": "Try again",
1032
+ "es": "Reintentar",
1033
+ "es-419": "Reintentar"
1034
+ },
1035
+ paywallCloseButton: {
1036
+ "pt-BR": "Fechar",
1037
+ "pt": "Fechar",
1038
+ "en": "Close",
1039
+ "en-US": "Close",
1040
+ "en-GB": "Close",
1041
+ "es": "Cerrar",
1042
+ "es-419": "Cerrar"
1043
+ }
1044
+ };
1045
+ function getSdkString(key) {
1046
+ const map = SDK_STRINGS[key];
1047
+ const lang = currentLanguage;
1048
+ if (lang in map) return map[lang];
1049
+ const base = lang.split("-")[0];
1050
+ if (base && base in map) return map[base];
1051
+ if (defaultLanguage in map) return map[defaultLanguage];
1052
+ return Object.values(map)[0];
1053
+ }
1054
+ function initLocalization(options) {
1055
+ if (options?.defaultLanguage) {
1056
+ defaultLanguage = options.defaultLanguage;
1057
+ }
1058
+ if (options?.detectDevice !== false) {
1059
+ const deviceLanguage = detectDeviceLanguage();
1060
+ currentLanguage = deviceLanguage;
1061
+ }
1062
+ }
1063
+
983
1064
  // src/domains/paywall/PaywallWebView.tsx
984
1065
  var import_react4 = require("react");
985
- var import_react_native4 = require("react-native");
1066
+ var import_react_native5 = require("react-native");
986
1067
 
987
1068
  // src/domains/paywall/parseWebViewMessage.ts
1069
+ function deriveMessageId(message) {
1070
+ if (message.id) return message.id;
1071
+ const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
1072
+ return `${message.type}:${payloadKey}`;
1073
+ }
988
1074
  function parseWebViewMessage(raw) {
989
1075
  try {
990
1076
  const parsed = JSON.parse(raw);
@@ -1008,7 +1094,7 @@ function buildPaywallInjectionScript(data) {
1008
1094
  secondaryProductId: data.secondaryProductId
1009
1095
  }
1010
1096
  };
1011
- 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;`;
1097
+ 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;`;
1012
1098
  }
1013
1099
  function buildPurchaseStateScript(isPurchasing) {
1014
1100
  return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
@@ -1019,14 +1105,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
1019
1105
  var NativeWebViewComponent = null;
1020
1106
  function getNativeWebView() {
1021
1107
  if (!NativeWebViewComponent) {
1022
- NativeWebViewComponent = (0, import_react_native4.requireNativeComponent)("PaywalloWebView");
1108
+ NativeWebViewComponent = (0, import_react_native5.requireNativeComponent)("PaywalloWebView");
1023
1109
  }
1024
1110
  return NativeWebViewComponent;
1025
1111
  }
1026
1112
  var webViewEmitter = null;
1027
1113
  function getWebViewEmitter() {
1028
1114
  if (!webViewEmitter) {
1029
- webViewEmitter = new import_react_native4.NativeEventEmitter(import_react_native4.NativeModules.PaywalloWebViewModule);
1115
+ webViewEmitter = new import_react_native5.NativeEventEmitter(import_react_native5.NativeModules.PaywalloWebViewModule);
1030
1116
  }
1031
1117
  return webViewEmitter;
1032
1118
  }
@@ -1044,6 +1130,7 @@ function PaywallWebView({
1044
1130
  primaryProductId,
1045
1131
  secondaryProductId,
1046
1132
  isPurchasing,
1133
+ webUrl: webUrlProp,
1047
1134
  onPurchase,
1048
1135
  onClose,
1049
1136
  onRestore,
@@ -1055,8 +1142,39 @@ function PaywallWebView({
1055
1142
  const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
1056
1143
  const onErrorRef = (0, import_react4.useRef)(onError);
1057
1144
  onErrorRef.current = onError;
1145
+ const onReadyRef = (0, import_react4.useRef)(onReady);
1146
+ onReadyRef.current = onReady;
1058
1147
  const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
1059
- const webUrl = (0, import_react4.useMemo)(() => PaywalloClient.getWebUrl() ?? "http://localhost:3000", []);
1148
+ const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react4.useState)(
1149
+ () => webUrlProp ?? PaywalloClient.getWebUrl()
1150
+ );
1151
+ (0, import_react4.useEffect)(() => {
1152
+ if (webUrlProp !== void 0) {
1153
+ setResolvedWebUrl(webUrlProp ?? null);
1154
+ return;
1155
+ }
1156
+ const url = PaywalloClient.getWebUrl();
1157
+ if (url) {
1158
+ setResolvedWebUrl(url);
1159
+ return;
1160
+ }
1161
+ const interval = setInterval(() => {
1162
+ const u = PaywalloClient.getWebUrl();
1163
+ if (u) {
1164
+ setResolvedWebUrl(u);
1165
+ clearInterval(interval);
1166
+ }
1167
+ }, 200);
1168
+ return () => clearInterval(interval);
1169
+ }, [webUrlProp]);
1170
+ (0, import_react4.useEffect)(() => {
1171
+ if (!resolvedWebUrl) {
1172
+ onErrorRef.current?.({
1173
+ code: -1,
1174
+ description: "Paywall URL not configured. SDK may not be initialized."
1175
+ });
1176
+ }
1177
+ }, []);
1060
1178
  const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
1061
1179
  if (!craftData || paywallDataSent.current) return;
1062
1180
  paywallDataSent.current = true;
@@ -1067,19 +1185,21 @@ function PaywallWebView({
1067
1185
  secondaryProductId
1068
1186
  });
1069
1187
  setScriptToInject(script);
1070
- onReady?.();
1071
- }, [craftData, products, primaryProductId, secondaryProductId, onReady]);
1072
- const lastProcessedRef = (0, import_react4.useRef)({ type: "", time: 0 });
1188
+ }, [craftData, products, primaryProductId, secondaryProductId]);
1189
+ const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
1073
1190
  const handleParsedMessage = (0, import_react4.useCallback)(
1074
1191
  (message) => {
1075
- const now = Date.now();
1076
- if (message.type === lastProcessedRef.current.type && now - lastProcessedRef.current.time < 500) {
1077
- return;
1078
- }
1079
- lastProcessedRef.current = { type: message.type, time: now };
1192
+ const msgId = deriveMessageId(message);
1193
+ if (processedIdsRef.current.has(msgId)) return;
1194
+ processedIdsRef.current.add(msgId);
1195
+ setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
1080
1196
  switch (message.type) {
1081
1197
  case "ready":
1082
- if (!paywallDataSent.current) buildAndSetInjectionScript();
1198
+ if (!paywallDataSent.current) {
1199
+ buildAndSetInjectionScript();
1200
+ } else {
1201
+ onReadyRef.current?.();
1202
+ }
1083
1203
  break;
1084
1204
  case "purchase":
1085
1205
  if (message.payload?.productId) onPurchase(message.payload.productId);
@@ -1094,7 +1214,7 @@ function PaywallWebView({
1094
1214
  if (message.payload?.url) {
1095
1215
  const url = message.payload.url;
1096
1216
  if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
1097
- void import_react_native4.Linking.openURL(url);
1217
+ void import_react_native5.Linking.openURL(url);
1098
1218
  }
1099
1219
  }
1100
1220
  break;
@@ -1170,8 +1290,9 @@ function PaywallWebView({
1170
1290
  if (!paywallDataSent.current) return;
1171
1291
  setScriptToInject(buildPurchaseStateScript(isPurchasing));
1172
1292
  }, [isPurchasing]);
1173
- const paywallUrl = `${webUrl}/paywall/${paywallId}`;
1174
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1293
+ if (!resolvedWebUrl) return null;
1294
+ const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
1295
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native5.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1175
1296
  NativeWebView,
1176
1297
  {
1177
1298
  sourceUrl: paywallUrl,
@@ -1183,14 +1304,26 @@ function PaywallWebView({
1183
1304
  }
1184
1305
  ) });
1185
1306
  }
1186
- var styles = import_react_native4.StyleSheet.create({
1307
+ var styles = import_react_native5.StyleSheet.create({
1187
1308
  container: { flex: 1 },
1188
1309
  webview: { flex: 1, backgroundColor: "transparent" }
1189
1310
  });
1190
1311
 
1191
1312
  // src/domains/paywall/PaywallModal.tsx
1192
1313
  var import_jsx_runtime2 = require("react/jsx-runtime");
1193
- var SCREEN_HEIGHT2 = import_react_native5.Dimensions.get("window").height;
1314
+ var SCREEN_HEIGHT2 = import_react_native6.Dimensions.get("window").height;
1315
+ function ErrorFallback({ description, onRetry, onClose }) {
1316
+ const overrides = PaywalloClient.getConfig()?.errorStrings;
1317
+ const title = overrides?.title ?? getSdkString("paywallErrorTitle");
1318
+ const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
1319
+ const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native6.View, { style: styles2.fallbackCard, children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackTitle, children: title }),
1322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackDescription, children: description }),
1323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.retryButtonText, children: retryLabel }) }),
1324
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.closeButtonText, children: closeLabel }) })
1325
+ ] }) });
1326
+ }
1194
1327
  function PaywallModal({
1195
1328
  placement,
1196
1329
  visible,
@@ -1208,7 +1341,13 @@ function PaywallModal({
1208
1341
  variantKey,
1209
1342
  campaignId
1210
1343
  );
1211
- const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId);
1344
+ const openAnim = (0, import_react5.useRef)(new import_react_native6.Animated.Value(SCREEN_HEIGHT2)).current;
1345
+ const [modalVisible, setModalVisible] = (0, import_react5.useState)(false);
1346
+ const handleResult = (0, import_react5.useCallback)((result) => {
1347
+ setModalVisible(false);
1348
+ onResult(result);
1349
+ }, [onResult]);
1350
+ const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
1212
1351
  const [webViewError, setWebViewError] = (0, import_react5.useState)(null);
1213
1352
  const [retryCount, setRetryCount] = (0, import_react5.useState)(0);
1214
1353
  const retryCountRef = (0, import_react5.useRef)(0);
@@ -1228,36 +1367,38 @@ function PaywallModal({
1228
1367
  setWebViewError(null);
1229
1368
  setRetryCount(0);
1230
1369
  }, []);
1231
- const openAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(SCREEN_HEIGHT2)).current;
1232
1370
  (0, import_react5.useEffect)(() => {
1233
1371
  if (visible) {
1372
+ setModalVisible(true);
1234
1373
  openAnim.setValue(SCREEN_HEIGHT2);
1235
- import_react_native5.Animated.timing(openAnim, {
1374
+ import_react_native6.Animated.timing(openAnim, {
1236
1375
  toValue: 0,
1237
1376
  duration: 550,
1238
- easing: import_react_native5.Easing.out(import_react_native5.Easing.cubic),
1377
+ easing: import_react_native6.Easing.out(import_react_native6.Easing.cubic),
1239
1378
  useNativeDriver: true
1240
1379
  }).start();
1241
1380
  }
1242
1381
  }, [visible, openAnim]);
1243
- if (!visible) return null;
1382
+ if (!modalVisible && !visible) return null;
1244
1383
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1245
- import_react_native5.Modal,
1384
+ import_react_native6.Modal,
1246
1385
  {
1247
- visible,
1386
+ visible: modalVisible,
1248
1387
  animationType: "none",
1249
1388
  presentationStyle: "overFullScreen",
1250
1389
  onRequestClose: handleClose,
1251
1390
  transparent: true,
1252
1391
  statusBarTranslucent: true,
1253
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles2.container, children: [
1254
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.errorText, children: error.message }) }),
1255
- !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles2.fallbackCard, children: [
1256
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.fallbackTitle, children: "Algo deu errado" }),
1257
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.fallbackDescription, children: webViewError.description }),
1258
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Pressable, { style: styles2.retryButton, onPress: handleRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.retryButtonText, children: "Tentar novamente" }) }),
1259
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Pressable, { style: styles2.closeButton, onPress: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.closeButtonText, children: "Fechar" }) })
1260
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1392
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native6.View, { style: styles2.container, children: [
1393
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.errorText, children: error.message }) }),
1394
+ !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1395
+ ErrorFallback,
1396
+ {
1397
+ description: webViewError.description,
1398
+ onRetry: handleRetry,
1399
+ onClose: handleClose
1400
+ }
1401
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1261
1402
  PaywallWebView,
1262
1403
  {
1263
1404
  paywallId: paywallConfig.id,
@@ -1277,7 +1418,7 @@ function PaywallModal({
1277
1418
  }
1278
1419
  );
1279
1420
  }
1280
- var styles2 = import_react_native5.StyleSheet.create({
1421
+ var styles2 = import_react_native6.StyleSheet.create({
1281
1422
  overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
1282
1423
  animatedContainer: { flex: 1, backgroundColor: "#fff" },
1283
1424
  container: { flex: 1, backgroundColor: "#fff" },
@@ -1342,7 +1483,11 @@ var PaywallErrorBoundary = class extends import_react6.Component {
1342
1483
  return { hasError: true };
1343
1484
  }
1344
1485
  componentDidCatch(error) {
1345
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
1486
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] Paywall render error:", error);
1487
+ try {
1488
+ PaywalloClient.getApiClient()?.reportError("PAYWALL_RENDER_ERROR", error.message);
1489
+ } catch {
1490
+ }
1346
1491
  }
1347
1492
  render() {
1348
1493
  return this.state.hasError ? null : this.props.children;
@@ -1365,7 +1510,7 @@ function usePaywallContext() {
1365
1510
 
1366
1511
  // src/domains/paywall/usePaywallPresenter.ts
1367
1512
  var import_react8 = require("react");
1368
- function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreloadedWebView) {
1513
+ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1369
1514
  const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react8.useState)(false);
1370
1515
  const resolverRef = (0, import_react8.useRef)(null);
1371
1516
  const presentedAtRef = (0, import_react8.useRef)(Date.now());
@@ -1414,9 +1559,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
1414
1559
  };
1415
1560
  void track();
1416
1561
  setShowingPreloadedWebView(false);
1417
- clearPreloadedWebView();
1418
1562
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
1419
- }, [preloadedWebView, clearPreloadedWebView, resolvePreloadedPaywall]);
1563
+ }, [preloadedWebView, resolvePreloadedPaywall]);
1420
1564
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1421
1565
  }
1422
1566
 
@@ -1624,9 +1768,7 @@ var AffiliateManager = class {
1624
1768
  }
1625
1769
  }
1626
1770
  log(...args) {
1627
- if (this.debug) {
1628
- console.warn("[AffiliateManager]", ...args);
1629
- }
1771
+ if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
1630
1772
  }
1631
1773
  };
1632
1774
  var affiliateManager = new AffiliateManager();
@@ -1644,26 +1786,42 @@ var PaywalloAffiliate = {
1644
1786
  };
1645
1787
 
1646
1788
  // src/domains/subscription/SubscriptionCache.ts
1647
- var CACHE_KEY = "subscription_cache";
1789
+ var LEGACY_CACHE_KEY = "subscription_cache";
1790
+ var CACHE_KEY_PREFIX = "subscription_cache:";
1648
1791
  var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
1792
+ function keyFor(distinctId) {
1793
+ return `${CACHE_KEY_PREFIX}${distinctId}`;
1794
+ }
1649
1795
  var SubscriptionCache = class {
1650
1796
  constructor(ttl = DEFAULT_TTL) {
1651
- this.memoryCache = null;
1797
+ this.memoryCache = /* @__PURE__ */ new Map();
1798
+ this.legacyCleanupDone = false;
1652
1799
  this.ttl = ttl;
1653
1800
  this.storage = new SecureStorage();
1654
1801
  }
1655
- async get() {
1656
- if (this.memoryCache && !this.isExpired(this.memoryCache)) {
1657
- return this.memoryCache;
1802
+ async cleanupLegacyCache() {
1803
+ if (this.legacyCleanupDone) return;
1804
+ this.legacyCleanupDone = true;
1805
+ try {
1806
+ await this.storage.remove(LEGACY_CACHE_KEY);
1807
+ } catch {
1808
+ }
1809
+ }
1810
+ async get(distinctId) {
1811
+ await this.cleanupLegacyCache();
1812
+ if (!distinctId) return null;
1813
+ const memEntry = this.memoryCache.get(distinctId);
1814
+ if (memEntry && !this.isExpired(memEntry)) {
1815
+ return memEntry;
1658
1816
  }
1659
1817
  try {
1660
- const stored = await this.storage.get(CACHE_KEY);
1818
+ const stored = await this.storage.get(keyFor(distinctId));
1661
1819
  if (!stored) {
1662
1820
  return null;
1663
1821
  }
1664
1822
  const parsed = JSON.parse(stored);
1665
1823
  if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
1666
- await this.storage.remove(CACHE_KEY);
1824
+ await this.storage.remove(keyFor(distinctId));
1667
1825
  return null;
1668
1826
  }
1669
1827
  const cached = parsed;
@@ -1692,32 +1850,39 @@ var SubscriptionCache = class {
1692
1850
  }
1693
1851
  cached.isStale = this.isExpired(cached);
1694
1852
  if (!cached.isStale) {
1695
- this.memoryCache = cached;
1853
+ this.memoryCache.set(distinctId, cached);
1696
1854
  }
1697
1855
  return cached;
1698
1856
  } catch {
1699
1857
  return null;
1700
1858
  }
1701
1859
  }
1702
- async set(data) {
1860
+ async set(distinctId, data) {
1861
+ if (!distinctId) return;
1703
1862
  const cached = {
1704
1863
  data,
1705
1864
  cachedAt: Date.now(),
1706
1865
  isStale: false
1707
1866
  };
1708
- this.memoryCache = cached;
1867
+ this.memoryCache.set(distinctId, cached);
1709
1868
  try {
1710
- await this.storage.set(CACHE_KEY, JSON.stringify(cached));
1711
- } catch (error) {
1712
- if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
1869
+ await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
1870
+ } catch {
1713
1871
  }
1714
1872
  }
1715
- async invalidate() {
1716
- this.memoryCache = null;
1873
+ async invalidate(distinctId) {
1874
+ if (!distinctId) return;
1875
+ this.memoryCache.delete(distinctId);
1717
1876
  try {
1718
- await this.storage.remove(CACHE_KEY);
1719
- } catch (error) {
1720
- if (__DEV__) console.warn("[SubscriptionCache] invalidate() failed:", error instanceof Error ? error.message : String(error));
1877
+ await this.storage.remove(keyFor(distinctId));
1878
+ } catch {
1879
+ }
1880
+ }
1881
+ async invalidateAll() {
1882
+ this.memoryCache.clear();
1883
+ try {
1884
+ await this.storage.remove(LEGACY_CACHE_KEY);
1885
+ } catch {
1721
1886
  }
1722
1887
  }
1723
1888
  isExpired(cached) {
@@ -1729,60 +1894,214 @@ var SubscriptionCache = class {
1729
1894
  };
1730
1895
  var subscriptionCache = new SubscriptionCache();
1731
1896
 
1732
- // src/core/ApiClient.ts
1897
+ // src/domains/subscription/SubscriptionManager.ts
1733
1898
  var import_react_native7 = require("react-native");
1734
1899
 
1735
- // src/core/apiUrlUtils.ts
1736
- var DEFAULT_WEB_URL = "https://paywallo.com.br";
1737
- function deriveWebUrl(baseUrl) {
1738
- try {
1739
- const url = new URL(baseUrl);
1740
- if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
1741
- url.port = "3000";
1742
- return url.toString().replace(/\/$/, "");
1900
+ // src/domains/session/SessionError.ts
1901
+ var SessionError = class extends PaywalloError {
1902
+ constructor(code, message) {
1903
+ super("session", code, message);
1904
+ this.name = "SessionError";
1905
+ }
1906
+ };
1907
+ var SESSION_ERROR_CODES = {
1908
+ NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
1909
+ START_FAILED: "SESSION_START_FAILED",
1910
+ END_FAILED: "SESSION_END_FAILED",
1911
+ RESTORE_FAILED: "SESSION_RESTORE_FAILED"
1912
+ };
1913
+
1914
+ // src/domains/subscription/SubscriptionManager.ts
1915
+ var SubscriptionManagerClass = class {
1916
+ constructor() {
1917
+ this.config = null;
1918
+ this.listeners = /* @__PURE__ */ new Set();
1919
+ this.userId = null;
1920
+ this.cache = new SubscriptionCache();
1921
+ }
1922
+ cacheKey() {
1923
+ return this.userId ?? "__anonymous__";
1924
+ }
1925
+ init(config) {
1926
+ this.config = config;
1927
+ if (config.cacheTTL) {
1928
+ this.cache.setTTL(config.cacheTTL);
1743
1929
  }
1744
- if (url.hostname.startsWith("api.")) {
1745
- url.hostname = url.hostname.replace("api.", "app.");
1746
- return url.toString().replace(/\/$/, "");
1930
+ }
1931
+ setUserId(userId) {
1932
+ if (this.userId !== userId) {
1933
+ this.userId = userId;
1934
+ void this.cache.invalidateAll();
1747
1935
  }
1748
- return DEFAULT_WEB_URL;
1749
- } catch {
1750
- return DEFAULT_WEB_URL;
1751
1936
  }
1752
- }
1753
- function buildConditionalFlagUrl(baseUrl, flagKey, context) {
1754
- const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
1755
- if (context.platform) url.searchParams.set("platform", context.platform);
1756
- if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
1757
- if (context.country) url.searchParams.set("country", context.country);
1758
- if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
1759
- return url.toString();
1760
- }
1761
-
1762
- // src/domains/iap/apiPurchaseMethods.ts
1763
- async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
1764
- const response = await client.post(
1765
- `/purchases/validate/${appKey}`,
1766
- {
1767
- platform,
1768
- receipt,
1769
- productId,
1770
- transactionId,
1771
- priceLocal,
1772
- currency,
1773
- country,
1774
- distinctId,
1775
- ...paywallPlacement !== void 0 && { paywallPlacement },
1776
- ...variantKey !== void 0 && { variantKey }
1777
- },
1778
- false
1779
- );
1780
- return response.data;
1781
- }
1782
- async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
1937
+ async hasActiveSubscription(forceRefresh = false) {
1938
+ const status = await this.getSubscriptionStatus(forceRefresh);
1939
+ return status.hasActiveSubscription;
1940
+ }
1941
+ async getSubscription(forceRefresh = false) {
1942
+ const status = await this.getSubscriptionStatus(forceRefresh);
1943
+ return status.subscription;
1944
+ }
1945
+ async getEntitlements(forceRefresh = false) {
1946
+ const status = await this.getSubscriptionStatus(forceRefresh);
1947
+ return status.entitlements;
1948
+ }
1949
+ async getSubscriptionStatus(forceRefresh = false) {
1950
+ if (!this.config) {
1951
+ return this.getEmptyStatus();
1952
+ }
1953
+ if (!forceRefresh) {
1954
+ const cached = await this.cache.get(this.cacheKey());
1955
+ if (cached && !cached.isStale) {
1956
+ return cached.data;
1957
+ }
1958
+ }
1959
+ try {
1960
+ const status = await this.fetchSubscriptionStatus();
1961
+ await this.cache.set(this.cacheKey(), status);
1962
+ this.notifyListeners(status);
1963
+ return status;
1964
+ } catch (error) {
1965
+ this.log("Failed to fetch subscription status", error);
1966
+ const cached = await this.cache.get(this.cacheKey());
1967
+ if (cached) {
1968
+ return cached.data;
1969
+ }
1970
+ return this.getEmptyStatus();
1971
+ }
1972
+ }
1973
+ async restorePurchases() {
1974
+ if (!this.config) {
1975
+ throw new SessionError(
1976
+ SESSION_ERROR_CODES.NOT_INITIALIZED,
1977
+ "SubscriptionManager not initialized"
1978
+ );
1979
+ }
1980
+ await this.cache.invalidate(this.cacheKey());
1981
+ return this.getSubscriptionStatus(true);
1982
+ }
1983
+ async fetchSubscriptionStatus() {
1984
+ if (!this.config) {
1985
+ return this.getEmptyStatus();
1986
+ }
1987
+ const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
1988
+ if (this.userId) {
1989
+ url.searchParams.set("userId", this.userId);
1990
+ }
1991
+ url.searchParams.set("platform", import_react_native7.Platform.OS);
1992
+ const response = await fetch(url.toString(), {
1993
+ method: "GET",
1994
+ headers: {
1995
+ "Content-Type": "application/json",
1996
+ "X-App-Key": this.config.appKey
1997
+ }
1998
+ });
1999
+ if (!response.ok) {
2000
+ throw new SessionError(
2001
+ SESSION_ERROR_CODES.RESTORE_FAILED,
2002
+ `Failed to fetch subscription status: ${response.status}`
2003
+ );
2004
+ }
2005
+ const data = await response.json();
2006
+ if (data.subscription) {
2007
+ data.subscription.expiresAt = new Date(data.subscription.expiresAt);
2008
+ data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
2009
+ data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
2010
+ if (data.subscription.cancellationDate) {
2011
+ data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
2012
+ }
2013
+ if (data.subscription.gracePeriodExpiresAt) {
2014
+ data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
2015
+ }
2016
+ }
2017
+ return data;
2018
+ }
2019
+ getEmptyStatus() {
2020
+ return {
2021
+ hasActiveSubscription: false,
2022
+ subscription: null,
2023
+ entitlements: []
2024
+ };
2025
+ }
2026
+ addListener(listener) {
2027
+ this.listeners.add(listener);
2028
+ return () => this.listeners.delete(listener);
2029
+ }
2030
+ notifyListeners(status) {
2031
+ for (const listener of this.listeners) {
2032
+ listener(status);
2033
+ }
2034
+ }
2035
+ async onPurchaseComplete() {
2036
+ await this.cache.invalidate(this.cacheKey());
2037
+ await this.getSubscriptionStatus(true);
2038
+ }
2039
+ log(...args) {
2040
+ if (this.config?.debug) console.log("[Paywallo:Subscription]", ...args);
2041
+ }
2042
+ };
2043
+ var subscriptionManager = new SubscriptionManagerClass();
2044
+
2045
+ // src/core/ApiClient.ts
2046
+ var import_react_native9 = require("react-native");
2047
+
2048
+ // src/core/apiUrlUtils.ts
2049
+ var DEFAULT_WEB_URL = "https://paywallo.com.br";
2050
+ function deriveWebUrl(baseUrl) {
2051
+ try {
2052
+ const url = new URL(baseUrl);
2053
+ if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
2054
+ url.port = "3000";
2055
+ return url.toString().replace(/\/$/, "");
2056
+ }
2057
+ if (url.hostname.startsWith("api.")) {
2058
+ url.hostname = url.hostname.replace("api.", "app.");
2059
+ return url.toString().replace(/\/$/, "");
2060
+ }
2061
+ return DEFAULT_WEB_URL;
2062
+ } catch {
2063
+ return DEFAULT_WEB_URL;
2064
+ }
2065
+ }
2066
+ function buildConditionalFlagUrl(baseUrl, flagKey, context) {
2067
+ const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
2068
+ if (context.platform) url.searchParams.set("platform", context.platform);
2069
+ if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
2070
+ if (context.country) url.searchParams.set("country", context.country);
2071
+ if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
2072
+ return url.toString();
2073
+ }
2074
+
2075
+ // src/domains/iap/apiPurchaseMethods.ts
2076
+ async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
2077
+ const response = await client.post(
2078
+ `/purchases/validate/${appKey}`,
2079
+ {
2080
+ platform,
2081
+ receipt,
2082
+ productId,
2083
+ transactionId,
2084
+ priceLocal,
2085
+ currency,
2086
+ country,
2087
+ distinctId,
2088
+ ...paywallPlacement !== void 0 && { paywallPlacement },
2089
+ ...variantKey !== void 0 && { variantKey }
2090
+ },
2091
+ false
2092
+ );
2093
+ if (!response.ok) {
2094
+ throw new Error(`Server validation failed: HTTP ${response.status}`);
2095
+ }
2096
+ return response.data;
2097
+ }
2098
+ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
1783
2099
  const url = new URL(`${baseUrl}/purchases/status/${appKey}`);
1784
2100
  url.searchParams.set("distinctId", distinctId);
1785
2101
  const response = await client.get(url.toString());
2102
+ if (!response.ok) {
2103
+ throw new Error(`Subscription status check failed: HTTP ${response.status}`);
2104
+ }
1786
2105
  return response.data;
1787
2106
  }
1788
2107
  async function getEmergencyPaywall(client) {
@@ -1962,10 +2281,8 @@ var HttpClient = class {
1962
2281
  sleep(ms) {
1963
2282
  return new Promise((resolve) => setTimeout(resolve, ms));
1964
2283
  }
1965
- log(message, data) {
1966
- if (this.config.debug) {
1967
- console.warn("[HttpClient]", message, data ?? "");
1968
- }
2284
+ log(...args) {
2285
+ if (this.config.debug) console.log("[Paywallo:Http]", ...args);
1969
2286
  }
1970
2287
  setDebug(debug) {
1971
2288
  this.config.debug = debug;
@@ -1976,7 +2293,7 @@ var HttpClient = class {
1976
2293
  };
1977
2294
 
1978
2295
  // src/core/network/NetworkMonitor.ts
1979
- var import_react_native6 = require("react-native");
2296
+ var import_react_native8 = require("react-native");
1980
2297
 
1981
2298
  // src/core/network/types.ts
1982
2299
  var DEFAULT_NETWORK_CONFIG = {
@@ -1991,8 +2308,6 @@ var NetworkMonitorClass = class {
1991
2308
  this.config = DEFAULT_NETWORK_CONFIG;
1992
2309
  this.listeners = /* @__PURE__ */ new Set();
1993
2310
  this.currentState = "unknown";
1994
- this.netInfoModule = null;
1995
- this.netInfoSubscription = null;
1996
2311
  this.appStateSubscription = null;
1997
2312
  this.initialized = false;
1998
2313
  this.checkIntervalId = null;
@@ -2008,13 +2323,6 @@ var NetworkMonitorClass = class {
2008
2323
  void this.setupFallbackDetection();
2009
2324
  this.setupAppStateListener();
2010
2325
  }
2011
- setupNetInfoListener() {
2012
- if (!this.netInfoModule) return;
2013
- this.netInfoSubscription = this.netInfoModule.addEventListener((state) => {
2014
- const newState = this.netInfoStateToNetworkState(state);
2015
- this.updateState(newState);
2016
- });
2017
- }
2018
2326
  async setupFallbackDetection() {
2019
2327
  await this.checkConnectivity();
2020
2328
  this.checkIntervalId = setInterval(() => {
@@ -2022,7 +2330,7 @@ var NetworkMonitorClass = class {
2022
2330
  }, this.config.checkInterval);
2023
2331
  }
2024
2332
  setupAppStateListener() {
2025
- this.appStateSubscription = import_react_native6.AppState.addEventListener("change", (state) => {
2333
+ this.appStateSubscription = import_react_native8.AppState.addEventListener("change", (state) => {
2026
2334
  if (state === "active") {
2027
2335
  void this.checkConnectivity();
2028
2336
  }
@@ -2042,15 +2350,6 @@ var NetworkMonitorClass = class {
2042
2350
  this.updateState("offline");
2043
2351
  }
2044
2352
  }
2045
- netInfoStateToNetworkState(state) {
2046
- if (state.isConnected === null) {
2047
- return "unknown";
2048
- }
2049
- if (!state.isConnected) {
2050
- return "offline";
2051
- }
2052
- return "online";
2053
- }
2054
2353
  updateState(newState) {
2055
2354
  const previousState = this.currentState;
2056
2355
  if (previousState === newState) {
@@ -2093,10 +2392,6 @@ var NetworkMonitorClass = class {
2093
2392
  return this.currentState;
2094
2393
  }
2095
2394
  dispose() {
2096
- if (this.netInfoSubscription) {
2097
- this.netInfoSubscription();
2098
- this.netInfoSubscription = null;
2099
- }
2100
2395
  if (this.appStateSubscription) {
2101
2396
  this.appStateSubscription.remove();
2102
2397
  this.appStateSubscription = null;
@@ -2109,10 +2404,8 @@ var NetworkMonitorClass = class {
2109
2404
  this.initialized = false;
2110
2405
  this.currentState = "unknown";
2111
2406
  }
2112
- log(message, data) {
2113
- if (this.config.debug) {
2114
- console.warn("[NetworkMonitor]", message, data ?? "");
2115
- }
2407
+ log(...args) {
2408
+ if (this.config.debug) console.log("[Paywallo:Network]", ...args);
2116
2409
  }
2117
2410
  setDebug(debug) {
2118
2411
  this.config.debug = debug;
@@ -2122,6 +2415,7 @@ var networkMonitor = new NetworkMonitorClass();
2122
2415
 
2123
2416
  // src/core/queue/OfflineQueue.ts
2124
2417
  var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
2418
+ init_uuid();
2125
2419
 
2126
2420
  // src/core/queue/deduplication.ts
2127
2421
  function normalizeForComparison(obj) {
@@ -2142,6 +2436,8 @@ function normalizeForComparison(obj) {
2142
2436
  return "{" + pairs.join(",") + "}";
2143
2437
  }
2144
2438
  function findDuplicateIndex(queue, item) {
2439
+ const byId = queue.findIndex((existing) => existing.id === item.id);
2440
+ if (byId !== -1) return byId;
2145
2441
  const normalizedBody = normalizeForComparison(item.body);
2146
2442
  return queue.findIndex(
2147
2443
  (existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
@@ -2210,9 +2506,9 @@ var OfflineQueueClass = class {
2210
2506
  void this.saveToStorage();
2211
2507
  }
2212
2508
  }
2213
- async enqueue(method, url, body, headers) {
2509
+ async enqueue(method, url, body, headers, id) {
2214
2510
  const item = {
2215
- id: generateUUID(),
2511
+ id: id ?? generateUUID(),
2216
2512
  method,
2217
2513
  url,
2218
2514
  body,
@@ -2296,7 +2592,6 @@ var OfflineQueueClass = class {
2296
2592
  this.emit({ type: "processing:end", queueSize: this.queue.length });
2297
2593
  return { processed, failed };
2298
2594
  }
2299
- /** Returns true if the item was permanently removed (counted as failed). */
2300
2595
  async handleItemFailure(item, error, permanent = false) {
2301
2596
  if (permanent || item.attempts >= item.maxAttempts) {
2302
2597
  await this.removeItem(item.id);
@@ -2358,10 +2653,8 @@ var OfflineQueueClass = class {
2358
2653
  }
2359
2654
  }
2360
2655
  }
2361
- log(message, data) {
2362
- if (this.config.debug) {
2363
- console.warn("[OfflineQueue]", message, data ?? "");
2364
- }
2656
+ log(...args) {
2657
+ if (this.config.debug) console.log("[Paywallo:Queue]", ...args);
2365
2658
  }
2366
2659
  setDebug(debug) {
2367
2660
  this.config.debug = debug;
@@ -2503,10 +2796,8 @@ var QueueProcessorClass = class {
2503
2796
  this.httpClient = null;
2504
2797
  this.initialized = false;
2505
2798
  }
2506
- log(message, data) {
2507
- if (this.config.debug) {
2508
- console.warn("[QueueProcessor]", message, data ?? "");
2509
- }
2799
+ log(...args) {
2800
+ if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
2510
2801
  }
2511
2802
  setDebug(debug) {
2512
2803
  this.config.debug = debug;
@@ -2518,10 +2809,9 @@ var queueProcessor = new QueueProcessorClass();
2518
2809
  var ApiCache = class {
2519
2810
  constructor() {
2520
2811
  this.CACHE_TTL = 3e5;
2521
- // 5 minutes
2522
2812
  this.CACHE_NULL_TTL = 3e4;
2523
- // 30 seconds — for null/404 variants
2524
2813
  this.MAX_SIZE = 200;
2814
+ this.STALE_WINDOW_MS = 864e5;
2525
2815
  this.variantCache = /* @__PURE__ */ new Map();
2526
2816
  this.paywallCache = /* @__PURE__ */ new Map();
2527
2817
  this.campaignCache = /* @__PURE__ */ new Map();
@@ -2559,7 +2849,7 @@ var ApiCache = class {
2559
2849
  }
2560
2850
  getStaleVariant(key) {
2561
2851
  const entry = this.variantCache.get(key);
2562
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2852
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2563
2853
  return void 0;
2564
2854
  }
2565
2855
  getPaywall(key) {
@@ -2571,42 +2861,42 @@ var ApiCache = class {
2571
2861
  }
2572
2862
  getStalePaywall(key) {
2573
2863
  const entry = this.paywallCache.get(key);
2574
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2864
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2575
2865
  return void 0;
2576
2866
  }
2577
2867
  getCampaign(key) {
2578
2868
  return this.getCached(this.campaignCache, key);
2579
2869
  }
2580
- setCampaign(key, value) {
2581
- this.setCached(this.campaignCache, key, value);
2870
+ setCampaign(key, value, ttlOverride) {
2871
+ this.setCached(this.campaignCache, key, value, ttlOverride);
2582
2872
  this.evictExpiredEntries(this.campaignCache);
2583
2873
  }
2584
2874
  getStaleCampaign(key) {
2585
2875
  const entry = this.campaignCache.get(key);
2586
- if (entry && Date.now() - entry.ts < 864e5) return entry.value;
2876
+ if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
2587
2877
  return void 0;
2588
2878
  }
2589
2879
  };
2590
2880
 
2591
2881
  // src/core/ApiClient.ts
2592
- var SDK_VERSION = "1.0.0";
2593
- var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
2882
+ var SDK_VERSION = "1.4.0";
2594
2883
  var _ApiClient = class _ApiClient {
2595
- constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2884
+ constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2596
2885
  this.cache = new ApiCache();
2597
2886
  this.appKey = appKey;
2598
- this.baseUrl = DEFAULT_BASE_URL;
2887
+ this.baseUrl = apiUrl;
2599
2888
  this.webUrl = deriveWebUrl(this.baseUrl);
2600
2889
  this.debug = debug;
2601
2890
  this.environment = environment;
2602
2891
  this.offlineQueueEnabled = offlineQueueEnabled;
2603
2892
  this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
2604
2893
  }
2605
- // ─── Infrastructure ──────────────────────────────────────────────────────────
2606
- /** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
2607
2894
  getHttpClient() {
2608
2895
  return this.httpClient;
2609
2896
  }
2897
+ getBaseUrl() {
2898
+ return this.baseUrl;
2899
+ }
2610
2900
  getWebUrl() {
2611
2901
  return this.webUrl;
2612
2902
  }
@@ -2632,7 +2922,6 @@ var _ApiClient = class _ApiClient {
2632
2922
  async clearQueue() {
2633
2923
  await offlineQueue.clear();
2634
2924
  }
2635
- // ─── Public HTTP facade (headers injected automatically) ─────────────────────
2636
2925
  async get(path) {
2637
2926
  return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
2638
2927
  }
@@ -2646,29 +2935,29 @@ var _ApiClient = class _ApiClient {
2646
2935
  message,
2647
2936
  context,
2648
2937
  sdkVersion: SDK_VERSION,
2649
- platform: import_react_native7.Platform.OS
2938
+ platform: import_react_native9.Platform.OS
2650
2939
  }, true);
2651
- } catch {
2940
+ } catch (err) {
2941
+ if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
2652
2942
  }
2653
2943
  }
2654
- // ─── Business methods ────────────────────────────────────────────────────────
2655
2944
  async trackEvent(eventName, distinctId, properties, timestamp) {
2656
2945
  await this.postWithQueue("/api/v1/events", {
2657
2946
  eventName,
2658
2947
  distinctId,
2659
- properties: { ...properties, platform: import_react_native7.Platform.OS },
2948
+ properties: { ...properties, platform: import_react_native9.Platform.OS },
2660
2949
  timestamp: timestamp ?? Date.now(),
2661
2950
  environment: this.environment.toLowerCase()
2662
2951
  }, `event:${eventName}`);
2663
2952
  }
2664
- async identify(distinctId, properties, email) {
2665
- await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native7.Platform.OS }, "identify");
2953
+ async identify(distinctId, properties, email, deviceId) {
2954
+ await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native9.Platform.OS, ...deviceId && { deviceId } }, "identify");
2666
2955
  }
2667
2956
  async startSession(distinctId, sessionId, platform) {
2668
2957
  await this.postWithQueue("/api/v1/sessions/start", {
2669
2958
  distinctId,
2670
2959
  sessionId,
2671
- platform: platform ?? import_react_native7.Platform.OS,
2960
+ platform: platform ?? import_react_native9.Platform.OS,
2672
2961
  environment: this.environment.toLowerCase()
2673
2962
  }, `session.start:${sessionId}`);
2674
2963
  return { success: true };
@@ -2746,7 +3035,7 @@ var _ApiClient = class _ApiClient {
2746
3035
  const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
2747
3036
  if (!result) {
2748
3037
  this.log("No campaign found for placement:", placement);
2749
- this.cache.setCampaign(key, null);
3038
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
2750
3039
  return null;
2751
3040
  }
2752
3041
  this.log("Got campaign:", placement, result.variantKey);
@@ -2759,6 +3048,32 @@ var _ApiClient = class _ApiClient {
2759
3048
  return null;
2760
3049
  }
2761
3050
  }
3051
+ async getPrimaryCampaign(distinctId) {
3052
+ const key = `primary:${distinctId}`;
3053
+ const hit = this.cache.getCampaign(key);
3054
+ if (hit !== void 0) return hit;
3055
+ try {
3056
+ const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
3057
+ if (res.status === 404) {
3058
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
3059
+ return null;
3060
+ }
3061
+ if (!res.ok) {
3062
+ this.log("Non-OK response for primary campaign:", res.status);
3063
+ const stale = this.cache.getStaleCampaign(key);
3064
+ if (stale !== void 0) return stale;
3065
+ return null;
3066
+ }
3067
+ this.log("Got primary campaign:", res.data?.campaignId);
3068
+ this.cache.setCampaign(key, res.data);
3069
+ return res.data;
3070
+ } catch (error) {
3071
+ this.log("Failed to get primary campaign:", error);
3072
+ const stale = this.cache.getStaleCampaign(key);
3073
+ if (stale !== void 0) return stale;
3074
+ return null;
3075
+ }
3076
+ }
2762
3077
  async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
2763
3078
  const result = await validatePurchase(
2764
3079
  this,
@@ -2827,12 +3142,6 @@ var _ApiClient = class _ApiClient {
2827
3142
  return { value: false, flagKey };
2828
3143
  }
2829
3144
  }
2830
- // ─── Cache-only reads ────────────────────────────────────────────────────────
2831
- /**
2832
- * Reads a flag variant from cache layers only — no network call.
2833
- * Checks in-memory cache first, then SecureStorage.
2834
- * Returns null when both layers miss.
2835
- */
2836
3145
  async getVariantFromCache(flagKey, distinctId) {
2837
3146
  const key = `${flagKey}:${distinctId}`;
2838
3147
  const hit = this.cache.getVariant(key);
@@ -2861,13 +3170,10 @@ var _ApiClient = class _ApiClient {
2861
3170
  try {
2862
3171
  const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
2863
3172
  await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
2864
- } catch {
3173
+ } catch (err) {
3174
+ this.log("writeFlagToStorage failed", err);
2865
3175
  }
2866
3176
  }
2867
- /**
2868
- * POST with offline queue fallback. Enqueues when offline or on network failure.
2869
- * Throws only when offlineQueueEnabled is false and the request fails.
2870
- */
2871
3177
  async postWithQueue(url, payload, label) {
2872
3178
  if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
2873
3179
  await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
@@ -2887,10 +3193,9 @@ var _ApiClient = class _ApiClient {
2887
3193
  }
2888
3194
  }
2889
3195
  log(...args) {
2890
- if (this.debug) console.warn("[Paywallo API]", ...args);
3196
+ if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
2891
3197
  }
2892
3198
  };
2893
- // ─── Private helpers ─────────────────────────────────────────────────────────
2894
3199
  _ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
2895
3200
  var ApiClient = _ApiClient;
2896
3201
 
@@ -2911,42 +3216,72 @@ var CAMPAIGN_ERROR_CODES = {
2911
3216
 
2912
3217
  // src/domains/campaign/CampaignGateService.ts
2913
3218
  var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
3219
+ var PRELOAD_WAIT_MAX_MS = 2e3;
3220
+ var PRELOAD_WAIT_INTERVAL_MS = 100;
2914
3221
  var CampaignGateService = class {
2915
3222
  constructor() {
2916
3223
  this.preloadedCampaigns = /* @__PURE__ */ new Map();
3224
+ this.pendingPreloads = /* @__PURE__ */ new Set();
2917
3225
  }
2918
3226
  async getCampaign(apiClient, placement, context) {
2919
3227
  const distinctId = identityManager.getDistinctId();
2920
3228
  if (!distinctId) return null;
2921
3229
  return apiClient.getCampaign(placement, distinctId, context);
2922
3230
  }
3231
+ async waitForPreload(placement) {
3232
+ if (!this.pendingPreloads.has(placement)) return;
3233
+ const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
3234
+ await new Promise((resolve) => {
3235
+ const check = () => {
3236
+ if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
3237
+ resolve();
3238
+ return;
3239
+ }
3240
+ setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
3241
+ };
3242
+ check();
3243
+ });
3244
+ }
2923
3245
  async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
2924
- const campaign = await this.getCampaign(apiClient, placement, context);
3246
+ await this.waitForPreload(placement);
3247
+ const preloaded = this.getPreloadedCampaign(placement);
3248
+ const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
2925
3249
  if (!campaign) {
2926
3250
  return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
2927
3251
  }
2928
- if (!context?.forceShow && await hasActive()) {
2929
- return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
3252
+ if (!context?.forceShow) {
3253
+ const isActive = await hasActive();
3254
+ if (isActive) {
3255
+ return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
3256
+ }
2930
3257
  }
2931
3258
  if (campaignPresenter) return campaignPresenter(campaign);
2932
3259
  if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
2933
- return { ...await paywallPresenter(campaign.paywall.placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
3260
+ return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
2934
3261
  }
2935
3262
  async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
2936
- if (await hasActive()) return true;
3263
+ const isActive = await hasActive();
3264
+ if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3265
+ if (isActive) return true;
2937
3266
  const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
2938
3267
  return r.purchased || r.restored;
2939
3268
  }
2940
3269
  async preloadCampaign(apiClient, placement, context) {
3270
+ this.pendingPreloads.add(placement);
2941
3271
  try {
2942
3272
  const campaign = await this.getCampaign(apiClient, placement, context);
2943
3273
  if (!campaign?.paywall) {
2944
3274
  return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
2945
3275
  }
2946
3276
  this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
3277
+ if (PaywalloClient.getConfig()?.debug) {
3278
+ console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
3279
+ }
2947
3280
  return { success: true };
2948
3281
  } catch (error) {
2949
3282
  return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
3283
+ } finally {
3284
+ this.pendingPreloads.delete(placement);
2950
3285
  }
2951
3286
  }
2952
3287
  getPreloadedCampaign(placement) {
@@ -2960,12 +3295,13 @@ var CampaignGateService = class {
2960
3295
  }
2961
3296
  clearPreloaded() {
2962
3297
  this.preloadedCampaigns.clear();
3298
+ this.pendingPreloads.clear();
2963
3299
  }
2964
3300
  };
2965
3301
  var campaignGateService = new CampaignGateService();
2966
3302
 
2967
3303
  // src/domains/identity/AdvertisingIdManager.ts
2968
- var import_react_native8 = require("react-native");
3304
+ var import_react_native10 = require("react-native");
2969
3305
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
2970
3306
  var AdvertisingIdManager = class {
2971
3307
  constructor() {
@@ -2976,7 +3312,7 @@ var AdvertisingIdManager = class {
2976
3312
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
2977
3313
  try {
2978
3314
  const tracking = await import("expo-tracking-transparency");
2979
- if (import_react_native8.Platform.OS === "ios") {
3315
+ if (import_react_native10.Platform.OS === "ios") {
2980
3316
  if (!tracking.isAvailable()) {
2981
3317
  const idfv2 = await this.collectIdfv();
2982
3318
  this.cached = { ...fallback, idfv: idfv2 };
@@ -2995,7 +3331,7 @@ var AdvertisingIdManager = class {
2995
3331
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
2996
3332
  return this.cached;
2997
3333
  }
2998
- if (import_react_native8.Platform.OS === "android") {
3334
+ if (import_react_native10.Platform.OS === "android") {
2999
3335
  const androidStatus = await tracking.getTrackingPermissionsAsync();
3000
3336
  const optedIn = androidStatus.granted;
3001
3337
  const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
@@ -3025,43 +3361,67 @@ var AdvertisingIdManager = class {
3025
3361
  }
3026
3362
  };
3027
3363
 
3028
- // src/domains/identity/FacebookIdManager.ts
3364
+ // src/domains/identity/InstallTracker.ts
3365
+ var import_react_native14 = require("react-native");
3366
+ var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
3367
+ var import_react_native_device_info2 = __toESM(require("react-native-device-info"));
3368
+
3369
+ // src/domains/identity/MetaBridge.ts
3370
+ var import_react_native11 = require("react-native");
3029
3371
  var TIMEOUT_MS = 2e3;
3030
- var FacebookIdManager = class {
3031
- async getAnonId() {
3372
+ var NativeBridge = import_react_native11.NativeModules.PaywalloFBBridge ?? null;
3373
+ var MetaBridge = class {
3374
+ async getAnonymousID() {
3375
+ if (!NativeBridge) return null;
3032
3376
  try {
3033
3377
  let timerId;
3034
- const timeoutPromise = new Promise((resolve) => {
3378
+ const timeout = new Promise((resolve) => {
3035
3379
  timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
3036
3380
  });
3037
- const result = await Promise.race([this.fetchAnonId(), timeoutPromise]);
3381
+ const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
3038
3382
  clearTimeout(timerId);
3039
3383
  return result;
3040
3384
  } catch {
3041
3385
  return null;
3042
3386
  }
3043
3387
  }
3044
- async fetchAnonId() {
3388
+ async logEvent(name, params = {}) {
3389
+ if (!NativeBridge) return;
3045
3390
  try {
3046
- const fbsdk = await import("react-native-fbsdk-next");
3047
- const anonId = await fbsdk.AppEventsLogger.getAnonymousID();
3048
- return anonId || null;
3391
+ await NativeBridge.logEvent(name, params);
3392
+ } catch {
3393
+ }
3394
+ }
3395
+ async logPurchase(amount, currency, params = {}) {
3396
+ if (!NativeBridge) return;
3397
+ try {
3398
+ await NativeBridge.logPurchase(amount, currency, params);
3399
+ } catch {
3400
+ }
3401
+ }
3402
+ setUserID(userId) {
3403
+ if (!NativeBridge) return;
3404
+ try {
3405
+ NativeBridge.setUserID(userId);
3406
+ } catch {
3407
+ }
3408
+ }
3409
+ flush() {
3410
+ if (!NativeBridge) return;
3411
+ try {
3412
+ NativeBridge.flush();
3049
3413
  } catch {
3050
- return null;
3051
3414
  }
3052
3415
  }
3053
3416
  };
3054
- var facebookIdManager = new FacebookIdManager();
3055
-
3056
- // src/domains/identity/InstallTracker.ts
3057
- var import_react_native11 = require("react-native");
3417
+ var metaBridge = new MetaBridge();
3058
3418
 
3059
3419
  // src/domains/identity/InstallReferrerManager.ts
3060
- var import_react_native9 = require("react-native");
3420
+ var import_react_native12 = require("react-native");
3061
3421
  var REFERRER_TIMEOUT_MS = 5e3;
3062
3422
  var InstallReferrerManager = class {
3063
3423
  async getReferrer() {
3064
- if (import_react_native9.Platform.OS !== "android") {
3424
+ if (import_react_native12.Platform.OS !== "android") {
3065
3425
  return this.emptyResult();
3066
3426
  }
3067
3427
  let timerId;
@@ -3121,21 +3481,8 @@ var InstallReferrerManager = class {
3121
3481
  var installReferrerManager = new InstallReferrerManager();
3122
3482
 
3123
3483
  // src/domains/session/SessionManager.ts
3124
- var import_react_native10 = require("react-native");
3125
-
3126
- // src/domains/session/SessionError.ts
3127
- var SessionError = class extends PaywalloError {
3128
- constructor(code, message) {
3129
- super("session", code, message);
3130
- this.name = "SessionError";
3131
- }
3132
- };
3133
- var SESSION_ERROR_CODES = {
3134
- NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
3135
- START_FAILED: "SESSION_START_FAILED",
3136
- END_FAILED: "SESSION_END_FAILED",
3137
- RESTORE_FAILED: "SESSION_RESTORE_FAILED"
3138
- };
3484
+ var import_react_native13 = require("react-native");
3485
+ init_uuid();
3139
3486
 
3140
3487
  // src/domains/session/sessionTracking.ts
3141
3488
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
@@ -3303,7 +3650,7 @@ var SessionManager = class {
3303
3650
  if (this.appStateSubscription) {
3304
3651
  this.appStateSubscription.remove();
3305
3652
  }
3306
- this.appStateSubscription = import_react_native10.AppState.addEventListener("change", this.handleAppStateChange);
3653
+ this.appStateSubscription = import_react_native13.AppState.addEventListener("change", this.handleAppStateChange);
3307
3654
  }
3308
3655
  async handleForeground() {
3309
3656
  if (this.backgroundTimestamp) {
@@ -3365,9 +3712,7 @@ var SessionManager = class {
3365
3712
  }
3366
3713
  }
3367
3714
  log(...args) {
3368
- if (this.debug) {
3369
- console.warn("[Paywallo Session]", ...args);
3370
- }
3715
+ if (this.debug) console.log("[Paywallo:Session]", ...args);
3371
3716
  }
3372
3717
  };
3373
3718
  var sessionManager = new SessionManager();
@@ -3380,12 +3725,11 @@ var InstallTracker = class {
3380
3725
  }
3381
3726
  async trackIfNeeded(apiClient, requestATT = false) {
3382
3727
  const storage = new SecureStorage(this.debug);
3383
- const alreadyTracked = await storage.get("install_tracked");
3728
+ const alreadyTracked = await storage.get("@panel:install_tracked");
3384
3729
  if (alreadyTracked) return;
3385
3730
  let fallbackTracked = null;
3386
3731
  try {
3387
- const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3388
- fallbackTracked = await AsyncStorage3.getItem("@paywallo:install_tracked");
3732
+ fallbackTracked = await import_async_storage3.default.getItem("@paywallo:install_tracked");
3389
3733
  } catch {
3390
3734
  }
3391
3735
  if (fallbackTracked) return;
@@ -3393,16 +3737,10 @@ var InstallTracker = class {
3393
3737
  const sessionId = sessionManager.getSessionId();
3394
3738
  const installedAt = Date.now();
3395
3739
  if (!distinctId) return;
3396
- let deviceInfo = null;
3397
- try {
3398
- const mod = await import("react-native-device-info");
3399
- deviceInfo = mod.default;
3400
- } catch {
3401
- deviceInfo = null;
3402
- }
3740
+ const deviceInfo = import_react_native_device_info2.default;
3403
3741
  let deviceData = {};
3404
3742
  try {
3405
- const screen = import_react_native11.Dimensions.get("screen");
3743
+ const screen = import_react_native14.Dimensions.get("screen");
3406
3744
  const screenWidth = Math.round(screen.width ?? 0);
3407
3745
  const screenHeight = Math.round(screen.height ?? 0);
3408
3746
  const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
@@ -3411,33 +3749,80 @@ var InstallTracker = class {
3411
3749
  const osVersion = deviceInfo?.getSystemVersion() || "unknown";
3412
3750
  const appVersion = deviceInfo?.getVersion() || "unknown";
3413
3751
  const buildNumber = deviceInfo?.getBuildNumber() || "0";
3414
- deviceData = {
3415
- deviceModel,
3416
- osVersion,
3417
- appVersion,
3418
- buildNumber,
3419
- ...screenWidth > 0 && { screenWidth },
3420
- ...screenHeight > 0 && { screenHeight },
3421
- timezone,
3422
- locale
3423
- };
3424
- } catch {
3425
- if (this.debug) {
3426
- console.warn("[Paywallo] Failed to collect device data for install event");
3752
+ let carrier = "unknown";
3753
+ try {
3754
+ carrier = await deviceInfo?.getCarrier?.() ?? "unknown";
3755
+ } catch {
3427
3756
  }
3757
+ let totalDisk = 0;
3758
+ try {
3759
+ totalDisk = Number(await deviceInfo?.getTotalDiskCapacity?.()) || 0;
3760
+ } catch {
3761
+ }
3762
+ let freeDisk = 0;
3763
+ try {
3764
+ freeDisk = Number(await deviceInfo?.getFreeDiskStorage?.()) || 0;
3765
+ } catch {
3766
+ }
3767
+ let totalRam = 0;
3768
+ try {
3769
+ totalRam = Number(await deviceInfo?.getTotalMemory?.()) || 0;
3770
+ } catch {
3771
+ }
3772
+ let brand = "unknown";
3773
+ try {
3774
+ brand = deviceInfo?.getBrand?.() ?? "unknown";
3775
+ } catch {
3776
+ }
3777
+ let installerPackage = null;
3778
+ try {
3779
+ if (import_react_native14.Platform.OS === "android") {
3780
+ installerPackage = await deviceInfo?.getInstallerPackageName?.() ?? null;
3781
+ }
3782
+ } catch {
3783
+ }
3784
+ deviceData = {
3785
+ deviceModel,
3786
+ osVersion,
3787
+ appVersion,
3788
+ buildNumber,
3789
+ ...screenWidth > 0 && { screenWidth },
3790
+ ...screenHeight > 0 && { screenHeight },
3791
+ timezone,
3792
+ locale,
3793
+ ...carrier !== "unknown" && { carrier },
3794
+ ...totalDisk > 0 && { totalDisk },
3795
+ ...freeDisk > 0 && { freeDisk },
3796
+ ...totalRam > 0 && { totalRam },
3797
+ ...brand !== "unknown" && { brand },
3798
+ ...installerPackage && { installerPackage }
3799
+ };
3800
+ } catch {
3428
3801
  }
3429
3802
  const [fbAnonId, referrer, adIds] = await Promise.all([
3430
- facebookIdManager.getAnonId(),
3803
+ metaBridge.getAnonymousID(),
3431
3804
  installReferrerManager.getReferrer(),
3432
3805
  this.advertisingIdManager.collect(requestATT)
3433
3806
  ]);
3807
+ let effectiveAnonId = fbAnonId;
3808
+ if (!effectiveAnonId) {
3809
+ const stored = await storage.get("@panel:anon_id");
3810
+ if (stored) {
3811
+ effectiveAnonId = stored;
3812
+ } else {
3813
+ const { generateUUID: generateUUID2 } = await Promise.resolve().then(() => (init_uuid(), uuid_exports));
3814
+ effectiveAnonId = `PW_${generateUUID2()}`;
3815
+ await storage.set("@panel:anon_id", effectiveAnonId).catch(() => {
3816
+ });
3817
+ }
3818
+ }
3434
3819
  try {
3435
3820
  await apiClient.trackEvent("$app_installed", distinctId, {
3436
3821
  installedAt,
3437
- platform: import_react_native11.Platform.OS,
3822
+ platform: import_react_native14.Platform.OS,
3438
3823
  ...sessionId && { sessionId },
3439
3824
  ...deviceData,
3440
- ...fbAnonId && { fbAnonId },
3825
+ ...effectiveAnonId && { fbAnonId: effectiveAnonId },
3441
3826
  ...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
3442
3827
  ...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
3443
3828
  ...referrer.fbclid && { referrerFbclid: referrer.fbclid },
@@ -3449,19 +3834,47 @@ var InstallTracker = class {
3449
3834
  ...adIds.gaid && { gaid: adIds.gaid },
3450
3835
  attStatus: adIds.attStatus
3451
3836
  });
3452
- const stored = await storage.set("install_tracked", String(installedAt));
3837
+ if (import_react_native14.Platform.OS === "ios") {
3838
+ void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId).catch(() => {
3839
+ });
3840
+ }
3841
+ await storage.set("@panel:install_tracked", String(installedAt));
3453
3842
  try {
3454
- const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3455
- await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
3843
+ await import_async_storage3.default.setItem("@paywallo:install_tracked", String(installedAt));
3456
3844
  } catch {
3457
3845
  }
3458
- if (this.debug && !stored) {
3459
- console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
3460
- }
3461
- } catch (error) {
3462
- if (this.debug) {
3463
- console.warn("[Paywallo] Failed to track install:", error);
3464
- }
3846
+ } catch {
3847
+ }
3848
+ }
3849
+ async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
3850
+ const storage = new SecureStorage(this.debug);
3851
+ const alreadyMatched = await storage.get("@panel:deferred_match_done");
3852
+ if (alreadyMatched) return;
3853
+ const controller = new AbortController();
3854
+ const timer = setTimeout(() => controller.abort(), 5e3);
3855
+ try {
3856
+ const baseUrl = apiClient.getBaseUrl();
3857
+ const appKey = apiClient.appKey;
3858
+ await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
3859
+ method: "POST",
3860
+ headers: { "Content-Type": "application/json", "X-App-Key": appKey },
3861
+ body: JSON.stringify({
3862
+ idfv: adIds.idfv,
3863
+ deviceModel: deviceData.deviceModel,
3864
+ osVersion: deviceData.osVersion,
3865
+ screenWidth: deviceData.screenWidth,
3866
+ screenHeight: deviceData.screenHeight,
3867
+ timezone: deviceData.timezone,
3868
+ locale: deviceData.locale,
3869
+ fbAnonId: anonId
3870
+ }),
3871
+ signal: controller.signal
3872
+ });
3873
+ await storage.set("@panel:deferred_match_done", "1").catch(() => {
3874
+ });
3875
+ } catch {
3876
+ } finally {
3877
+ clearTimeout(timer);
3465
3878
  }
3466
3879
  }
3467
3880
  };
@@ -3471,25 +3884,13 @@ function isValidEventName(name) {
3471
3884
  if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3472
3885
  return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3473
3886
  }
3474
- var OFFLINE_ACTIVE_STATUSES = ["active", "grace_period"];
3475
- async function resolveClientOfflineFromCache() {
3476
- try {
3477
- const cached = await subscriptionCache.get();
3478
- if (!cached) return false;
3479
- const sub = cached.data.subscription;
3480
- if (!sub) return false;
3481
- if (!OFFLINE_ACTIVE_STATUSES.includes(sub.status)) return false;
3482
- if (!sub.expiresAt) return true;
3483
- return sub.expiresAt > /* @__PURE__ */ new Date();
3484
- } catch {
3485
- return false;
3486
- }
3487
- }
3488
3887
  var _PaywalloClientClass = class _PaywalloClientClass {
3489
3888
  constructor() {
3490
3889
  this.config = null;
3491
3890
  this.apiClient = null;
3492
3891
  this.initPromise = null;
3892
+ this.readyPromise = null;
3893
+ this.resolveReady = null;
3493
3894
  this.paywallPresenter = null;
3494
3895
  this.campaignPresenter = null;
3495
3896
  this.subscriptionGetter = null;
@@ -3499,29 +3900,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3499
3900
  this.pendingConfig = null;
3500
3901
  this.networkCleanup = null;
3501
3902
  this.initAttempts = 0;
3903
+ this.autoPreloadedPlacement = null;
3904
+ this.autoPreloadPlacementPromise = null;
3905
+ this.sessionFlagsMap = /* @__PURE__ */ new Map();
3502
3906
  }
3503
3907
  async init(config) {
3504
- if (config.debug) console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
3908
+ if (!this.readyPromise) {
3909
+ this.readyPromise = new Promise((resolve) => {
3910
+ this.resolveReady = resolve;
3911
+ });
3912
+ }
3505
3913
  if (this.config && this.apiClient) {
3506
- if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
3507
3914
  return;
3508
3915
  }
3509
3916
  if (this.initPromise) {
3510
- if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
3511
3917
  await this.initPromise;
3512
- if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
3513
3918
  return;
3514
3919
  }
3515
3920
  if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
3516
- if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
3517
3921
  this.pendingConfig = config;
3518
3922
  this.initAttempts = 0;
3519
3923
  this.initPromise = this.initWithRetry(config);
3520
3924
  try {
3521
3925
  await this.initPromise;
3522
- if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
3523
3926
  } catch (error) {
3524
- if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
3525
3927
  this.initPromise = null;
3526
3928
  this.config = null;
3527
3929
  this.apiClient = null;
@@ -3530,6 +3932,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3530
3932
  throw error;
3531
3933
  }
3532
3934
  }
3935
+ isReady() {
3936
+ return this.config !== null && this.apiClient !== null;
3937
+ }
3938
+ waitUntilReady() {
3939
+ if (this.isReady()) return Promise.resolve();
3940
+ if (!this.readyPromise) {
3941
+ this.readyPromise = new Promise((resolve) => {
3942
+ this.resolveReady = resolve;
3943
+ });
3944
+ }
3945
+ return this.readyPromise;
3946
+ }
3533
3947
  async initWithRetry(config) {
3534
3948
  while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
3535
3949
  try {
@@ -3544,7 +3958,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3544
3958
  this.initAttempts++;
3545
3959
  if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
3546
3960
  const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
3547
- if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
3548
3961
  await new Promise((resolve) => setTimeout(resolve, delay));
3549
3962
  }
3550
3963
  }
@@ -3555,12 +3968,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3555
3968
  this.networkCleanup = networkMonitor.addListener((state) => {
3556
3969
  if (state !== "online") return;
3557
3970
  if (this.config && this.apiClient) return;
3558
- if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
3971
+ if (this.initPromise) return;
3559
3972
  this.initPromise = this.initWithRetry(config);
3560
- this.initPromise.then(() => {
3561
- if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
3562
- }).catch(() => {
3973
+ this.initPromise.catch(() => {
3563
3974
  this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
3975
+ }).finally(() => {
3976
+ if (!this.isReady()) this.initPromise = null;
3564
3977
  });
3565
3978
  });
3566
3979
  }
@@ -3572,12 +3985,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3572
3985
  }
3573
3986
  reportInitFailure(config, error, reason) {
3574
3987
  try {
3575
- const tempClient = new ApiClient(config.appKey, config.debug);
3988
+ const tempClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug);
3576
3989
  void tempClient.reportError("INIT_FAILED", reason, {
3577
3990
  attempts: this.initAttempts,
3578
3991
  error: error instanceof Error ? error.message : String(error)
3579
3992
  });
3580
- } catch {
3993
+ } catch (err) {
3994
+ if (config.debug) console.warn("[Paywallo] reportInitFailure error", err);
3581
3995
  }
3582
3996
  }
3583
3997
  reportInitSuccess(config, attempts) {
@@ -3585,41 +3999,83 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3585
3999
  if (this.apiClient) {
3586
4000
  void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
3587
4001
  }
3588
- } catch {
4002
+ } catch (err) {
4003
+ if (config.debug) console.warn("[Paywallo] reportInitSuccess error", err);
4004
+ }
4005
+ }
4006
+ async resolveSessionFlags(keys, apiClient, distinctId) {
4007
+ try {
4008
+ const results = await apiClient.evaluateFlags(keys, distinctId);
4009
+ this.sessionFlagsMap.clear();
4010
+ for (const key of keys) {
4011
+ this.sessionFlagsMap.set(key, key in results ? results[key] : null);
4012
+ }
4013
+ } catch (error) {
4014
+ for (const key of keys) {
4015
+ this.sessionFlagsMap.set(key, null);
4016
+ }
3589
4017
  }
3590
4018
  }
3591
4019
  async doInit(config) {
3592
4020
  const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
3593
4021
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
3594
- if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
3595
4022
  await Promise.all([
3596
4023
  networkMonitor.initialize({ debug: config.debug }),
3597
4024
  offlineQueue.initialize({ debug: config.debug }).then(
3598
4025
  () => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
3599
4026
  )
3600
4027
  ]);
3601
- if (config.debug) console.warn("[Paywallo INIT]", "Step 1 DONE");
3602
- if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
3603
- this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
4028
+ this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
3604
4029
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3605
4030
  affiliateManager.initialize(this.apiClient, config.debug);
3606
- if (config.debug) console.warn("[Paywallo INIT]", "Step 2 DONE");
3607
- if (config.debug) console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
4031
+ subscriptionManager.init({
4032
+ serverUrl: this.apiClient.getBaseUrl(),
4033
+ appKey: config.appKey,
4034
+ cacheTTL: config.subscriptionCacheTTL,
4035
+ debug: config.debug
4036
+ });
3608
4037
  await Promise.all([
3609
4038
  identityManager.initialize(this.apiClient, config.debug),
3610
4039
  sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
3611
4040
  ]);
3612
4041
  const distinctId = identityManager.getDistinctId();
3613
- if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
3614
4042
  if (config.autoStartSession !== false) {
3615
4043
  sessionManager.startSession().catch(() => {
3616
- if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
3617
4044
  });
3618
4045
  }
3619
4046
  new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
3620
4047
  });
4048
+ if (config.sessionFlags && config.sessionFlags.length > 0) {
4049
+ await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
4050
+ }
3621
4051
  this.config = config;
3622
- if (config.debug) console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
4052
+ if (this.resolveReady) {
4053
+ this.resolveReady();
4054
+ this.resolveReady = null;
4055
+ }
4056
+ if (config.debug) console.info("[Paywallo INIT] complete");
4057
+ if (this.apiClient) {
4058
+ const apiClientRef = this.apiClient;
4059
+ if (config.autoPreloadCampaign) {
4060
+ this.autoPreloadedPlacement = config.autoPreloadCampaign;
4061
+ this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
4062
+ if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
4063
+ void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
4064
+ });
4065
+ } else {
4066
+ this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
4067
+ const placement = primary?.placement ?? primary?.paywall?.placement;
4068
+ if (placement) {
4069
+ this.autoPreloadedPlacement = placement;
4070
+ if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
4071
+ void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
4072
+ });
4073
+ return placement;
4074
+ }
4075
+ return null;
4076
+ }).catch(() => null);
4077
+ }
4078
+ }
3623
4079
  }
3624
4080
  async identify(options) {
3625
4081
  this.ensureInitialized();
@@ -3629,7 +4085,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3629
4085
  const apiClient = this.getApiClientOrThrow();
3630
4086
  const distinctId = identityManager.getDistinctId();
3631
4087
  if (!distinctId) {
3632
- if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
3633
4088
  return;
3634
4089
  }
3635
4090
  if (!isValidEventName(eventName)) {
@@ -3637,25 +4092,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3637
4092
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
3638
4093
  }
3639
4094
  const sessionId = sessionManager.getSessionId();
3640
- if (eventName === "$purchase_completed") {
3641
- if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
3642
- distinctId: distinctId.substring(0, 8) + "...",
3643
- productId: options?.properties?.productId,
3644
- priceUsd: options?.properties?.priceUsd,
3645
- price: options?.properties?.price,
3646
- currency: options?.properties?.currency,
3647
- placement: options?.properties?.placement
3648
- }));
3649
- } else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
3650
- if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
3651
- distinctId: distinctId.substring(0, 8) + "...",
3652
- placement: options?.properties?.placement,
3653
- variantKey: options?.properties?.variantKey,
3654
- timeOnPaywall: options?.properties?.timeOnPaywall
3655
- }));
3656
- } else {
3657
- if (this.config?.debug) console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
3658
- }
3659
4095
  await apiClient.trackEvent(eventName, distinctId, {
3660
4096
  ...identityManager.getProperties(),
3661
4097
  ...options?.properties,
@@ -3666,7 +4102,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3666
4102
  const apiClient = this.getApiClientOrThrow();
3667
4103
  const distinctId = identityManager.getDistinctId();
3668
4104
  if (!distinctId) {
3669
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3670
4105
  return;
3671
4106
  }
3672
4107
  if (!Number.isInteger(index) || index < 0) {
@@ -3690,7 +4125,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3690
4125
  const apiClient = this.getApiClientOrThrow();
3691
4126
  const distinctId = identityManager.getDistinctId();
3692
4127
  if (!distinctId) {
3693
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3694
4128
  return;
3695
4129
  }
3696
4130
  if (!actionName || typeof actionName !== "string") {
@@ -3719,14 +4153,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3719
4153
  try {
3720
4154
  const distinctId = identityManager.getDistinctId();
3721
4155
  if (!distinctId) {
3722
- if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
3723
4156
  return { variant: null };
3724
4157
  }
3725
- const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3726
- if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
3727
- return result;
4158
+ return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3728
4159
  } catch (error) {
3729
- if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
3730
4160
  if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
3731
4161
  return { variant: null };
3732
4162
  }
@@ -3742,7 +4172,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3742
4172
  const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3743
4173
  return result.value;
3744
4174
  } catch (error) {
3745
- if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
3746
4175
  if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
3747
4176
  return false;
3748
4177
  }
@@ -3765,15 +4194,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3765
4194
  async hasActiveSubscription() {
3766
4195
  this.ensureInitialized();
3767
4196
  try {
3768
- if (this.activeChecker) return await this.activeChecker();
4197
+ if (this.activeChecker) {
4198
+ return await this.activeChecker();
4199
+ }
3769
4200
  const distinctId = identityManager.getDistinctId();
3770
- if (!distinctId || !this.apiClient) return false;
4201
+ if (!distinctId || !this.apiClient) {
4202
+ return false;
4203
+ }
3771
4204
  const status = await this.apiClient.getSubscriptionStatus(distinctId);
3772
4205
  return status.hasActiveSubscription;
3773
- } catch {
3774
- if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014 using persistent cache");
3775
- if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", "hasActiveSubscription failed");
3776
- return resolveClientOfflineFromCache();
4206
+ } catch (error) {
4207
+ if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
4208
+ return false;
3777
4209
  }
3778
4210
  }
3779
4211
  async getSubscription() {
@@ -3786,7 +4218,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3786
4218
  if (!status.subscription) return null;
3787
4219
  return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
3788
4220
  } catch {
3789
- if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
3790
4221
  return null;
3791
4222
  }
3792
4223
  }
@@ -3797,20 +4228,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3797
4228
  }
3798
4229
  async reset() {
3799
4230
  await identityManager.reset();
4231
+ await subscriptionCache.invalidateAll();
3800
4232
  }
3801
4233
  async fullReset() {
3802
4234
  const wasDebug = this.config?.debug ?? false;
3803
4235
  await sessionManager.endSession();
3804
4236
  await identityManager.reset();
3805
- await subscriptionCache.invalidate();
4237
+ await subscriptionCache.invalidateAll();
3806
4238
  queueProcessor.dispose();
3807
4239
  offlineQueue.dispose();
3808
4240
  networkMonitor.dispose();
3809
4241
  campaignGateService.clearPreloaded();
3810
4242
  this.cleanupNetworkRecovery();
4243
+ this.sessionFlagsMap.clear();
3811
4244
  this.apiClient = null;
3812
4245
  this.config = null;
3813
4246
  this.initPromise = null;
4247
+ this.readyPromise = null;
4248
+ this.resolveReady = null;
3814
4249
  this.pendingConfig = null;
3815
4250
  this.initAttempts = 0;
3816
4251
  this.paywallPresenter = null;
@@ -3819,7 +4254,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3819
4254
  this.activeChecker = null;
3820
4255
  this.restoreHandler = null;
3821
4256
  this.lastOnboardingStepTimestamp = null;
3822
- if (wasDebug) console.warn("[Paywallo]", "Full reset complete");
4257
+ this.autoPreloadedPlacement = null;
4258
+ this.autoPreloadPlacementPromise = null;
4259
+ }
4260
+ getSessionFlag(key) {
4261
+ if (!this.config || !this.apiClient) return null;
4262
+ const value = this.sessionFlagsMap.get(key);
4263
+ return value !== void 0 ? value : null;
3823
4264
  }
3824
4265
  getConfig() {
3825
4266
  return this.config;
@@ -3908,6 +4349,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3908
4349
  getPreloadedCampaign(placement) {
3909
4350
  return campaignGateService.getPreloadedCampaign(placement);
3910
4351
  }
4352
+ isPreloaded(placement) {
4353
+ return campaignGateService.getPreloadedCampaign(placement) !== null;
4354
+ }
4355
+ getAutoPreloadedPlacement() {
4356
+ return this.autoPreloadedPlacement;
4357
+ }
4358
+ waitForAutoPreloadedPlacement() {
4359
+ return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
4360
+ }
3911
4361
  isOnline() {
3912
4362
  return networkMonitor.isOnline();
3913
4363
  }
@@ -3937,48 +4387,6 @@ var PaywalloClient = new PaywalloClientClass();
3937
4387
  var import_react9 = require("react");
3938
4388
  var PaywalloContext = (0, import_react9.createContext)(null);
3939
4389
 
3940
- // src/utils/localization.ts
3941
- var import_react_native12 = require("react-native");
3942
- var currentLanguage = "pt-BR";
3943
- var defaultLanguage = "pt-BR";
3944
- function setCurrentLanguage(language) {
3945
- currentLanguage = language;
3946
- }
3947
- function setDefaultLanguage(language) {
3948
- defaultLanguage = language;
3949
- }
3950
- function getCurrentLanguage() {
3951
- return currentLanguage;
3952
- }
3953
- function getDefaultLanguage() {
3954
- return defaultLanguage;
3955
- }
3956
- function getLocalizedString(text) {
3957
- if (!text) return "";
3958
- if (typeof text === "string") return text;
3959
- return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
3960
- }
3961
- function detectDeviceLanguage() {
3962
- try {
3963
- if (import_react_native12.Platform.OS === "ios") {
3964
- const settings = import_react_native12.NativeModules.SettingsManager?.settings;
3965
- return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
3966
- }
3967
- return import_react_native12.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
3968
- } catch {
3969
- return "en-US";
3970
- }
3971
- }
3972
- function initLocalization(options) {
3973
- if (options?.defaultLanguage) {
3974
- defaultLanguage = options.defaultLanguage;
3975
- }
3976
- if (options?.detectDevice !== false) {
3977
- const deviceLanguage = detectDeviceLanguage();
3978
- currentLanguage = deviceLanguage;
3979
- }
3980
- }
3981
-
3982
4390
  // src/hooks/usePaywallo.ts
3983
4391
  var import_react10 = require("react");
3984
4392
  function usePaywallo() {
@@ -4004,7 +4412,8 @@ function mapToIAPProduct(product) {
4004
4412
  type: product.type === "subscription" ? "subs" : "inapp",
4005
4413
  subscriptionPeriodAndroid: product.subscriptionPeriod,
4006
4414
  introductoryPrice: product.introductoryPrice,
4007
- freeTrialPeriodAndroid: product.freeTrialPeriod
4415
+ freeTrialPeriodAndroid: product.freeTrialPeriod,
4416
+ trialDays: product.trialDays ?? 0
4008
4417
  };
4009
4418
  }
4010
4419
  function mapToFormattedProduct(product) {
@@ -4019,6 +4428,7 @@ function mapToFormattedProduct(product) {
4019
4428
  subscriptionPeriod: product.subscriptionPeriod,
4020
4429
  introductoryPrice: product.introductoryPrice,
4021
4430
  freeTrialPeriod: product.freeTrialPeriod,
4431
+ trialDays: product.trialDays ?? 0,
4022
4432
  pricePerMonth: product.pricePerMonth,
4023
4433
  pricePerWeek: product.pricePerWeek,
4024
4434
  pricePerDay: product.pricePerDay
@@ -4104,23 +4514,31 @@ function usePurchase() {
4104
4514
  if (result.success && result.purchase) {
4105
4515
  setState("idle");
4106
4516
  return {
4107
- productId: result.purchase.productId,
4108
- transactionId: result.purchase.transactionId,
4109
- transactionDate: result.purchase.transactionDate,
4110
- transactionReceipt: result.purchase.receipt
4517
+ status: "success",
4518
+ purchase: {
4519
+ productId: result.purchase.productId,
4520
+ transactionId: result.purchase.transactionId,
4521
+ transactionDate: result.purchase.transactionDate,
4522
+ transactionReceipt: result.purchase.receipt
4523
+ }
4111
4524
  };
4112
4525
  }
4113
4526
  if (result.error) {
4114
- const purchaseError = createPurchaseError("PURCHASE_FAILED", result.error.message);
4115
- setError(purchaseError);
4527
+ setError(result.error);
4116
4528
  setState("error");
4117
- throw result.error;
4529
+ return { status: "failed", error: result.error };
4118
4530
  }
4119
4531
  setState("idle");
4120
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, "Purchase cancelled by user", true);
4532
+ return { status: "cancelled" };
4121
4533
  } catch (err) {
4534
+ const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
4535
+ if (purchaseError.userCancelled) {
4536
+ setState("idle");
4537
+ return { status: "cancelled" };
4538
+ }
4539
+ setError(purchaseError);
4122
4540
  setState("error");
4123
- throw err;
4541
+ return { status: "failed", error: purchaseError };
4124
4542
  }
4125
4543
  }, []);
4126
4544
  const restore = (0, import_react12.useCallback)(async () => {
@@ -4148,7 +4566,6 @@ function usePurchase() {
4148
4566
  purchase,
4149
4567
  restore,
4150
4568
  error,
4151
- isLoading: state === "loading",
4152
4569
  isPurchasing: state === "purchasing",
4153
4570
  isRestoring: state === "restoring"
4154
4571
  };
@@ -4156,139 +4573,6 @@ function usePurchase() {
4156
4573
 
4157
4574
  // src/hooks/useSubscription.ts
4158
4575
  var import_react13 = require("react");
4159
-
4160
- // src/domains/subscription/SubscriptionManager.ts
4161
- var import_react_native13 = require("react-native");
4162
- var SubscriptionManagerClass = class {
4163
- constructor() {
4164
- this.config = null;
4165
- this.listeners = /* @__PURE__ */ new Set();
4166
- this.userId = null;
4167
- this.cache = new SubscriptionCache();
4168
- }
4169
- init(config) {
4170
- this.config = config;
4171
- if (config.cacheTTL) {
4172
- this.cache.setTTL(config.cacheTTL);
4173
- }
4174
- }
4175
- setUserId(userId) {
4176
- if (this.userId !== userId) {
4177
- this.userId = userId;
4178
- void this.cache.invalidate();
4179
- }
4180
- }
4181
- async hasActiveSubscription(forceRefresh = false) {
4182
- const status = await this.getSubscriptionStatus(forceRefresh);
4183
- return status.hasActiveSubscription;
4184
- }
4185
- async getSubscription(forceRefresh = false) {
4186
- const status = await this.getSubscriptionStatus(forceRefresh);
4187
- return status.subscription;
4188
- }
4189
- async getEntitlements(forceRefresh = false) {
4190
- const status = await this.getSubscriptionStatus(forceRefresh);
4191
- return status.entitlements;
4192
- }
4193
- async getSubscriptionStatus(forceRefresh = false) {
4194
- if (!this.config) {
4195
- return this.getEmptyStatus();
4196
- }
4197
- if (!forceRefresh) {
4198
- const cached = await this.cache.get();
4199
- if (cached && !cached.isStale) {
4200
- return cached.data;
4201
- }
4202
- }
4203
- try {
4204
- const status = await this.fetchSubscriptionStatus();
4205
- await this.cache.set(status);
4206
- this.notifyListeners(status);
4207
- return status;
4208
- } catch (error) {
4209
- this.log("Failed to fetch subscription status", error);
4210
- const cached = await this.cache.get();
4211
- if (cached) {
4212
- return cached.data;
4213
- }
4214
- return this.getEmptyStatus();
4215
- }
4216
- }
4217
- async restorePurchases() {
4218
- if (!this.config) {
4219
- throw new SessionError(
4220
- SESSION_ERROR_CODES.NOT_INITIALIZED,
4221
- "SubscriptionManager not initialized"
4222
- );
4223
- }
4224
- await this.cache.invalidate();
4225
- return this.getSubscriptionStatus(true);
4226
- }
4227
- async fetchSubscriptionStatus() {
4228
- if (!this.config) {
4229
- return this.getEmptyStatus();
4230
- }
4231
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
4232
- if (this.userId) {
4233
- url.searchParams.set("userId", this.userId);
4234
- }
4235
- url.searchParams.set("platform", import_react_native13.Platform.OS);
4236
- const response = await fetch(url.toString(), {
4237
- method: "GET",
4238
- headers: {
4239
- "Content-Type": "application/json",
4240
- "X-App-Key": this.config.appKey
4241
- }
4242
- });
4243
- if (!response.ok) {
4244
- throw new SessionError(
4245
- SESSION_ERROR_CODES.RESTORE_FAILED,
4246
- `Failed to fetch subscription status: ${response.status}`
4247
- );
4248
- }
4249
- const data = await response.json();
4250
- if (data.subscription) {
4251
- data.subscription.expiresAt = new Date(data.subscription.expiresAt);
4252
- data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
4253
- data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
4254
- if (data.subscription.cancellationDate) {
4255
- data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
4256
- }
4257
- if (data.subscription.gracePeriodExpiresAt) {
4258
- data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
4259
- }
4260
- }
4261
- return data;
4262
- }
4263
- getEmptyStatus() {
4264
- return {
4265
- hasActiveSubscription: false,
4266
- subscription: null,
4267
- entitlements: []
4268
- };
4269
- }
4270
- addListener(listener) {
4271
- this.listeners.add(listener);
4272
- return () => this.listeners.delete(listener);
4273
- }
4274
- notifyListeners(status) {
4275
- for (const listener of this.listeners) {
4276
- listener(status);
4277
- }
4278
- }
4279
- async onPurchaseComplete() {
4280
- await this.cache.invalidate();
4281
- await this.getSubscriptionStatus(true);
4282
- }
4283
- log(message, data) {
4284
- if (this.config?.debug) {
4285
- console.warn(`[SubscriptionManager] ${message}`, data ?? "");
4286
- }
4287
- }
4288
- };
4289
- var subscriptionManager = new SubscriptionManagerClass();
4290
-
4291
- // src/hooks/useSubscription.ts
4292
4576
  function useSubscription() {
4293
4577
  const [status, setStatus] = (0, import_react13.useState)(null);
4294
4578
  const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
@@ -4323,6 +4607,9 @@ function useSubscription() {
4323
4607
  void subscriptionManager.getSubscriptionStatus().then((s) => {
4324
4608
  setStatus(s);
4325
4609
  setIsLoading(false);
4610
+ }).catch((err) => {
4611
+ setError(err instanceof Error ? err : new Error(String(err)));
4612
+ setIsLoading(false);
4326
4613
  });
4327
4614
  const removeListener = subscriptionManager.addListener((newStatus) => {
4328
4615
  setStatus(newStatus);
@@ -4344,7 +4631,7 @@ function useSubscription() {
4344
4631
 
4345
4632
  // src/PaywalloProvider.tsx
4346
4633
  var import_react18 = require("react");
4347
- var import_react_native14 = require("react-native");
4634
+ var import_react_native15 = require("react-native");
4348
4635
 
4349
4636
  // src/hooks/useCampaignPreload.ts
4350
4637
  var import_react14 = require("react");
@@ -4405,10 +4692,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
4405
4692
  function isCacheEntryValid(entry) {
4406
4693
  return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
4407
4694
  }
4408
- function consumeCacheEntry(cache, placement) {
4695
+ function getCacheEntry(cache, placement) {
4409
4696
  const entry = cache.get(placement);
4410
4697
  if (!isCacheEntryValid(entry)) return null;
4411
- cache.delete(placement);
4412
4698
  return entry ?? null;
4413
4699
  }
4414
4700
 
@@ -4421,7 +4707,7 @@ function useCampaignPreload() {
4421
4707
  []
4422
4708
  );
4423
4709
  const consumePreloadedCampaign = (0, import_react14.useCallback)(
4424
- (placement) => consumeCacheEntry(preloadedCampaignsRef.current, placement),
4710
+ (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
4425
4711
  []
4426
4712
  );
4427
4713
  const preloadCampaign = (0, import_react14.useCallback)(
@@ -4469,6 +4755,7 @@ function useCampaignPreload() {
4469
4755
  campaignId: campaign.campaignId,
4470
4756
  variantKey: campaign.variantKey
4471
4757
  });
4758
+ if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
4472
4759
  return { success: true };
4473
4760
  } catch (error) {
4474
4761
  return {
@@ -4502,8 +4789,8 @@ function useProductLoader() {
4502
4789
  const map = /* @__PURE__ */ new Map();
4503
4790
  for (const p of loaded) map.set(p.productId, p);
4504
4791
  setProducts(map);
4505
- } catch (error) {
4506
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
4792
+ } catch (err) {
4793
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] refreshProducts failed", err);
4507
4794
  } finally {
4508
4795
  setIsLoadingProducts(false);
4509
4796
  }
@@ -4513,7 +4800,7 @@ function useProductLoader() {
4513
4800
 
4514
4801
  // src/hooks/usePurchaseOrchestrator.ts
4515
4802
  var import_react16 = require("react");
4516
- function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
4803
+ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
4517
4804
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
4518
4805
  const handlePreloadedPurchase = (0, import_react16.useCallback)(
4519
4806
  async (productId) => {
@@ -4538,17 +4825,18 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4538
4825
  await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4539
4826
  await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4540
4827
  }
4828
+ invalidateSubscriptionCache();
4541
4829
  setShowingPreloadedWebView(false);
4542
4830
  clearPreloadedWebView();
4543
4831
  resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
4544
4832
  } catch (error) {
4545
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
4833
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded purchase failed", error);
4546
4834
  setShowingPreloadedWebView(false);
4547
4835
  clearPreloadedWebView();
4548
4836
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
4549
4837
  }
4550
4838
  },
4551
- [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
4839
+ [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
4552
4840
  );
4553
4841
  const handlePreloadedRestore = (0, import_react16.useCallback)(async () => {
4554
4842
  try {
@@ -4558,16 +4846,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4558
4846
  const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
4559
4847
  await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
4560
4848
  }
4849
+ invalidateSubscriptionCache();
4561
4850
  setShowingPreloadedWebView(false);
4562
4851
  clearPreloadedWebView();
4563
4852
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
4564
4853
  } catch (error) {
4565
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
4854
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded restore failed", error);
4566
4855
  setShowingPreloadedWebView(false);
4567
4856
  clearPreloadedWebView();
4568
4857
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
4569
4858
  }
4570
- }, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
4859
+ }, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
4571
4860
  return { handlePreloadedPurchase, handlePreloadedRestore };
4572
4861
  }
4573
4862
 
@@ -4579,7 +4868,6 @@ function toDomainSubscriptionInfo(sub) {
4579
4868
  return {
4580
4869
  id: "",
4581
4870
  productId: sub.productId,
4582
- // The hook's SubscriptionStatus is a subset of the domain's — cast is safe
4583
4871
  status: sub.status,
4584
4872
  expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
4585
4873
  originalPurchaseDate: /* @__PURE__ */ new Date(0),
@@ -4597,31 +4885,32 @@ function toDomainResponse(status) {
4597
4885
  entitlements: []
4598
4886
  };
4599
4887
  }
4600
- async function resolveOfflineFromCache() {
4888
+ function isSubscriptionStillActive(sub) {
4889
+ if (!sub) return false;
4890
+ if (!ACTIVE_STATUSES.includes(sub.status)) return false;
4891
+ if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
4892
+ return true;
4893
+ }
4894
+ async function resolveOfflineFromCache(distinctId) {
4601
4895
  try {
4602
- const cached = await subscriptionCache.get();
4896
+ const cached = await subscriptionCache.get(distinctId);
4603
4897
  if (!cached) return false;
4604
4898
  const sub = cached.data.subscription;
4605
- if (!sub) return false;
4606
- if (!ACTIVE_STATUSES.includes(sub.status)) return false;
4607
- const hasValidExpiry = sub.expiresAt.getTime() === 0 || sub.expiresAt > /* @__PURE__ */ new Date();
4608
- return hasValidExpiry;
4899
+ return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
4609
4900
  } catch {
4610
4901
  return false;
4611
4902
  }
4612
4903
  }
4613
4904
  function isActiveCacheEntry(entry) {
4614
- if (!entry.data) return false;
4615
- if (!ACTIVE_STATUSES.includes(entry.data.status)) return false;
4616
- if (entry.data.expiresAt && entry.data.expiresAt < /* @__PURE__ */ new Date()) return false;
4617
- return true;
4905
+ return isSubscriptionStillActive(entry.data);
4618
4906
  }
4619
4907
  function useSubscriptionSync() {
4620
4908
  const [subscription, setSubscription] = (0, import_react17.useState)(null);
4621
4909
  const cacheRef = (0, import_react17.useRef)(null);
4622
4910
  const invalidateCache = (0, import_react17.useCallback)(() => {
4623
4911
  cacheRef.current = null;
4624
- void subscriptionCache.invalidate();
4912
+ const distinctId = PaywalloClient.getDistinctId();
4913
+ if (distinctId) void subscriptionCache.invalidate(distinctId);
4625
4914
  }, []);
4626
4915
  const fetchAndCache = (0, import_react17.useCallback)(async () => {
4627
4916
  const apiClient = PaywalloClient.getApiClient();
@@ -4634,7 +4923,7 @@ function useSubscriptionSync() {
4634
4923
  } : null;
4635
4924
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4636
4925
  setSubscription(sub);
4637
- void subscriptionCache.set(toDomainResponse(status));
4926
+ void subscriptionCache.set(distinctId, toDomainResponse(status));
4638
4927
  return sub;
4639
4928
  }, []);
4640
4929
  const hasActiveSubscription = (0, import_react17.useCallback)(async () => {
@@ -4646,23 +4935,23 @@ function useSubscriptionSync() {
4646
4935
  return isActiveCacheEntry(cached);
4647
4936
  }
4648
4937
  try {
4649
- const persisted = await subscriptionCache.get();
4650
- if (persisted) {
4651
- if (!persisted.isStale) {
4652
- const sub = persisted.data.subscription ? {
4653
- productId: persisted.data.subscription.productId,
4654
- status: persisted.data.subscription.status,
4655
- expiresAt: persisted.data.subscription.expiresAt ?? null,
4656
- platform: persisted.data.subscription.platform,
4657
- autoRenewEnabled: persisted.data.subscription.willRenew,
4658
- inGracePeriod: persisted.data.subscription.status === "grace_period"
4659
- } : null;
4938
+ const persisted = await subscriptionCache.get(distinctId);
4939
+ if (persisted && !persisted.isStale) {
4940
+ const persistedSub = persisted.data.subscription;
4941
+ const sub = persistedSub ? {
4942
+ productId: persistedSub.productId,
4943
+ status: persistedSub.status,
4944
+ expiresAt: persistedSub.expiresAt ?? null,
4945
+ platform: persistedSub.platform,
4946
+ autoRenewEnabled: persistedSub.willRenew,
4947
+ inGracePeriod: persistedSub.status === "grace_period"
4948
+ } : null;
4949
+ const stillActive = isSubscriptionStillActive(sub);
4950
+ if (stillActive) {
4660
4951
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4661
4952
  setSubscription(sub);
4662
- return persisted.data.hasActiveSubscription;
4953
+ return true;
4663
4954
  }
4664
- void fetchAndCache();
4665
- return persisted.data.hasActiveSubscription;
4666
4955
  }
4667
4956
  } catch {
4668
4957
  }
@@ -4674,12 +4963,12 @@ function useSubscriptionSync() {
4674
4963
  } : null;
4675
4964
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4676
4965
  setSubscription(sub);
4677
- void subscriptionCache.set(toDomainResponse(status));
4966
+ void subscriptionCache.set(distinctId, toDomainResponse(status));
4678
4967
  return status.hasActiveSubscription;
4679
4968
  } catch {
4680
- return await resolveOfflineFromCache();
4969
+ return await resolveOfflineFromCache(distinctId);
4681
4970
  }
4682
- }, [fetchAndCache]);
4971
+ }, []);
4683
4972
  const getSubscription = (0, import_react17.useCallback)(async () => {
4684
4973
  const apiClient = PaywalloClient.getApiClient();
4685
4974
  const distinctId = PaywalloClient.getDistinctId();
@@ -4690,7 +4979,7 @@ function useSubscriptionSync() {
4690
4979
  return await fetchAndCache();
4691
4980
  } catch {
4692
4981
  try {
4693
- const persisted = await subscriptionCache.get();
4982
+ const persisted = await subscriptionCache.get(distinctId);
4694
4983
  if (!persisted?.data.subscription) return null;
4695
4984
  const s = persisted.data.subscription;
4696
4985
  const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
@@ -4721,13 +5010,38 @@ function PaywalloProvider({ children, config }) {
4721
5010
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
4722
5011
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
4723
5012
  const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
4724
- const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreloadedWebView);
5013
+ const autoPreloadTriggeredRef = (0, import_react18.useRef)(false);
5014
+ const preloadedWebViewRef = (0, import_react18.useRef)(preloadedWebView);
5015
+ (0, import_react18.useEffect)(() => {
5016
+ preloadedWebViewRef.current = preloadedWebView;
5017
+ }, [preloadedWebView]);
5018
+ const triggerRepreload = (0, import_react18.useCallback)((placement) => {
5019
+ void PaywalloClient.preloadCampaign(placement).catch(() => {
5020
+ });
5021
+ }, []);
5022
+ const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
5023
+ const SCREEN_HEIGHT3 = (0, import_react18.useMemo)(() => import_react_native15.Dimensions.get("window").height, []);
5024
+ const slideAnim = (0, import_react18.useRef)(new import_react_native15.Animated.Value(SCREEN_HEIGHT3)).current;
5025
+ const handlePreloadedWebViewReady = (0, import_react18.useCallback)(() => {
5026
+ if (PaywalloClient.getConfig()?.debug) {
5027
+ console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
5028
+ }
5029
+ baseHandlePreloadedWebViewReady();
5030
+ }, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
5031
+ const handlePreloadedClose = (0, import_react18.useCallback)(() => {
5032
+ const placement = preloadedWebView?.placement;
5033
+ import_react_native15.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: import_react_native15.Easing.in(import_react_native15.Easing.cubic), useNativeDriver: true }).start(() => {
5034
+ baseHandlePreloadedClose();
5035
+ if (placement) triggerRepreload(placement);
5036
+ });
5037
+ }, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
4725
5038
  const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
4726
5039
  preloadedWebView,
4727
5040
  resolvePreloadedPaywall,
4728
5041
  clearPreloadedWebView,
4729
5042
  setShowingPreloadedWebView,
4730
- presentedAtRef
5043
+ presentedAtRef,
5044
+ invalidateCache
4731
5045
  );
4732
5046
  const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react18.useState)(false);
4733
5047
  const handlePreloadedPurchase = (0, import_react18.useCallback)(async (productId) => {
@@ -4740,13 +5054,19 @@ function PaywalloProvider({ children, config }) {
4740
5054
  }, [rawPreloadedPurchase]);
4741
5055
  (0, import_react18.useEffect)(() => {
4742
5056
  initLocalization({ detectDevice: true });
4743
- PaywalloClient.init(config).then(() => {
5057
+ PaywalloClient.init(config).then(async () => {
4744
5058
  setDistinctId(PaywalloClient.getDistinctId());
4745
5059
  setIsInitialized(true);
5060
+ if (!autoPreloadTriggeredRef.current) {
5061
+ autoPreloadTriggeredRef.current = true;
5062
+ const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
5063
+ if (placement) void preloadCampaign(placement).catch(() => {
5064
+ });
5065
+ }
4746
5066
  }).catch((err) => {
4747
5067
  setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
4748
5068
  });
4749
- }, [config]);
5069
+ }, [config, preloadCampaign]);
4750
5070
  const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
4751
5071
  const api = PaywalloClient.getApiClient();
4752
5072
  return api ? api.getPaywall(p) : null;
@@ -4845,28 +5165,54 @@ function PaywalloProvider({ children, config }) {
4845
5165
  setActivePaywall(null);
4846
5166
  }
4847
5167
  }, [activePaywall]);
5168
+ const bridgePaywallPresenter = (0, import_react18.useCallback)((placement) => {
5169
+ if (PaywalloClient.getConfig()?.debug) {
5170
+ console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
5171
+ }
5172
+ if (preloadedWebViewRef.current?.placement === placement) {
5173
+ if (preloadedWebViewRef.current.loaded) {
5174
+ return presentPreloadedWebView();
5175
+ }
5176
+ return new Promise((resolve) => {
5177
+ let elapsed = 0;
5178
+ const poll = () => {
5179
+ if (preloadedWebViewRef.current?.loaded) {
5180
+ resolve(presentPreloadedWebView());
5181
+ return;
5182
+ }
5183
+ elapsed += 100;
5184
+ if (elapsed >= 500) {
5185
+ resolve(presentCampaign(placement));
5186
+ return;
5187
+ }
5188
+ setTimeout(poll, 100);
5189
+ };
5190
+ setTimeout(poll, 100);
5191
+ });
5192
+ }
5193
+ return presentCampaign(placement);
5194
+ }, [presentPreloadedWebView, presentCampaign]);
4848
5195
  (0, import_react18.useEffect)(() => {
4849
5196
  if (!isInitialized) return;
4850
5197
  const onEmergency = async (id) => {
4851
5198
  try {
4852
- await presentPaywall(id);
5199
+ await bridgePaywallPresenter(id);
4853
5200
  } catch {
4854
5201
  }
4855
5202
  };
4856
- PaywalloClient.registerPaywallPresenter(presentPaywall);
5203
+ PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
4857
5204
  PaywalloClient.registerSubscriptionGetter(getSubscription);
4858
5205
  PaywalloClient.registerActiveChecker(hasActiveSubscription);
4859
5206
  PaywalloClient.registerRestoreHandler(restorePurchases);
4860
5207
  PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
4861
- }, [isInitialized, presentPaywall, getSubscription, hasActiveSubscription, restorePurchases]);
4862
- (0, import_react18.useEffect)(() => {
4863
- if (activePaywall) setPreloadedWebView(null);
4864
- }, [activePaywall, setPreloadedWebView]);
5208
+ }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
5209
+ const isReady = isInitialized && !isLoadingProducts;
4865
5210
  const noOp = (0, import_react18.useCallback)(() => {
4866
5211
  }, []);
4867
5212
  const contextValue = (0, import_react18.useMemo)(() => ({
4868
5213
  config: PaywalloClient.getConfig(),
4869
5214
  isInitialized,
5215
+ isReady,
4870
5216
  initError,
4871
5217
  distinctId,
4872
5218
  subscription,
@@ -4882,6 +5228,7 @@ function PaywalloProvider({ children, config }) {
4882
5228
  refreshProducts
4883
5229
  }), [
4884
5230
  isInitialized,
5231
+ isReady,
4885
5232
  initError,
4886
5233
  distinctId,
4887
5234
  subscription,
@@ -4896,24 +5243,20 @@ function PaywalloProvider({ children, config }) {
4896
5243
  getPaywallConfig,
4897
5244
  refreshProducts
4898
5245
  ]);
4899
- const SCREEN_HEIGHT3 = (0, import_react18.useMemo)(() => import_react_native14.Dimensions.get("window").height, []);
4900
- const slideAnim = (0, import_react18.useRef)(new import_react_native14.Animated.Value(SCREEN_HEIGHT3)).current;
4901
- (0, import_react18.useEffect)(() => {
5246
+ (0, import_react18.useLayoutEffect)(() => {
4902
5247
  if (showingPreloadedWebView) {
4903
5248
  slideAnim.setValue(SCREEN_HEIGHT3);
4904
- import_react_native14.Animated.timing(slideAnim, { toValue: 0, duration: 320, easing: import_react_native14.Easing.out(import_react_native14.Easing.cubic), useNativeDriver: true }).start();
4905
- } else {
4906
- slideAnim.setValue(SCREEN_HEIGHT3);
5249
+ import_react_native15.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native15.Easing.out(import_react_native15.Easing.cubic), useNativeDriver: true }).start();
4907
5250
  }
4908
5251
  }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
4909
5252
  const preloadStyle = [
4910
5253
  styles3.preloadOverlay,
4911
5254
  showingPreloadedWebView ? styles3.visible : styles3.hidden,
4912
- { transform: [{ translateY: showingPreloadedWebView ? slideAnim : SCREEN_HEIGHT3 }] }
5255
+ showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
4913
5256
  ];
4914
5257
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
4915
5258
  children,
4916
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native14.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5259
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native15.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
4917
5260
  PaywallWebView,
4918
5261
  {
4919
5262
  paywallId: preloadedWebView.paywallId,
@@ -4922,6 +5265,7 @@ function PaywalloProvider({ children, config }) {
4922
5265
  primaryProductId: preloadedWebView.primaryProductId,
4923
5266
  secondaryProductId: preloadedWebView.secondaryProductId,
4924
5267
  isPurchasing: isPreloadPurchasing,
5268
+ webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
4925
5269
  onReady: handlePreloadedWebViewReady,
4926
5270
  onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
4927
5271
  onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
@@ -4942,10 +5286,10 @@ function PaywalloProvider({ children, config }) {
4942
5286
  ) })
4943
5287
  ] });
4944
5288
  }
4945
- var styles3 = import_react_native14.StyleSheet.create({
5289
+ var styles3 = import_react_native15.StyleSheet.create({
4946
5290
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
4947
- hidden: { zIndex: -1 },
4948
- visible: { zIndex: 9999 }
5291
+ hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
5292
+ visible: { zIndex: 9999, opacity: 1 }
4949
5293
  });
4950
5294
 
4951
5295
  // src/utils/productVariableResolver.ts
@@ -5085,6 +5429,7 @@ var index_default = Paywallo;
5085
5429
  IDENTITY_ERROR_CODES,
5086
5430
  IdentityError,
5087
5431
  IdentityManager,
5432
+ MetaBridge,
5088
5433
  PAYWALL_ERROR_CODES,
5089
5434
  PURCHASE_ERROR_CODES,
5090
5435
  PaywallContext,
@@ -5111,6 +5456,7 @@ var index_default = Paywallo;
5111
5456
  hasVariables,
5112
5457
  identityManager,
5113
5458
  initLocalization,
5459
+ metaBridge,
5114
5460
  nativeStoreKit,
5115
5461
  networkMonitor,
5116
5462
  offlineQueue,