@virex-tech/paywallo-sdk 1.4.1 → 1.5.2

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
@@ -52,10 +52,7 @@ var init_uuid = __esm({
52
52
  // src/index.ts
53
53
  var index_exports = {};
54
54
  __export(index_exports, {
55
- AFFILIATE_ERROR_CODES: () => AFFILIATE_ERROR_CODES,
56
55
  ANALYTICS_ERROR_CODES: () => ANALYTICS_ERROR_CODES,
57
- AffiliateError: () => AffiliateError,
58
- AffiliateManager: () => AffiliateManager,
59
56
  AnalyticsError: () => AnalyticsError,
60
57
  ApiClient: () => ApiClient,
61
58
  CAMPAIGN_ERROR_CODES: () => CAMPAIGN_ERROR_CODES,
@@ -73,7 +70,6 @@ __export(index_exports, {
73
70
  PaywallError: () => PaywallError,
74
71
  PaywallModal: () => PaywallModal,
75
72
  Paywallo: () => Paywallo,
76
- PaywalloAffiliate: () => PaywalloAffiliate,
77
73
  PaywalloClient: () => PaywalloClient,
78
74
  PaywalloContext: () => PaywalloContext,
79
75
  PaywalloError: () => PaywalloError,
@@ -83,7 +79,6 @@ __export(index_exports, {
83
79
  SessionError: () => SessionError,
84
80
  SessionManager: () => SessionManager,
85
81
  SubscriptionCache: () => SubscriptionCache,
86
- affiliateManager: () => affiliateManager,
87
82
  createPurchaseError: () => createPurchaseError,
88
83
  default: () => index_default,
89
84
  detectDeviceLanguage: () => detectDeviceLanguage,
@@ -149,19 +144,20 @@ var CLIENT_ERROR_CODES = {
149
144
  MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
150
145
  INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
151
146
  PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
147
+ INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
152
148
  UNKNOWN: "CLIENT_UNKNOWN"
153
149
  };
154
150
 
155
151
  // src/domains/paywall/PaywallModal.tsx
156
152
  var import_react5 = require("react");
157
- var import_react_native6 = require("react-native");
153
+ var import_react_native8 = require("react-native");
158
154
 
159
155
  // src/domains/paywall/usePaywallActions.ts
160
156
  var import_react2 = require("react");
161
- var import_react_native3 = require("react-native");
157
+ var import_react_native5 = require("react-native");
162
158
 
163
159
  // src/domains/iap/IAPService.ts
164
- var import_react_native2 = require("react-native");
160
+ var import_react_native4 = require("react-native");
165
161
 
166
162
  // src/domains/iap/PurchaseError.ts
167
163
  var PurchaseError = class extends PaywalloError {
@@ -206,10 +202,7 @@ function createPurchaseError(code, message) {
206
202
  }
207
203
 
208
204
  // src/domains/iap/NativeStoreKit.ts
209
- var import_react_native = require("react-native");
210
-
211
- // src/domains/identity/IdentityManager.ts
212
- var import_react_native_device_info = __toESM(require("react-native-device-info"));
205
+ var import_react_native3 = require("react-native");
213
206
 
214
207
  // src/domains/identity/IdentityError.ts
215
208
  var IdentityError = class extends PaywalloError {
@@ -229,13 +222,141 @@ var IDENTITY_ERROR_CODES = {
229
222
  // src/domains/identity/IdentityManager.ts
230
223
  init_uuid();
231
224
 
225
+ // src/utils/NativeDeviceInfo.ts
226
+ var import_react_native = require("react-native");
227
+ var FALLBACK = {
228
+ deviceId: "unknown",
229
+ model: "unknown",
230
+ modelId: "unknown",
231
+ systemVersion: "0.0.0",
232
+ appVersion: "0.0.0",
233
+ buildNumber: "0",
234
+ bundleId: "unknown",
235
+ brand: "unknown",
236
+ systemName: "unknown",
237
+ totalDisk: 0,
238
+ freeDisk: 0,
239
+ totalRam: 0,
240
+ carrier: "unknown",
241
+ installerPackage: null,
242
+ idfv: null
243
+ };
244
+ var _debug = false;
245
+ var warnedOnce = false;
246
+ var cachedDeviceInfo = null;
247
+ function setNativeDeviceInfoDebug(debug) {
248
+ _debug = debug;
249
+ }
250
+ function isDeviceInfoResult(value) {
251
+ if (typeof value !== "object" || value === null) return false;
252
+ const v = value;
253
+ return typeof v["deviceId"] === "string" && typeof v["model"] === "string" && typeof v["systemVersion"] === "string" && typeof v["appVersion"] === "string" && typeof v["bundleId"] === "string";
254
+ }
255
+ function getNativeModule() {
256
+ const mod = import_react_native.NativeModules.PaywalloDevice;
257
+ if (!mod) {
258
+ if (!warnedOnce) {
259
+ warnedOnce = true;
260
+ console.warn("[Paywallo] NativeModules.PaywalloDevice not found \u2014 falling back to defaults");
261
+ }
262
+ return null;
263
+ }
264
+ return mod;
265
+ }
266
+ async function getDeviceInfo() {
267
+ if (cachedDeviceInfo !== null) return cachedDeviceInfo;
268
+ const mod = getNativeModule();
269
+ if (!mod) return FALLBACK;
270
+ const raw = await mod.getDeviceInfo();
271
+ if (!isDeviceInfoResult(raw)) return FALLBACK;
272
+ cachedDeviceInfo = raw;
273
+ return raw;
274
+ }
275
+ async function getInstallReferrer() {
276
+ const mod = getNativeModule();
277
+ if (!mod) return null;
278
+ try {
279
+ return await mod.getInstallReferrer();
280
+ } catch {
281
+ return null;
282
+ }
283
+ }
284
+ function isAvailable() {
285
+ return import_react_native.NativeModules.PaywalloDevice != null;
286
+ }
287
+ function clearCache() {
288
+ cachedDeviceInfo = null;
289
+ }
290
+ var nativeDeviceInfo = {
291
+ getDeviceInfo,
292
+ getInstallReferrer,
293
+ isAvailable,
294
+ clearCache
295
+ };
296
+
297
+ // src/core/storage/NativeStorage.ts
298
+ var import_react_native2 = require("react-native");
299
+ var NativeModule = import_react_native2.NativeModules.PaywalloStorage;
300
+ var _debug2 = false;
301
+ var _warnedUnavailable = false;
302
+ function setNativeStorageDebug(debug) {
303
+ _debug2 = debug;
304
+ }
305
+ function isAvailable2() {
306
+ return NativeModule != null;
307
+ }
308
+ function warnIfUnavailable() {
309
+ if (NativeModule != null) return true;
310
+ if (!_warnedUnavailable) {
311
+ _warnedUnavailable = true;
312
+ if (_debug2) console.warn("[Paywallo] PaywalloStorage native module not available \u2014 storage will not persist");
313
+ }
314
+ return false;
315
+ }
316
+ function getModule() {
317
+ if (!warnIfUnavailable()) return null;
318
+ return NativeModule;
319
+ }
320
+ async function get(key) {
321
+ return getModule()?.get(key) ?? null;
322
+ }
323
+ async function set(key, value) {
324
+ return getModule()?.set(key, value) ?? false;
325
+ }
326
+ async function remove(key) {
327
+ return getModule()?.remove(key) ?? false;
328
+ }
329
+ async function secureGet(key) {
330
+ return getModule()?.secureGet(key) ?? null;
331
+ }
332
+ async function secureSet(key, value) {
333
+ return getModule()?.secureSet(key, value) ?? false;
334
+ }
335
+ async function secureRemove(key) {
336
+ return getModule()?.secureRemove(key) ?? false;
337
+ }
338
+ async function legacySecureGet(key) {
339
+ const mod = getModule();
340
+ if (!mod?.legacySecureGet) return null;
341
+ return mod.legacySecureGet(key);
342
+ }
343
+ var nativeStorage = {
344
+ isAvailable: isAvailable2,
345
+ get,
346
+ set,
347
+ remove,
348
+ secureGet,
349
+ secureSet,
350
+ secureRemove,
351
+ legacySecureGet
352
+ };
353
+
232
354
  // src/core/storage/SecureStorage.ts
233
- var import_async_storage = __toESM(require("@react-native-async-storage/async-storage"));
234
- var import_react_native_keychain = __toESM(require("react-native-keychain"));
235
- var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
355
+ var KEYCHAIN_KEY_PREFIX = "com.paywallo.sdk.";
356
+ var REGULAR_KEY_PREFIX = "@paywallo:";
236
357
  var SecureStorage = class {
237
358
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
238
- constructor(_debug = false) {
359
+ constructor(_debug3 = false) {
239
360
  }
240
361
  async get(key) {
241
362
  const keychainValue = await this.getFromKeychain(key);
@@ -243,9 +364,10 @@ var SecureStorage = class {
243
364
  return keychainValue;
244
365
  }
245
366
  try {
246
- const value = await import_async_storage.default.getItem(`@paywallo:${key}`);
367
+ const value = await nativeStorage.get(`${REGULAR_KEY_PREFIX}${key}`);
247
368
  if (value) {
248
369
  await this.setToKeychain(key, value);
370
+ nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0);
249
371
  }
250
372
  return value;
251
373
  } catch {
@@ -253,20 +375,13 @@ var SecureStorage = class {
253
375
  }
254
376
  }
255
377
  async set(key, value) {
256
- try {
257
- await Promise.all([
258
- import_async_storage.default.setItem(`@paywallo:${key}`, value),
259
- this.setToKeychain(key, value)
260
- ]);
261
- return true;
262
- } catch {
263
- return false;
264
- }
378
+ return this.setToKeychain(key, value);
265
379
  }
266
380
  async remove(key) {
267
381
  try {
268
382
  await Promise.all([
269
- import_async_storage.default.removeItem(`@paywallo:${key}`),
383
+ // Also remove the legacy plaintext copy if it exists (migration cleanup).
384
+ nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0),
270
385
  this.removeFromKeychain(key)
271
386
  ]);
272
387
  return true;
@@ -275,31 +390,25 @@ var SecureStorage = class {
275
390
  }
276
391
  }
277
392
  hasKeychainSupport() {
278
- return true;
393
+ return nativeStorage.isAvailable();
279
394
  }
280
395
  async getFromKeychain(key) {
281
396
  try {
282
- const result = await import_react_native_keychain.default.getGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
283
- if (result && result.password) {
284
- return result.password;
285
- }
286
- return null;
397
+ return await nativeStorage.secureGet(`${KEYCHAIN_KEY_PREFIX}${key}`);
287
398
  } catch {
288
399
  return null;
289
400
  }
290
401
  }
291
402
  async setToKeychain(key, value) {
292
403
  try {
293
- await import_react_native_keychain.default.setGenericPassword("panel", value, { service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
294
- return true;
404
+ return await nativeStorage.secureSet(`${KEYCHAIN_KEY_PREFIX}${key}`, value);
295
405
  } catch {
296
406
  return false;
297
407
  }
298
408
  }
299
409
  async removeFromKeychain(key) {
300
410
  try {
301
- await import_react_native_keychain.default.resetGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
302
- return true;
411
+ return await nativeStorage.secureRemove(`${KEYCHAIN_KEY_PREFIX}${key}`);
303
412
  } catch {
304
413
  return false;
305
414
  }
@@ -307,7 +416,59 @@ var SecureStorage = class {
307
416
  };
308
417
  var secureStorage = new SecureStorage();
309
418
 
419
+ // src/core/storage/StorageMigration.ts
420
+ var MIGRATION_FLAG_KEY = "@paywallo:_migration_v152_done";
421
+ var LEGACY_SECURE_KEYS = [
422
+ "device_id",
423
+ "device_id_fallback",
424
+ "anon_id",
425
+ "user_email",
426
+ "user_properties"
427
+ ];
428
+ var KEYCHAIN_KEY_PREFIX2 = "com.paywallo.sdk.";
429
+ async function runStorageMigrationIfNeeded() {
430
+ if (!nativeStorage.isAvailable()) return;
431
+ const alreadyDone = await nativeStorage.get(MIGRATION_FLAG_KEY);
432
+ if (alreadyDone === "1") return;
433
+ await migrateAsyncStorage();
434
+ await migrateKeychainLegacy();
435
+ await nativeStorage.set(MIGRATION_FLAG_KEY, "1");
436
+ }
437
+ async function migrateAsyncStorage() {
438
+ try {
439
+ const mod = await import("@react-native-async-storage/async-storage");
440
+ const AsyncStorage = mod.default;
441
+ const allKeys = await AsyncStorage.getAllKeys();
442
+ const paywallo = allKeys.filter((k) => k.startsWith("@paywallo:"));
443
+ if (paywallo.length === 0) return;
444
+ const pairs = await AsyncStorage.multiGet(paywallo);
445
+ for (const [key, value] of pairs) {
446
+ if (!value) continue;
447
+ const existing = await nativeStorage.get(key);
448
+ if (!existing) {
449
+ await nativeStorage.set(key, value);
450
+ }
451
+ }
452
+ } catch {
453
+ }
454
+ }
455
+ async function migrateKeychainLegacy() {
456
+ for (const key of LEGACY_SECURE_KEYS) {
457
+ try {
458
+ const legacyValue = await nativeStorage.legacySecureGet(`${KEYCHAIN_KEY_PREFIX2}${key}`);
459
+ if (!legacyValue) continue;
460
+ const newSlotKey = `${KEYCHAIN_KEY_PREFIX2}${key}`;
461
+ const existing = await nativeStorage.secureGet(newSlotKey);
462
+ if (!existing) {
463
+ await nativeStorage.secureSet(newSlotKey, legacyValue);
464
+ }
465
+ } catch {
466
+ }
467
+ }
468
+ }
469
+
310
470
  // src/domains/identity/IdentityManager.ts
471
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
311
472
  var DEVICE_ID_KEY = "device_id";
312
473
  var ANON_ID_KEY = "anon_id";
313
474
  var USER_EMAIL_KEY = "user_email";
@@ -322,18 +483,31 @@ var IdentityManager = class {
322
483
  this.secureStorage = null;
323
484
  this.debug = false;
324
485
  this.initialized = false;
486
+ this.initPromise = null;
325
487
  }
326
488
  async initialize(apiClient, debug = false) {
327
489
  if (this.initialized) {
328
490
  return this.deviceId ?? "";
329
491
  }
492
+ if (this.initPromise) {
493
+ return this.initPromise;
494
+ }
495
+ this.initPromise = this._doInitialize(apiClient, debug).finally(() => {
496
+ this.initPromise = null;
497
+ });
498
+ return this.initPromise;
499
+ }
500
+ async _doInitialize(apiClient, debug) {
330
501
  this.apiClient = apiClient;
331
502
  this.debug = debug;
332
503
  this.secureStorage = new SecureStorage(debug);
504
+ await runStorageMigrationIfNeeded();
333
505
  await this.loadPersistedState();
334
506
  if (!this.deviceId) {
335
507
  try {
336
- this.deviceId = await import_react_native_device_info.default.getUniqueId();
508
+ const info = await nativeDeviceInfo.getDeviceInfo();
509
+ const deviceId = info.deviceId;
510
+ this.deviceId = !deviceId || deviceId === "unknown" ? generateUUID() : deviceId;
337
511
  } catch {
338
512
  this.deviceId = generateUUID();
339
513
  }
@@ -364,8 +538,13 @@ var IdentityManager = class {
364
538
  }
365
539
  }
366
540
  if (email) {
367
- this.email = email;
368
- await this.secureStorage.set(USER_EMAIL_KEY, email);
541
+ if (!EMAIL_REGEX.test(email)) {
542
+ if (this.debug) console.warn("[Paywallo] Invalid email format, skipping email field");
543
+ email = void 0;
544
+ } else {
545
+ this.email = email;
546
+ await this.secureStorage.set(USER_EMAIL_KEY, email);
547
+ }
369
548
  }
370
549
  if (properties) {
371
550
  this.properties = { ...this.properties, ...properties };
@@ -469,8 +648,8 @@ var IdentityManager = class {
469
648
  var identityManager = new IdentityManager();
470
649
 
471
650
  // src/domains/iap/NativeStoreKit.ts
472
- var PaywalloStoreKitNative = import_react_native.NativeModules.PaywalloStoreKit ?? null;
473
- function isAvailable() {
651
+ var PaywalloStoreKitNative = import_react_native3.NativeModules.PaywalloStoreKit ?? null;
652
+ function isAvailable3() {
474
653
  return PaywalloStoreKitNative != null;
475
654
  }
476
655
  function mapNativeProduct(native) {
@@ -542,11 +721,11 @@ function mapNativePurchase(native) {
542
721
  transactionId: native.transactionId,
543
722
  transactionDate: native.transactionDate,
544
723
  receipt: native.receipt,
545
- platform: import_react_native.Platform.OS
724
+ platform: import_react_native3.Platform.OS
546
725
  };
547
726
  }
548
727
  var nativeStoreKit = {
549
- isAvailable,
728
+ isAvailable: isAvailable3,
550
729
  async getProducts(productIds) {
551
730
  if (!PaywalloStoreKitNative) {
552
731
  return [];
@@ -563,8 +742,8 @@ var nativeStoreKit = {
563
742
  }
564
743
  const distinctId = identityManager.getDistinctId();
565
744
  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);
745
+ if (distinctId && !uuidRegex.test(distinctId)) {
746
+ if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
568
747
  }
569
748
  try {
570
749
  const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
@@ -585,7 +764,7 @@ var nativeStoreKit = {
585
764
  try {
586
765
  await PaywalloStoreKitNative.finishTransaction(transactionId);
587
766
  } catch (err) {
588
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction native error", err);
767
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction native error", err);
589
768
  }
590
769
  },
591
770
  async getActiveTransactions() {
@@ -594,7 +773,7 @@ var nativeStoreKit = {
594
773
  const results = await PaywalloStoreKitNative.getActiveTransactions();
595
774
  return results.map(mapNativePurchase);
596
775
  } catch (err) {
597
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] getActiveTransactions failed", err);
776
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] getActiveTransactions failed", err);
598
777
  return [];
599
778
  }
600
779
  }
@@ -605,6 +784,7 @@ var IAPService = class {
605
784
  constructor(apiClient) {
606
785
  this.productsCache = /* @__PURE__ */ new Map();
607
786
  this.apiClient = null;
787
+ this.purchaseInFlight = null;
608
788
  this.apiClient = apiClient ?? null;
609
789
  }
610
790
  get debug() {
@@ -640,7 +820,7 @@ var IAPService = class {
640
820
  }
641
821
  return products;
642
822
  } catch (err) {
643
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts failed", err);
823
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts failed", err);
644
824
  return [];
645
825
  }
646
826
  }
@@ -665,7 +845,7 @@ var IAPService = class {
665
845
  async validateWithServer(purchase, productId, options) {
666
846
  const apiClient = this.getApiClient();
667
847
  const product = this.productsCache.get(productId);
668
- const platform = import_react_native2.Platform.OS;
848
+ const platform = import_react_native4.Platform.OS;
669
849
  const distinctId = PaywalloClient.getDistinctId();
670
850
  return this.validateWithRetry(
671
851
  () => apiClient.validatePurchase(
@@ -686,7 +866,7 @@ var IAPService = class {
686
866
  try {
687
867
  await nativeStoreKit.finishTransaction(transactionId);
688
868
  } catch (err) {
689
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
869
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
690
870
  }
691
871
  }
692
872
  async purchase(productId, options) {
@@ -696,27 +876,36 @@ var IAPService = class {
696
876
  error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
697
877
  };
698
878
  }
879
+ if (this.purchaseInFlight) {
880
+ return {
881
+ success: false,
882
+ error: new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, "A purchase is already in progress")
883
+ };
884
+ }
885
+ this.purchaseInFlight = productId;
699
886
  const debug = PaywalloClient.getConfig()?.debug;
700
- if (debug) console.info(`[Paywallo PURCHASE] starting ${productId}`);
887
+ if (debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
701
888
  try {
702
889
  const purchase = await nativeStoreKit.purchase(productId);
703
890
  if (!purchase) {
704
- if (debug) console.info(`[Paywallo PURCHASE] ${productId} cancelled`);
891
+ if (debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
705
892
  return { success: false };
706
893
  }
707
- if (debug) console.info(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
894
+ if (debug) console.log(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
708
895
  await this.finishTransaction(purchase.transactionId);
709
- if (debug) console.info(`[Paywallo PURCHASE] ${productId} finished`);
896
+ if (debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
710
897
  this.validateWithServer(purchase, productId, options).then(() => {
711
- if (debug) console.info(`[Paywallo PURCHASE] ${productId} server validated`);
898
+ if (debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
712
899
  }).catch((err) => {
713
- if (debug) console.warn("[Paywallo PURCHASE] background validation failed", err);
900
+ if (debug) console.error("[Paywallo PURCHASE] background validation failed", err);
714
901
  });
715
902
  return { success: true, purchase };
716
903
  } catch (error) {
717
- if (debug) console.warn(`[Paywallo PURCHASE] ${productId} failed`, error);
904
+ if (debug) console.error(`[Paywallo PURCHASE] ${productId} failed`, error);
718
905
  const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
719
906
  return { success: false, error: e };
907
+ } finally {
908
+ this.purchaseInFlight = null;
720
909
  }
721
910
  }
722
911
  async restore() {
@@ -727,7 +916,7 @@ var IAPService = class {
727
916
  const transactions = await nativeStoreKit.getActiveTransactions();
728
917
  const apiClient = this.getApiClient();
729
918
  const distinctId = PaywalloClient.getDistinctId();
730
- const platform = import_react_native2.Platform.OS;
919
+ const platform = import_react_native4.Platform.OS;
731
920
  const results = await Promise.allSettled(
732
921
  transactions.map((tx) => {
733
922
  const product = this.productsCache.get(tx.productId);
@@ -751,14 +940,14 @@ var IAPService = class {
751
940
  try {
752
941
  await nativeStoreKit.finishTransaction(tx.transactionId);
753
942
  } catch (err) {
754
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
943
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
755
944
  }
756
945
  validated.push(tx);
757
946
  }
758
947
  }
759
948
  return validated;
760
949
  } catch (err) {
761
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] restore failed", err);
950
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] restore failed", err);
762
951
  return [];
763
952
  }
764
953
  }
@@ -789,7 +978,7 @@ function usePaywallTracking() {
789
978
  ...sessionId && { sessionId }
790
979
  });
791
980
  } catch (err) {
792
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackDismissed failed", err);
981
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackDismissed failed", err);
793
982
  }
794
983
  }, []);
795
984
  const trackProductSelected = (0, import_react.useCallback)((opts) => {
@@ -804,7 +993,7 @@ function usePaywallTracking() {
804
993
  ...opts.campaignId && { campaignId: opts.campaignId },
805
994
  ...sessionId && { sessionId }
806
995
  }).catch((err) => {
807
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackProductSelected failed", err);
996
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackProductSelected failed", err);
808
997
  });
809
998
  }, []);
810
999
  const trackPurchased = (0, import_react.useCallback)(async (opts) => {
@@ -821,25 +1010,24 @@ function usePaywallTracking() {
821
1010
  ...sessionId && { sessionId }
822
1011
  });
823
1012
  } catch (err) {
824
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackPurchased failed", err);
1013
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackPurchased failed", err);
825
1014
  }
826
1015
  }, []);
827
1016
  return { trackDismissed, trackProductSelected, trackPurchased };
828
1017
  }
829
1018
 
830
1019
  // src/domains/paywall/usePaywallActions.ts
831
- var SCREEN_HEIGHT = import_react_native3.Dimensions.get("window").height;
832
1020
  function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
833
1021
  const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
834
1022
  const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
835
- const slideAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
1023
+ const slideAnim = (0, import_react2.useRef)(new import_react_native5.Animated.Value(0)).current;
836
1024
  const presentedAtRef = (0, import_react2.useRef)(Date.now());
837
1025
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
838
1026
  const handleClose = (0, import_react2.useCallback)(() => {
839
1027
  if (isClosing) return;
840
1028
  setIsClosing(true);
841
1029
  const animTarget = openAnim ?? slideAnim;
842
- import_react_native3.Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
1030
+ import_react_native5.Animated.timing(animTarget, { toValue: import_react_native5.Dimensions.get("window").height, duration: 350, useNativeDriver: true }).start(() => {
843
1031
  if (paywallConfig) {
844
1032
  const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
845
1033
  void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
@@ -928,6 +1116,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
928
1116
  trackingDoneRef.current = false;
929
1117
  return;
930
1118
  }
1119
+ let cancelled = false;
931
1120
  const load = async () => {
932
1121
  setError(null);
933
1122
  try {
@@ -948,27 +1137,30 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
948
1137
  if (!fetched) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_FOUND, `Paywall not found for placement: ${placement}`);
949
1138
  config = fetched;
950
1139
  }
1140
+ if (cancelled) return;
951
1141
  setPaywallConfig(config);
952
1142
  const productIds = [];
953
1143
  if (config.primaryProductId) productIds.push(config.primaryProductId);
954
1144
  if (config.secondaryProductId) productIds.push(config.secondaryProductId);
955
1145
  const snapProducts = preloadedProductsRef.current;
956
1146
  if (snapProducts && snapProducts.size > 0) {
957
- setProducts(snapProducts);
1147
+ if (!cancelled) setProducts(snapProducts);
958
1148
  } else if (productIds.length > 0) {
959
1149
  try {
960
1150
  const storeProducts = await getIAPService().loadProducts(productIds);
961
- const map = /* @__PURE__ */ new Map();
962
- for (const p of storeProducts) map.set(p.productId, p);
963
- setProducts(map);
1151
+ if (!cancelled) {
1152
+ const map = /* @__PURE__ */ new Map();
1153
+ for (const p of storeProducts) map.set(p.productId, p);
1154
+ setProducts(map);
1155
+ }
964
1156
  } catch (err) {
965
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts in paywall failed", err);
966
- setProducts(/* @__PURE__ */ new Map());
1157
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts in paywall failed", err);
1158
+ if (!cancelled) setProducts(/* @__PURE__ */ new Map());
967
1159
  }
968
1160
  } else {
969
- setProducts(/* @__PURE__ */ new Map());
1161
+ if (!cancelled) setProducts(/* @__PURE__ */ new Map());
970
1162
  }
971
- if (!trackingDoneRef.current) {
1163
+ if (!trackingDoneRef.current && !cancelled) {
972
1164
  trackingDoneRef.current = true;
973
1165
  const sessionId = PaywalloClient.getSessionId();
974
1166
  await apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
@@ -980,17 +1172,21 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
980
1172
  });
981
1173
  }
982
1174
  } catch (err) {
1175
+ if (cancelled) return;
983
1176
  const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
984
1177
  setError(e);
985
1178
  }
986
1179
  };
987
1180
  void load();
1181
+ return () => {
1182
+ cancelled = true;
1183
+ };
988
1184
  }, [placement, visible, variantKey, campaignId]);
989
1185
  return { paywallConfig, products, error };
990
1186
  }
991
1187
 
992
1188
  // src/utils/localization.ts
993
- var import_react_native4 = require("react-native");
1189
+ var import_react_native6 = require("react-native");
994
1190
  var currentLanguage = "pt-BR";
995
1191
  var defaultLanguage = "pt-BR";
996
1192
  function setCurrentLanguage(language) {
@@ -1012,11 +1208,11 @@ function getLocalizedString(text) {
1012
1208
  }
1013
1209
  function detectDeviceLanguage() {
1014
1210
  try {
1015
- if (import_react_native4.Platform.OS === "ios") {
1016
- const settings = import_react_native4.NativeModules.SettingsManager?.settings;
1211
+ if (import_react_native6.Platform.OS === "ios") {
1212
+ const settings = import_react_native6.NativeModules.SettingsManager?.settings;
1017
1213
  return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
1018
1214
  }
1019
- return import_react_native4.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
1215
+ return import_react_native6.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
1020
1216
  } catch {
1021
1217
  return "en-US";
1022
1218
  }
@@ -1052,9 +1248,8 @@ var SDK_STRINGS = {
1052
1248
  };
1053
1249
  function getSdkString(key) {
1054
1250
  const map = SDK_STRINGS[key];
1055
- const lang = currentLanguage;
1056
- if (lang in map) return map[lang];
1057
- const base = lang.split("-")[0];
1251
+ if (currentLanguage in map) return map[currentLanguage];
1252
+ const base = currentLanguage.split("-")[0];
1058
1253
  if (base && base in map) return map[base];
1059
1254
  if (defaultLanguage in map) return map[defaultLanguage];
1060
1255
  return Object.values(map)[0];
@@ -1071,9 +1266,17 @@ function initLocalization(options) {
1071
1266
 
1072
1267
  // src/domains/paywall/PaywallWebView.tsx
1073
1268
  var import_react4 = require("react");
1074
- var import_react_native5 = require("react-native");
1269
+ var import_react_native7 = require("react-native");
1075
1270
 
1076
1271
  // src/domains/paywall/parseWebViewMessage.ts
1272
+ var ALLOWED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
1273
+ "purchase",
1274
+ "close",
1275
+ "restore",
1276
+ "select-product",
1277
+ "open-url",
1278
+ "ready"
1279
+ ]);
1077
1280
  function deriveMessageId(message) {
1078
1281
  if (message.id) return message.id;
1079
1282
  const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
@@ -1082,10 +1285,20 @@ function deriveMessageId(message) {
1082
1285
  function parseWebViewMessage(raw) {
1083
1286
  try {
1084
1287
  const parsed = JSON.parse(raw);
1085
- if (typeof parsed?.type !== "string") {
1288
+ if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
1086
1289
  return null;
1087
1290
  }
1088
- return parsed;
1291
+ const payload = parsed.payload;
1292
+ const safePayload = payload !== null && typeof payload === "object" ? {
1293
+ productId: typeof payload.productId === "string" ? String(payload.productId) : void 0,
1294
+ url: typeof payload.url === "string" ? String(payload.url) : void 0,
1295
+ timestamp: typeof payload.timestamp === "number" ? Number(payload.timestamp) : void 0
1296
+ } : void 0;
1297
+ return {
1298
+ type: parsed.type,
1299
+ id: typeof parsed.id === "string" ? parsed.id : void 0,
1300
+ payload: safePayload
1301
+ };
1089
1302
  } catch {
1090
1303
  return null;
1091
1304
  }
@@ -1113,14 +1326,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
1113
1326
  var NativeWebViewComponent = null;
1114
1327
  function getNativeWebView() {
1115
1328
  if (!NativeWebViewComponent) {
1116
- NativeWebViewComponent = (0, import_react_native5.requireNativeComponent)("PaywalloWebView");
1329
+ NativeWebViewComponent = (0, import_react_native7.requireNativeComponent)("PaywalloWebView");
1117
1330
  }
1118
1331
  return NativeWebViewComponent;
1119
1332
  }
1120
1333
  var webViewEmitter = null;
1121
1334
  function getWebViewEmitter() {
1122
1335
  if (!webViewEmitter) {
1123
- webViewEmitter = new import_react_native5.NativeEventEmitter(import_react_native5.NativeModules.PaywalloWebViewModule);
1336
+ webViewEmitter = new import_react_native7.NativeEventEmitter(import_react_native7.NativeModules.PaywalloWebViewModule);
1124
1337
  }
1125
1338
  return webViewEmitter;
1126
1339
  }
@@ -1195,12 +1408,24 @@ function PaywallWebView({
1195
1408
  setScriptToInject(script);
1196
1409
  }, [craftData, products, primaryProductId, secondaryProductId]);
1197
1410
  const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
1411
+ const dedupeTimersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
1412
+ (0, import_react4.useEffect)(() => {
1413
+ return () => {
1414
+ for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
1415
+ dedupeTimersRef.current.clear();
1416
+ processedIdsRef.current.clear();
1417
+ };
1418
+ }, []);
1198
1419
  const handleParsedMessage = (0, import_react4.useCallback)(
1199
1420
  (message) => {
1200
1421
  const msgId = deriveMessageId(message);
1201
1422
  if (processedIdsRef.current.has(msgId)) return;
1202
1423
  processedIdsRef.current.add(msgId);
1203
- setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
1424
+ const timerId = setTimeout(() => {
1425
+ processedIdsRef.current.delete(msgId);
1426
+ dedupeTimersRef.current.delete(msgId);
1427
+ }, 5e3);
1428
+ dedupeTimersRef.current.set(msgId, timerId);
1204
1429
  switch (message.type) {
1205
1430
  case "ready":
1206
1431
  if (!paywallDataSent.current) {
@@ -1221,8 +1446,8 @@ function PaywallWebView({
1221
1446
  case "open-url":
1222
1447
  if (message.payload?.url) {
1223
1448
  const url = message.payload.url;
1224
- if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
1225
- void import_react_native5.Linking.openURL(url);
1449
+ if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url) || /^itms-apps:/i.test(url) || /^itms-services:/i.test(url) || /^market:/i.test(url)) {
1450
+ void import_react_native7.Linking.openURL(url);
1226
1451
  }
1227
1452
  }
1228
1453
  break;
@@ -1300,7 +1525,7 @@ function PaywallWebView({
1300
1525
  }, [isPurchasing]);
1301
1526
  if (!resolvedWebUrl) return null;
1302
1527
  const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
1303
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native5.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1528
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native7.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1304
1529
  NativeWebView,
1305
1530
  {
1306
1531
  sourceUrl: paywallUrl,
@@ -1312,24 +1537,23 @@ function PaywallWebView({
1312
1537
  }
1313
1538
  ) });
1314
1539
  }
1315
- var styles = import_react_native5.StyleSheet.create({
1540
+ var styles = import_react_native7.StyleSheet.create({
1316
1541
  container: { flex: 1 },
1317
1542
  webview: { flex: 1, backgroundColor: "transparent" }
1318
1543
  });
1319
1544
 
1320
1545
  // src/domains/paywall/PaywallModal.tsx
1321
1546
  var import_jsx_runtime2 = require("react/jsx-runtime");
1322
- var SCREEN_HEIGHT2 = import_react_native6.Dimensions.get("window").height;
1323
1547
  function ErrorFallback({ description, onRetry, onClose }) {
1324
1548
  const overrides = PaywalloClient.getConfig()?.errorStrings;
1325
1549
  const title = overrides?.title ?? getSdkString("paywallErrorTitle");
1326
1550
  const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
1327
1551
  const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
1328
- 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: [
1329
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackTitle, children: title }),
1330
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackDescription, children: description }),
1331
- /* @__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 }) }),
1332
- /* @__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 }) })
1552
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native8.View, { style: styles2.fallbackCard, children: [
1553
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.fallbackTitle, children: title }),
1554
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.fallbackDescription, children: description }),
1555
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.retryButtonText, children: retryLabel }) }),
1556
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.closeButtonText, children: closeLabel }) })
1333
1557
  ] }) });
1334
1558
  }
1335
1559
  function PaywallModal({
@@ -1349,7 +1573,8 @@ function PaywallModal({
1349
1573
  variantKey,
1350
1574
  campaignId
1351
1575
  );
1352
- const openAnim = (0, import_react5.useRef)(new import_react_native6.Animated.Value(SCREEN_HEIGHT2)).current;
1576
+ const screenHeight = import_react_native8.Dimensions.get("window").height;
1577
+ const openAnim = (0, import_react5.useRef)(new import_react_native8.Animated.Value(screenHeight)).current;
1353
1578
  const [modalVisible, setModalVisible] = (0, import_react5.useState)(false);
1354
1579
  const handleResult = (0, import_react5.useCallback)((result) => {
1355
1580
  setModalVisible(false);
@@ -1378,18 +1603,18 @@ function PaywallModal({
1378
1603
  (0, import_react5.useEffect)(() => {
1379
1604
  if (visible) {
1380
1605
  setModalVisible(true);
1381
- openAnim.setValue(SCREEN_HEIGHT2);
1382
- import_react_native6.Animated.timing(openAnim, {
1606
+ openAnim.setValue(import_react_native8.Dimensions.get("window").height);
1607
+ import_react_native8.Animated.timing(openAnim, {
1383
1608
  toValue: 0,
1384
1609
  duration: 550,
1385
- easing: import_react_native6.Easing.out(import_react_native6.Easing.cubic),
1610
+ easing: import_react_native8.Easing.out(import_react_native8.Easing.cubic),
1386
1611
  useNativeDriver: true
1387
1612
  }).start();
1388
1613
  }
1389
1614
  }, [visible, openAnim]);
1390
1615
  if (!modalVisible && !visible) return null;
1391
1616
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1392
- import_react_native6.Modal,
1617
+ import_react_native8.Modal,
1393
1618
  {
1394
1619
  visible: modalVisible,
1395
1620
  animationType: "none",
@@ -1397,8 +1622,8 @@ function PaywallModal({
1397
1622
  onRequestClose: handleClose,
1398
1623
  transparent: true,
1399
1624
  statusBarTranslucent: true,
1400
- 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: [
1401
- 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 }) }),
1625
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native8.View, { style: styles2.container, children: [
1626
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.errorText, children: error.message }) }),
1402
1627
  !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1403
1628
  ErrorFallback,
1404
1629
  {
@@ -1426,7 +1651,7 @@ function PaywallModal({
1426
1651
  }
1427
1652
  );
1428
1653
  }
1429
- var styles2 = import_react_native6.StyleSheet.create({
1654
+ var styles2 = import_react_native8.StyleSheet.create({
1430
1655
  overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
1431
1656
  animatedContainer: { flex: 1, backgroundColor: "#fff" },
1432
1657
  container: { flex: 1, backgroundColor: "#fff" },
@@ -1528,6 +1753,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1528
1753
  resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
1529
1754
  resolverRef.current = null;
1530
1755
  }
1756
+ setShowingPreloadedWebView(false);
1531
1757
  };
1532
1758
  }, []);
1533
1759
  const presentPreloadedWebView = (0, import_react8.useCallback)(() => {
@@ -1572,230 +1798,10 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1572
1798
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1573
1799
  }
1574
1800
 
1575
- // src/domains/affiliate/AffiliateError.ts
1576
- var AffiliateError = class extends PaywalloError {
1577
- constructor(code, message) {
1578
- super("affiliate", code, message);
1579
- this.name = "AffiliateError";
1580
- }
1581
- };
1582
- var AFFILIATE_ERROR_CODES = {
1583
- NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
1584
- COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
1585
- COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
1586
- BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
1587
- WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
1588
- NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
1589
- };
1590
-
1591
- // src/domains/affiliate/affiliateHttp.ts
1592
- function buildQueryString(params) {
1593
- if (!params) return "";
1594
- const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
1595
- if (entries.length === 0) return "";
1596
- return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
1597
- }
1598
- async function affiliateGet(apiClient, path, params) {
1599
- const response = await apiClient.get(`${path}${buildQueryString(params)}`);
1600
- return response.data;
1601
- }
1602
- async function affiliatePost(apiClient, path, body) {
1603
- const response = await apiClient.post(path, body);
1604
- return response.data;
1605
- }
1606
-
1607
- // src/domains/affiliate/affiliateDateMappers.ts
1608
- function mapRawCoupon(raw) {
1609
- return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
1610
- }
1611
- function mapRawCommission(raw) {
1612
- return { ...raw, createdAt: new Date(raw.createdAt) };
1613
- }
1614
- function mapRawWithdrawal(raw) {
1615
- return { ...raw, createdAt: new Date(raw.createdAt) };
1616
- }
1617
-
1618
- // src/domains/affiliate/AffiliateManager.ts
1619
- var AffiliateManager = class {
1620
- constructor() {
1621
- this.apiClient = null;
1622
- this.debug = false;
1623
- }
1624
- initialize(apiClient, debug = false) {
1625
- this.apiClient = apiClient;
1626
- this.debug = debug;
1627
- this.log("AffiliateManager initialized");
1628
- }
1629
- async registerCoupon(code) {
1630
- this.ensureInitialized();
1631
- const distinctId = identityManager.getDistinctId();
1632
- try {
1633
- const response = await this.post("/api/v1/affiliate/register-coupon", {
1634
- code,
1635
- distinctId
1636
- });
1637
- if (response.success && response.coupon) {
1638
- this.log("Coupon registered:", code);
1639
- return { success: true, coupon: mapRawCoupon(response.coupon) };
1640
- }
1641
- return {
1642
- success: false,
1643
- error: response.error
1644
- };
1645
- } catch (error) {
1646
- this.log("Failed to register coupon:", error);
1647
- return {
1648
- success: false,
1649
- error: error instanceof Error ? error.message : "Unknown error"
1650
- };
1651
- }
1652
- }
1653
- async applyCoupon(code) {
1654
- this.ensureInitialized();
1655
- const distinctId = identityManager.getDistinctId();
1656
- try {
1657
- const response = await this.post("/api/v1/affiliate/apply-coupon", {
1658
- code,
1659
- distinctId
1660
- });
1661
- if (response.success) {
1662
- this.log("Coupon applied:", code);
1663
- }
1664
- return response;
1665
- } catch (error) {
1666
- this.log("Failed to apply coupon:", error);
1667
- return {
1668
- success: false,
1669
- error: error instanceof Error ? error.message : "Unknown error"
1670
- };
1671
- }
1672
- }
1673
- async getMyCoupon() {
1674
- this.ensureInitialized();
1675
- const distinctId = identityManager.getDistinctId();
1676
- try {
1677
- const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
1678
- if (response.coupon) {
1679
- return { coupon: mapRawCoupon(response.coupon) };
1680
- }
1681
- return { coupon: null };
1682
- } catch (error) {
1683
- this.log("Failed to get my coupon:", error);
1684
- return { coupon: null };
1685
- }
1686
- }
1687
- async getAppliedCoupon() {
1688
- this.ensureInitialized();
1689
- const distinctId = identityManager.getDistinctId();
1690
- try {
1691
- const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
1692
- return response;
1693
- } catch (error) {
1694
- this.log("Failed to get applied coupon:", error);
1695
- return { coupon: null };
1696
- }
1697
- }
1698
- async getBalance() {
1699
- this.ensureInitialized();
1700
- const distinctId = identityManager.getDistinctId();
1701
- try {
1702
- const response = await this.get("/api/v1/affiliate/balance", {
1703
- distinctId
1704
- });
1705
- return response;
1706
- } catch (error) {
1707
- this.log("Failed to get balance:", error);
1708
- return {
1709
- total: 0,
1710
- pending: 0,
1711
- available: 0,
1712
- withdrawn: 0
1713
- };
1714
- }
1715
- }
1716
- async getCommissions() {
1717
- this.ensureInitialized();
1718
- const distinctId = identityManager.getDistinctId();
1719
- try {
1720
- const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
1721
- return response.commissions.map(mapRawCommission);
1722
- } catch (error) {
1723
- this.log("Failed to get commissions:", error);
1724
- return [];
1725
- }
1726
- }
1727
- async requestWithdrawal(amount) {
1728
- this.ensureInitialized();
1729
- const distinctId = identityManager.getDistinctId();
1730
- try {
1731
- const response = await this.post("/api/v1/affiliate/withdraw", {
1732
- amount,
1733
- distinctId
1734
- });
1735
- if (response.success && response.withdrawal) {
1736
- this.log("Withdrawal requested:", amount);
1737
- return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
1738
- }
1739
- return {
1740
- success: false,
1741
- error: response.error
1742
- };
1743
- } catch (error) {
1744
- this.log("Failed to request withdrawal:", error);
1745
- return {
1746
- success: false,
1747
- error: error instanceof Error ? error.message : "Unknown error"
1748
- };
1749
- }
1750
- }
1751
- async getWithdrawals() {
1752
- this.ensureInitialized();
1753
- const distinctId = identityManager.getDistinctId();
1754
- try {
1755
- const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
1756
- return response.withdrawals.map(mapRawWithdrawal);
1757
- } catch (error) {
1758
- this.log("Failed to get withdrawals:", error);
1759
- return [];
1760
- }
1761
- }
1762
- async get(path, params) {
1763
- this.ensureInitialized();
1764
- return affiliateGet(this.apiClient, path, params);
1765
- }
1766
- async post(path, body) {
1767
- this.ensureInitialized();
1768
- return affiliatePost(this.apiClient, path, body);
1769
- }
1770
- ensureInitialized() {
1771
- if (!this.apiClient) {
1772
- throw new AffiliateError(
1773
- AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
1774
- "AffiliateManager not initialized. Call initialize() first"
1775
- );
1776
- }
1777
- }
1778
- log(...args) {
1779
- if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
1780
- }
1781
- };
1782
- var affiliateManager = new AffiliateManager();
1783
-
1784
- // src/domains/affiliate/PaywalloAffiliate.ts
1785
- var PaywalloAffiliate = {
1786
- registerCoupon: (code) => affiliateManager.registerCoupon(code),
1787
- applyCoupon: (code) => affiliateManager.applyCoupon(code),
1788
- getMyCoupon: () => affiliateManager.getMyCoupon(),
1789
- getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
1790
- getBalance: () => affiliateManager.getBalance(),
1791
- getCommissions: () => affiliateManager.getCommissions(),
1792
- requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
1793
- getWithdrawals: () => affiliateManager.getWithdrawals()
1794
- };
1795
-
1796
1801
  // src/domains/subscription/SubscriptionCache.ts
1797
- var LEGACY_CACHE_KEY = "subscription_cache";
1802
+ var LEGACY_CACHE_KEY = "@panel:subscription_cache";
1798
1803
  var CACHE_KEY_PREFIX = "subscription_cache:";
1804
+ var CACHE_INDEX_KEY = "subscription_cache:__index__";
1799
1805
  var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
1800
1806
  function keyFor(distinctId) {
1801
1807
  return `${CACHE_KEY_PREFIX}${distinctId}`;
@@ -1875,7 +1881,28 @@ var SubscriptionCache = class {
1875
1881
  this.memoryCache.set(distinctId, cached);
1876
1882
  try {
1877
1883
  await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
1884
+ await this.addToIndex(distinctId);
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ async addToIndex(distinctId) {
1889
+ try {
1890
+ const raw = await this.storage.get(CACHE_INDEX_KEY);
1891
+ const ids = raw ? JSON.parse(raw) : [];
1892
+ if (!ids.includes(distinctId)) {
1893
+ ids.push(distinctId);
1894
+ await this.storage.set(CACHE_INDEX_KEY, JSON.stringify(ids));
1895
+ }
1896
+ } catch {
1897
+ }
1898
+ }
1899
+ async readIndex() {
1900
+ try {
1901
+ const raw = await this.storage.get(CACHE_INDEX_KEY);
1902
+ if (!raw) return [];
1903
+ return JSON.parse(raw);
1878
1904
  } catch {
1905
+ return [];
1879
1906
  }
1880
1907
  }
1881
1908
  async invalidate(distinctId) {
@@ -1887,11 +1914,15 @@ var SubscriptionCache = class {
1887
1914
  }
1888
1915
  }
1889
1916
  async invalidateAll() {
1917
+ const memKeys = Array.from(this.memoryCache.keys());
1918
+ const indexKeys = await this.readIndex();
1919
+ const allKeys = Array.from(/* @__PURE__ */ new Set([...memKeys, ...indexKeys]));
1890
1920
  this.memoryCache.clear();
1891
- try {
1892
- await this.storage.remove(LEGACY_CACHE_KEY);
1893
- } catch {
1894
- }
1921
+ await Promise.allSettled([
1922
+ this.storage.remove(LEGACY_CACHE_KEY),
1923
+ this.storage.remove(CACHE_INDEX_KEY),
1924
+ ...allKeys.map((k) => this.storage.remove(keyFor(k)))
1925
+ ]);
1895
1926
  }
1896
1927
  isExpired(cached) {
1897
1928
  return Date.now() - cached.cachedAt > this.ttl;
@@ -1902,9 +1933,6 @@ var SubscriptionCache = class {
1902
1933
  };
1903
1934
  var subscriptionCache = new SubscriptionCache();
1904
1935
 
1905
- // src/domains/subscription/SubscriptionManager.ts
1906
- var import_react_native7 = require("react-native");
1907
-
1908
1936
  // src/domains/session/SessionError.ts
1909
1937
  var SessionError = class extends PaywalloError {
1910
1938
  constructor(code, message) {
@@ -1992,11 +2020,10 @@ var SubscriptionManagerClass = class {
1992
2020
  if (!this.config) {
1993
2021
  return this.getEmptyStatus();
1994
2022
  }
1995
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
2023
+ const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
1996
2024
  if (this.userId) {
1997
- url.searchParams.set("userId", this.userId);
2025
+ url.searchParams.set("distinctId", this.userId);
1998
2026
  }
1999
- url.searchParams.set("platform", import_react_native7.Platform.OS);
2000
2027
  const response = await fetch(url.toString(), {
2001
2028
  method: "GET",
2002
2029
  headers: {
@@ -2051,7 +2078,7 @@ var SubscriptionManagerClass = class {
2051
2078
  var subscriptionManager = new SubscriptionManagerClass();
2052
2079
 
2053
2080
  // src/core/ApiClient.ts
2054
- var import_react_native9 = require("react-native");
2081
+ var import_react_native11 = require("react-native");
2055
2082
 
2056
2083
  // src/core/apiUrlUtils.ts
2057
2084
  var DEFAULT_WEB_URL = "https://paywallo.com.br";
@@ -2099,7 +2126,7 @@ async function validatePurchase(client, appKey, platform, receipt, productId, tr
2099
2126
  false
2100
2127
  );
2101
2128
  if (!response.ok) {
2102
- throw new Error(`Server validation failed: HTTP ${response.status}`);
2129
+ throw new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, `Server validation failed: HTTP ${response.status}`);
2103
2130
  }
2104
2131
  return response.data;
2105
2132
  }
@@ -2108,7 +2135,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
2108
2135
  url.searchParams.set("distinctId", distinctId);
2109
2136
  const response = await client.get(url.toString());
2110
2137
  if (!response.ok) {
2111
- throw new Error(`Subscription status check failed: HTTP ${response.status}`);
2138
+ throw new PurchaseError(PURCHASE_ERROR_CODES.NETWORK_ERROR, `Subscription status check failed: HTTP ${response.status}`);
2112
2139
  }
2113
2140
  return response.data;
2114
2141
  }
@@ -2150,7 +2177,7 @@ var HttpClient = class {
2150
2177
  constructor(baseUrl, config) {
2151
2178
  this.config = {
2152
2179
  baseUrl,
2153
- timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
2180
+ timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout ?? 1e4,
2154
2181
  retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
2155
2182
  debug: config?.debug ?? false
2156
2183
  };
@@ -2180,7 +2207,7 @@ var HttpClient = class {
2180
2207
  if (this.shouldRetry(response.status, retryConfig)) {
2181
2208
  if (state.attempt < retryConfig.maxRetries) {
2182
2209
  state.attempt++;
2183
- const delay = this.calculateDelay(state.attempt, retryConfig);
2210
+ const delay = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
2184
2211
  await this.sleep(delay);
2185
2212
  continue;
2186
2213
  }
@@ -2234,7 +2261,7 @@ var HttpClient = class {
2234
2261
  };
2235
2262
  } catch (error) {
2236
2263
  this.log("Request failed", {
2237
- url,
2264
+ url: this.redactUrl(url),
2238
2265
  error: error instanceof Error ? error.message : String(error)
2239
2266
  });
2240
2267
  throw error;
@@ -2247,6 +2274,14 @@ var HttpClient = class {
2247
2274
  }
2248
2275
  buildUrl(path) {
2249
2276
  if (path.startsWith("http://") || path.startsWith("https://")) {
2277
+ if (path.startsWith("http://")) {
2278
+ const isLocalDev = /^http:\/\/(localhost|127\.0\.0\.1|192\.168\.\d{1,3}\.\d{1,3})/.test(path);
2279
+ if (!isLocalDev) {
2280
+ const sanitizedPath = path.split("?")[0];
2281
+ this.log("Blocked http:// request \u2014 only HTTPS is allowed in production:", sanitizedPath);
2282
+ throw new ClientError(CLIENT_ERROR_CODES.INSECURE_REQUEST, `Insecure HTTP request blocked: ${sanitizedPath}`);
2283
+ }
2284
+ }
2250
2285
  return path;
2251
2286
  }
2252
2287
  return `${this.config.baseUrl}${path}`;
@@ -2286,9 +2321,28 @@ var HttpClient = class {
2286
2321
  const jitter = Math.random() * 0.3 * exponentialDelay;
2287
2322
  return Math.min(exponentialDelay + jitter, config.maxDelay);
2288
2323
  }
2324
+ calculateDelayForResponse(attempt, headers, config) {
2325
+ const retryAfter = headers["retry-after"];
2326
+ if (retryAfter) {
2327
+ const seconds = Number(retryAfter);
2328
+ if (!Number.isNaN(seconds) && seconds > 0) {
2329
+ return Math.min(seconds * 1e3, config.maxDelay);
2330
+ }
2331
+ const date = new Date(retryAfter);
2332
+ if (!Number.isNaN(date.getTime())) {
2333
+ const ms = date.getTime() - Date.now();
2334
+ if (ms > 0) return Math.min(ms, config.maxDelay);
2335
+ }
2336
+ }
2337
+ return this.calculateDelay(attempt, config);
2338
+ }
2289
2339
  sleep(ms) {
2290
2340
  return new Promise((resolve) => setTimeout(resolve, ms));
2291
2341
  }
2342
+ /** Strips path segments that look like app keys (alphanumeric, 20–64 chars) from URLs before logging. */
2343
+ redactUrl(url) {
2344
+ return url.replace(/\/([a-zA-Z0-9_-]{20,64})(\?|$|\/)/g, "/[REDACTED]$2");
2345
+ }
2292
2346
  log(...args) {
2293
2347
  if (this.config.debug) console.log("[Paywallo:Http]", ...args);
2294
2348
  }
@@ -2301,7 +2355,7 @@ var HttpClient = class {
2301
2355
  };
2302
2356
 
2303
2357
  // src/core/network/NetworkMonitor.ts
2304
- var import_react_native8 = require("react-native");
2358
+ var import_react_native9 = require("react-native");
2305
2359
 
2306
2360
  // src/core/network/types.ts
2307
2361
  var DEFAULT_NETWORK_CONFIG = {
@@ -2338,9 +2392,19 @@ var NetworkMonitorClass = class {
2338
2392
  }, this.config.checkInterval);
2339
2393
  }
2340
2394
  setupAppStateListener() {
2341
- this.appStateSubscription = import_react_native8.AppState.addEventListener("change", (state) => {
2395
+ this.appStateSubscription = import_react_native9.AppState.addEventListener("change", (state) => {
2342
2396
  if (state === "active") {
2397
+ if (!this.checkIntervalId) {
2398
+ this.checkIntervalId = setInterval(() => {
2399
+ void this.checkConnectivity();
2400
+ }, this.config.checkInterval);
2401
+ }
2343
2402
  void this.checkConnectivity();
2403
+ } else if (state === "background" || state === "inactive") {
2404
+ if (this.checkIntervalId) {
2405
+ clearInterval(this.checkIntervalId);
2406
+ this.checkIntervalId = null;
2407
+ }
2344
2408
  }
2345
2409
  });
2346
2410
  }
@@ -2422,7 +2486,6 @@ var NetworkMonitorClass = class {
2422
2486
  var networkMonitor = new NetworkMonitorClass();
2423
2487
 
2424
2488
  // src/core/queue/OfflineQueue.ts
2425
- var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
2426
2489
  init_uuid();
2427
2490
 
2428
2491
  // src/core/queue/deduplication.ts
@@ -2484,7 +2547,7 @@ var OfflineQueueClass = class {
2484
2547
  }
2485
2548
  async loadFromStorage() {
2486
2549
  try {
2487
- const stored = await import_async_storage2.default.getItem(this.config.storageKey);
2550
+ const stored = await nativeStorage.get(this.config.storageKey);
2488
2551
  if (stored) {
2489
2552
  this.queue = JSON.parse(stored);
2490
2553
  }
@@ -2494,7 +2557,7 @@ var OfflineQueueClass = class {
2494
2557
  }
2495
2558
  async saveToStorage() {
2496
2559
  try {
2497
- await import_async_storage2.default.setItem(this.config.storageKey, JSON.stringify(this.queue));
2560
+ await nativeStorage.set(this.config.storageKey, JSON.stringify(this.queue));
2498
2561
  return true;
2499
2562
  } catch (error) {
2500
2563
  this.log("Failed to save queue to storage", error);
@@ -2509,7 +2572,7 @@ var OfflineQueueClass = class {
2509
2572
  cleanupExpired() {
2510
2573
  const now = Date.now();
2511
2574
  const before = this.queue.length;
2512
- this.queue = this.queue.filter((item) => Date.now() - item.timestamp < this.config.maxAge);
2575
+ this.queue = this.queue.filter((item) => now - item.timestamp < this.config.maxAge);
2513
2576
  if (before !== this.queue.length) {
2514
2577
  void this.saveToStorage();
2515
2578
  }
@@ -2532,10 +2595,16 @@ var OfflineQueueClass = class {
2532
2595
  return this.queue[existingIndex];
2533
2596
  }
2534
2597
  if (this.queue.length >= this.config.maxItems) {
2535
- const removed = this.queue.shift();
2536
- if (removed) {
2537
- this.emit({ type: "item:evicted", item: removed, queueSize: this.queue.length });
2598
+ let evictIndex = 0;
2599
+ for (let i = 1; i < this.queue.length; i++) {
2600
+ const candidate = this.queue[i];
2601
+ const current = this.queue[evictIndex];
2602
+ if (candidate.attempts > current.attempts || candidate.attempts === current.attempts && candidate.timestamp < current.timestamp) {
2603
+ evictIndex = i;
2604
+ }
2538
2605
  }
2606
+ const [removed] = this.queue.splice(evictIndex, 1);
2607
+ this.emit({ type: "item:evicted", item: removed, queueSize: this.queue.length });
2539
2608
  }
2540
2609
  this.queue.push(item);
2541
2610
  await this.saveToStorage();
@@ -2734,9 +2803,78 @@ var QueueProcessorClass = class {
2734
2803
  this.log("Offline, skipping queue processing");
2735
2804
  return { processed: 0, failed: 0 };
2736
2805
  }
2737
- return offlineQueue.processQueue(async (item) => {
2806
+ const allItems = offlineQueue.getQueue();
2807
+ const eventItems = allItems.filter(
2808
+ (i) => i.url.includes("/api/v1/events") && !i.url.includes("/batch")
2809
+ );
2810
+ const eventItemIds = new Set(eventItems.map((i) => i.id));
2811
+ const { processed: batchProcessed, failed: batchFailed, retryIds } = await this.processEventBatches(eventItems);
2812
+ const batchResult = { processed: batchProcessed, failed: batchFailed };
2813
+ const individualResult = await offlineQueue.processQueue(async (item) => {
2814
+ if (eventItemIds.has(item.id)) {
2815
+ if (retryIds.has(item.id)) {
2816
+ return { success: false, permanent: false };
2817
+ }
2818
+ return { success: true, permanent: false };
2819
+ }
2738
2820
  return this.processItem(item);
2739
2821
  });
2822
+ this.log("Queue processing complete", {
2823
+ batchProcessed: batchResult.processed,
2824
+ batchFailed: batchResult.failed,
2825
+ individualProcessed: individualResult.processed,
2826
+ individualFailed: individualResult.failed
2827
+ });
2828
+ return {
2829
+ processed: batchResult.processed + individualResult.processed,
2830
+ failed: batchResult.failed + individualResult.failed
2831
+ };
2832
+ }
2833
+ async processEventBatches(eventItems) {
2834
+ if (eventItems.length === 0 || !this.httpClient) {
2835
+ return { processed: 0, failed: 0, retryIds: /* @__PURE__ */ new Set() };
2836
+ }
2837
+ const BATCH_SIZE = 50;
2838
+ let processed = 0;
2839
+ let failed = 0;
2840
+ const retryIds = /* @__PURE__ */ new Set();
2841
+ for (let i = 0; i < eventItems.length; i += BATCH_SIZE) {
2842
+ const batch = eventItems.slice(i, i + BATCH_SIZE);
2843
+ const bodies = batch.map((item) => item.body);
2844
+ const headers = batch[0].headers;
2845
+ this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
2846
+ const response = await this.httpClient.request("/api/v1/events/batch", {
2847
+ method: "POST",
2848
+ body: { events: bodies },
2849
+ headers,
2850
+ skipRetry: false
2851
+ });
2852
+ if (response.ok) {
2853
+ for (const item of batch) {
2854
+ await offlineQueue.removeItem(item.id);
2855
+ processed++;
2856
+ }
2857
+ this.log("Event batch processed successfully", { size: batch.length });
2858
+ } else if (response.status >= 400 && response.status < 500) {
2859
+ for (const item of batch) {
2860
+ await offlineQueue.removeItem(item.id);
2861
+ failed++;
2862
+ }
2863
+ this.log("Event batch failed permanently (4xx) - removing from queue", {
2864
+ status: response.status,
2865
+ size: batch.length
2866
+ });
2867
+ } else {
2868
+ for (const item of batch) {
2869
+ retryIds.add(item.id);
2870
+ }
2871
+ this.log("Event batch failed with server error - will retry", {
2872
+ status: response.status,
2873
+ size: batch.length
2874
+ });
2875
+ }
2876
+ }
2877
+ return { processed, failed, retryIds };
2740
2878
  }
2741
2879
  async processItem(item) {
2742
2880
  if (!this.httpClient) {
@@ -2746,7 +2884,6 @@ var QueueProcessorClass = class {
2746
2884
  id: item.id,
2747
2885
  url: item.url,
2748
2886
  method: item.method,
2749
- headers: item.headers,
2750
2887
  attempts: item.attempts
2751
2888
  });
2752
2889
  const response = await this.httpClient.request(item.url, {
@@ -2764,7 +2901,7 @@ var QueueProcessorClass = class {
2764
2901
  id: item.id,
2765
2902
  url: item.url,
2766
2903
  status: response.status,
2767
- appKeyInHeaders: item.headers?.["X-App-Key"]
2904
+ hasAppKey: item.headers?.["X-App-Key"] != null
2768
2905
  });
2769
2906
  return { success: false, permanent: true, status: response.status };
2770
2907
  }
@@ -2886,9 +3023,94 @@ var ApiCache = class {
2886
3023
  }
2887
3024
  };
2888
3025
 
3026
+ // src/core/EventBatcher.ts
3027
+ var import_react_native10 = require("react-native");
3028
+ var BATCH_MAX_SIZE = 10;
3029
+ var BATCH_FLUSH_MS = 5e3;
3030
+ var EventBatcher = class {
3031
+ constructor(post, debug = false) {
3032
+ this.batch = [];
3033
+ this.timer = null;
3034
+ this.post = post;
3035
+ this.debug = debug;
3036
+ }
3037
+ async track(eventName, distinctId, environment, properties, timestamp) {
3038
+ const EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
3039
+ if (!EVENT_NAME_REGEX.test(eventName)) {
3040
+ if (this.debug) console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
3041
+ }
3042
+ const event = {
3043
+ eventName,
3044
+ distinctId,
3045
+ properties: { ...properties, platform: import_react_native10.Platform.OS },
3046
+ timestamp: timestamp ?? Date.now(),
3047
+ environment: environment.toLowerCase()
3048
+ };
3049
+ this.batch.push(event);
3050
+ if (this.batch.length >= BATCH_MAX_SIZE) {
3051
+ if (this.timer !== null) {
3052
+ clearTimeout(this.timer);
3053
+ this.timer = null;
3054
+ }
3055
+ await this.flush();
3056
+ return;
3057
+ }
3058
+ if (this.timer !== null) clearTimeout(this.timer);
3059
+ this.timer = setTimeout(() => {
3060
+ this.timer = null;
3061
+ void this.flush();
3062
+ }, BATCH_FLUSH_MS);
3063
+ }
3064
+ async flush() {
3065
+ if (this.batch.length === 0) return;
3066
+ const batch = this.batch.splice(0, this.batch.length);
3067
+ if (this.timer !== null) {
3068
+ clearTimeout(this.timer);
3069
+ this.timer = null;
3070
+ }
3071
+ try {
3072
+ await this.post("/api/v1/events/batch", { events: batch }, `event_batch:${batch.length}`);
3073
+ } catch {
3074
+ for (const event of batch) {
3075
+ await this.post("/api/v1/events", event, `event:${event.eventName}`);
3076
+ }
3077
+ }
3078
+ }
3079
+ dispose() {
3080
+ if (this.timer !== null) {
3081
+ clearTimeout(this.timer);
3082
+ this.timer = null;
3083
+ }
3084
+ this.batch = [];
3085
+ }
3086
+ };
3087
+
3088
+ // src/core/FlagStorage.ts
3089
+ var FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3090
+ async function readFlagFromStorage(flagKey, distinctId) {
3091
+ try {
3092
+ const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
3093
+ if (!raw) return null;
3094
+ const parsed = JSON.parse(raw);
3095
+ if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
3096
+ const { variant, payload, cachedAt } = parsed;
3097
+ if (Date.now() - cachedAt > FLAG_MAX_AGE_MS) return null;
3098
+ return { variant, payload };
3099
+ } catch {
3100
+ return null;
3101
+ }
3102
+ }
3103
+ async function writeFlagToStorage(flagKey, distinctId, flag) {
3104
+ try {
3105
+ const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
3106
+ await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
3107
+ } catch {
3108
+ }
3109
+ }
3110
+
2889
3111
  // src/core/ApiClient.ts
2890
- var SDK_VERSION = "1.4.1";
2891
- var _ApiClient = class _ApiClient {
3112
+ var SDK_VERSION = "1.5.2";
3113
+ var ApiClient = class {
2892
3114
  constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2893
3115
  this.cache = new ApiCache();
2894
3116
  this.appKey = appKey;
@@ -2898,6 +3120,7 @@ var _ApiClient = class _ApiClient {
2898
3120
  this.environment = environment;
2899
3121
  this.offlineQueueEnabled = offlineQueueEnabled;
2900
3122
  this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
3123
+ this.batcher = new EventBatcher((url, payload, label) => this.postWithQueue(url, payload, label), debug);
2901
3124
  }
2902
3125
  getHttpClient() {
2903
3126
  return this.httpClient;
@@ -2908,6 +3131,9 @@ var _ApiClient = class _ApiClient {
2908
3131
  getWebUrl() {
2909
3132
  return this.webUrl;
2910
3133
  }
3134
+ getAppKey() {
3135
+ return this.appKey;
3136
+ }
2911
3137
  getEnvironment() {
2912
3138
  return this.environment;
2913
3139
  }
@@ -2943,30 +3169,33 @@ var _ApiClient = class _ApiClient {
2943
3169
  message,
2944
3170
  context,
2945
3171
  sdkVersion: SDK_VERSION,
2946
- platform: import_react_native9.Platform.OS
3172
+ platform: import_react_native11.Platform.OS
2947
3173
  }, true);
2948
3174
  } catch (err) {
2949
- if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
3175
+ if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
2950
3176
  }
2951
3177
  }
2952
3178
  async trackEvent(eventName, distinctId, properties, timestamp) {
2953
- await this.postWithQueue("/api/v1/events", {
2954
- eventName,
2955
- distinctId,
2956
- properties: { ...properties, platform: import_react_native9.Platform.OS },
2957
- timestamp: timestamp ?? Date.now(),
2958
- environment: this.environment.toLowerCase()
2959
- }, `event:${eventName}`);
3179
+ await this.batcher.track(eventName, distinctId, this.environment, properties, timestamp);
3180
+ }
3181
+ async flushEventBatch() {
3182
+ await this.batcher.flush();
3183
+ }
3184
+ dispose() {
3185
+ this.batcher.dispose();
2960
3186
  }
2961
3187
  async identify(distinctId, properties, email, deviceId) {
2962
- await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native9.Platform.OS, ...deviceId && { deviceId } }, "identify");
3188
+ await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native11.Platform.OS, ...deviceId && { deviceId } }, "identify");
2963
3189
  }
2964
- async startSession(distinctId, sessionId, platform) {
3190
+ async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
2965
3191
  await this.postWithQueue("/api/v1/sessions/start", {
2966
3192
  distinctId,
2967
3193
  sessionId,
2968
- platform: platform ?? import_react_native9.Platform.OS,
2969
- environment: this.environment.toLowerCase()
3194
+ platform: platform ?? import_react_native11.Platform.OS,
3195
+ environment: this.environment.toLowerCase(),
3196
+ appVersion: appVersion ?? "unknown",
3197
+ deviceModel: deviceModel ?? "unknown",
3198
+ osVersion: osVersion ?? String(import_react_native11.Platform.Version)
2970
3199
  }, `session.start:${sessionId}`);
2971
3200
  return { success: true };
2972
3201
  }
@@ -2978,7 +3207,7 @@ var _ApiClient = class _ApiClient {
2978
3207
  const key = `${flagKey}:${distinctId}`;
2979
3208
  const hit = this.cache.getVariant(key);
2980
3209
  if (hit !== void 0) return hit;
2981
- const persisted = await this.readFlagFromStorage(flagKey, distinctId);
3210
+ const persisted = await readFlagFromStorage(flagKey, distinctId);
2982
3211
  if (persisted !== null) {
2983
3212
  this.cache.setVariant(key, persisted);
2984
3213
  return persisted;
@@ -2986,25 +3215,20 @@ var _ApiClient = class _ApiClient {
2986
3215
  try {
2987
3216
  const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
2988
3217
  if (res.status === 404) {
2989
- this.log("Flag not found (404):", flagKey);
2990
3218
  const v = { variant: null };
2991
3219
  this.cache.setVariant(key, v, this.cache.nullTTL);
2992
3220
  return v;
2993
3221
  }
2994
3222
  if (!res.ok) {
2995
- this.log("Non-OK response for variant:", flagKey, res.status);
2996
3223
  const stale = this.cache.getStaleVariant(key);
2997
- if (stale !== void 0) return stale;
2998
- return { variant: null };
3224
+ return stale !== void 0 ? stale : { variant: null };
2999
3225
  }
3000
3226
  this.cache.setVariant(key, res.data);
3001
- await this.writeFlagToStorage(flagKey, distinctId, res.data);
3227
+ await writeFlagToStorage(flagKey, distinctId, res.data);
3002
3228
  return res.data;
3003
- } catch (error) {
3004
- this.log("Failed to get variant:", flagKey, error);
3229
+ } catch {
3005
3230
  const stale = this.cache.getStaleVariant(key);
3006
- if (stale !== void 0) return stale;
3007
- return { variant: null };
3231
+ return stale !== void 0 ? stale : { variant: null };
3008
3232
  }
3009
3233
  }
3010
3234
  async getPaywall(placement) {
@@ -3013,29 +3237,22 @@ var _ApiClient = class _ApiClient {
3013
3237
  try {
3014
3238
  const res = await this.get(`/api/v1/paywalls/${placement}`);
3015
3239
  if (res.status === 404) {
3016
- this.log("Paywall not found (404):", placement);
3017
3240
  this.cache.setPaywall(placement, null, this.cache.nullTTL);
3018
3241
  return null;
3019
3242
  }
3020
3243
  if (!res.ok) {
3021
- this.log("Non-OK response for paywall:", placement, res.status);
3022
3244
  const stale = this.cache.getStalePaywall(placement);
3023
- if (stale !== void 0) return stale;
3024
- return null;
3245
+ return stale !== void 0 ? stale : null;
3025
3246
  }
3026
3247
  this.cache.setPaywall(placement, res.data);
3027
3248
  return res.data;
3028
- } catch (error) {
3029
- this.log("Failed to get paywall:", placement, error);
3249
+ } catch {
3030
3250
  const stale = this.cache.getStalePaywall(placement);
3031
- if (stale !== void 0) return stale;
3032
- return null;
3251
+ return stale !== void 0 ? stale : null;
3033
3252
  }
3034
3253
  }
3035
3254
  async getEmergencyPaywall() {
3036
- const result = await getEmergencyPaywall(this);
3037
- this.log("Got emergency paywall:", result.enabled);
3038
- return result;
3255
+ return getEmergencyPaywall(this);
3039
3256
  }
3040
3257
  async getCampaign(placement, distinctId, context) {
3041
3258
  const key = `${placement}:${distinctId}`;
@@ -3044,18 +3261,14 @@ var _ApiClient = class _ApiClient {
3044
3261
  try {
3045
3262
  const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
3046
3263
  if (!result) {
3047
- this.log("No campaign found for placement:", placement);
3048
3264
  this.cache.setCampaign(key, null, this.cache.nullTTL);
3049
3265
  return null;
3050
3266
  }
3051
- this.log("Got campaign:", placement, result.variantKey);
3052
3267
  this.cache.setCampaign(key, result);
3053
3268
  return result;
3054
- } catch (error) {
3055
- this.log("Failed to get campaign:", placement, error);
3269
+ } catch {
3056
3270
  const stale = this.cache.getStaleCampaign(key);
3057
- if (stale !== void 0) return stale;
3058
- return null;
3271
+ return stale !== void 0 ? stale : null;
3059
3272
  }
3060
3273
  }
3061
3274
  async getPrimaryCampaign(distinctId) {
@@ -3065,63 +3278,34 @@ var _ApiClient = class _ApiClient {
3065
3278
  try {
3066
3279
  const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
3067
3280
  if (res.status === 404) {
3068
- this.log("Primary campaign not found (404)");
3069
3281
  this.cache.setCampaign(key, null, this.cache.nullTTL);
3070
3282
  return null;
3071
3283
  }
3072
3284
  if (!res.ok) {
3073
- this.log("Non-OK response for primary campaign:", res.status);
3074
3285
  const stale = this.cache.getStaleCampaign(key);
3075
- if (stale !== void 0) return stale;
3076
- return null;
3286
+ return stale !== void 0 ? stale : null;
3077
3287
  }
3078
- this.log("Got primary campaign:", res.data?.campaignId);
3079
3288
  this.cache.setCampaign(key, res.data);
3080
3289
  return res.data;
3081
- } catch (error) {
3082
- this.log("Failed to get primary campaign:", error);
3290
+ } catch {
3083
3291
  const stale = this.cache.getStaleCampaign(key);
3084
- if (stale !== void 0) return stale;
3085
- return null;
3292
+ return stale !== void 0 ? stale : null;
3086
3293
  }
3087
3294
  }
3088
3295
  async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
3089
- const result = await validatePurchase(
3090
- this,
3091
- this.appKey,
3092
- platform,
3093
- receipt,
3094
- productId,
3095
- transactionId,
3096
- priceLocal,
3097
- currency,
3098
- country,
3099
- distinctId,
3100
- paywallPlacement,
3101
- variantKey
3102
- );
3103
- this.log("Purchase validated:", result.success);
3104
- return result;
3296
+ return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
3105
3297
  }
3106
3298
  async getSubscriptionStatus(distinctId) {
3107
- const result = await getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
3108
- this.log("Subscription status:", result.hasActiveSubscription);
3109
- return result;
3299
+ return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
3110
3300
  }
3111
3301
  async evaluateFlags(keys, distinctId) {
3112
3302
  const query = keys.map(encodeURIComponent).join(",");
3113
- const path = `/api/v1/flags/evaluate?keys=${query}`;
3114
3303
  try {
3115
- const res = await this.httpClient.get(path, {
3304
+ const res = await this.httpClient.get(`/api/v1/flags/evaluate?keys=${query}`, {
3116
3305
  headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
3117
3306
  });
3118
- if (!res.ok) {
3119
- this.log("evaluateFlags failed:", res.status, keys);
3120
- return {};
3121
- }
3122
- const raw = res.data;
3123
- if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
3124
- const record = raw;
3307
+ if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
3308
+ const record = res.data;
3125
3309
  const result = {};
3126
3310
  for (const key of Object.keys(record)) {
3127
3311
  const value = record[key];
@@ -3129,11 +3313,10 @@ var _ApiClient = class _ApiClient {
3129
3313
  result[key] = variant;
3130
3314
  const flagVariant = { variant };
3131
3315
  this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
3132
- await this.writeFlagToStorage(key, distinctId, flagVariant);
3316
+ await writeFlagToStorage(key, distinctId, flagVariant);
3133
3317
  }
3134
3318
  return result;
3135
- } catch (error) {
3136
- this.log("Failed to evaluateFlags:", error);
3319
+ } catch {
3137
3320
  return {};
3138
3321
  }
3139
3322
  }
@@ -3141,15 +3324,9 @@ var _ApiClient = class _ApiClient {
3141
3324
  try {
3142
3325
  const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
3143
3326
  const response = await this.get(url);
3144
- if (response.status === 404) return { value: false, flagKey };
3145
- if (!response.ok) {
3146
- this.log("Non-OK response for conditional flag:", flagKey, response.status);
3147
- return { value: false, flagKey };
3148
- }
3149
- this.log("Got conditional flag:", flagKey, response.data.value);
3327
+ if (response.status === 404 || !response.ok) return { value: false, flagKey };
3150
3328
  return { value: response.data.value, flagKey };
3151
3329
  } catch {
3152
- this.log("Failed to get conditional flag:", flagKey);
3153
3330
  return { value: false, flagKey };
3154
3331
  }
3155
3332
  }
@@ -3157,34 +3334,13 @@ var _ApiClient = class _ApiClient {
3157
3334
  const key = `${flagKey}:${distinctId}`;
3158
3335
  const hit = this.cache.getVariant(key);
3159
3336
  if (hit !== void 0) return hit;
3160
- const persisted = await this.readFlagFromStorage(flagKey, distinctId);
3337
+ const persisted = await readFlagFromStorage(flagKey, distinctId);
3161
3338
  if (persisted !== null) {
3162
3339
  this.cache.setVariant(key, persisted);
3163
3340
  return persisted;
3164
3341
  }
3165
3342
  return null;
3166
3343
  }
3167
- async readFlagFromStorage(flagKey, distinctId) {
3168
- try {
3169
- const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
3170
- if (!raw) return null;
3171
- const parsed = JSON.parse(raw);
3172
- if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
3173
- const { variant, payload, cachedAt } = parsed;
3174
- if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
3175
- return { variant, payload };
3176
- } catch {
3177
- return null;
3178
- }
3179
- }
3180
- async writeFlagToStorage(flagKey, distinctId, flag) {
3181
- try {
3182
- const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
3183
- await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
3184
- } catch (err) {
3185
- this.log("writeFlagToStorage failed", err);
3186
- }
3187
- }
3188
3344
  async postWithQueue(url, payload, label) {
3189
3345
  if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
3190
3346
  await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
@@ -3197,7 +3353,6 @@ var _ApiClient = class _ApiClient {
3197
3353
  this.log("Request failed:", label, error);
3198
3354
  if (this.offlineQueueEnabled) {
3199
3355
  await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
3200
- this.log("Queued after failure:", label);
3201
3356
  return;
3202
3357
  }
3203
3358
  throw error;
@@ -3207,8 +3362,6 @@ var _ApiClient = class _ApiClient {
3207
3362
  if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
3208
3363
  }
3209
3364
  };
3210
- _ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
3211
- var ApiClient = _ApiClient;
3212
3365
 
3213
3366
  // src/domains/campaign/CampaignError.ts
3214
3367
  var CampaignError = class extends PaywalloError {
@@ -3233,6 +3386,7 @@ var CampaignGateService = class {
3233
3386
  constructor() {
3234
3387
  this.preloadedCampaigns = /* @__PURE__ */ new Map();
3235
3388
  this.pendingPreloads = /* @__PURE__ */ new Set();
3389
+ this.activePreloadPromises = /* @__PURE__ */ new Map();
3236
3390
  }
3237
3391
  async getCampaign(apiClient, placement, context) {
3238
3392
  const distinctId = identityManager.getDistinctId();
@@ -3272,12 +3426,23 @@ var CampaignGateService = class {
3272
3426
  }
3273
3427
  async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
3274
3428
  const isActive = await hasActive();
3275
- if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3429
+ if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3276
3430
  if (isActive) return true;
3277
- const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
3431
+ const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
3278
3432
  return r.purchased || r.restored;
3279
3433
  }
3280
3434
  async preloadCampaign(apiClient, placement, context) {
3435
+ const existing = this.activePreloadPromises.get(placement);
3436
+ if (existing) return existing;
3437
+ const promise = this._doPreload(apiClient, placement, context);
3438
+ this.activePreloadPromises.set(placement, promise);
3439
+ try {
3440
+ return await promise;
3441
+ } finally {
3442
+ this.activePreloadPromises.delete(placement);
3443
+ }
3444
+ }
3445
+ async _doPreload(apiClient, placement, context) {
3281
3446
  this.pendingPreloads.add(placement);
3282
3447
  try {
3283
3448
  const campaign = await this.getCampaign(apiClient, placement, context);
@@ -3286,7 +3451,7 @@ var CampaignGateService = class {
3286
3451
  }
3287
3452
  this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
3288
3453
  if (PaywalloClient.getConfig()?.debug) {
3289
- console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
3454
+ console.log(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
3290
3455
  }
3291
3456
  return { success: true };
3292
3457
  } catch (error) {
@@ -3312,7 +3477,7 @@ var CampaignGateService = class {
3312
3477
  var campaignGateService = new CampaignGateService();
3313
3478
 
3314
3479
  // src/domains/identity/AdvertisingIdManager.ts
3315
- var import_react_native10 = require("react-native");
3480
+ var import_react_native12 = require("react-native");
3316
3481
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
3317
3482
  var AdvertisingIdManager = class {
3318
3483
  constructor() {
@@ -3323,7 +3488,7 @@ var AdvertisingIdManager = class {
3323
3488
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
3324
3489
  try {
3325
3490
  const tracking = await import("expo-tracking-transparency");
3326
- if (import_react_native10.Platform.OS === "ios") {
3491
+ if (import_react_native12.Platform.OS === "ios") {
3327
3492
  if (!tracking.isAvailable()) {
3328
3493
  const idfv2 = await this.collectIdfv();
3329
3494
  this.cached = { ...fallback, idfv: idfv2 };
@@ -3342,10 +3507,8 @@ var AdvertisingIdManager = class {
3342
3507
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
3343
3508
  return this.cached;
3344
3509
  }
3345
- if (import_react_native10.Platform.OS === "android") {
3346
- const androidStatus = await tracking.getTrackingPermissionsAsync();
3347
- const optedIn = androidStatus.granted;
3348
- const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
3510
+ if (import_react_native12.Platform.OS === "android") {
3511
+ const rawGaid = tracking.getAdvertisingId();
3349
3512
  const gaid = this.sanitizeId(rawGaid);
3350
3513
  this.cached = { idfv: null, idfa: null, gaid, attStatus: "unavailable" };
3351
3514
  return this.cached;
@@ -3359,9 +3522,8 @@ var AdvertisingIdManager = class {
3359
3522
  }
3360
3523
  async collectIdfv() {
3361
3524
  try {
3362
- const app = await import("expo-application");
3363
- const idfv = await app.getIosIdForVendorAsync();
3364
- return this.sanitizeId(idfv);
3525
+ const info = await nativeDeviceInfo.getDeviceInfo();
3526
+ return this.sanitizeId(info.idfv);
3365
3527
  } catch {
3366
3528
  return null;
3367
3529
  }
@@ -3373,14 +3535,12 @@ var AdvertisingIdManager = class {
3373
3535
  };
3374
3536
 
3375
3537
  // src/domains/identity/InstallTracker.ts
3376
- var import_react_native14 = require("react-native");
3377
- var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
3378
- var import_react_native_device_info2 = __toESM(require("react-native-device-info"));
3538
+ var import_react_native16 = require("react-native");
3379
3539
 
3380
3540
  // src/domains/identity/MetaBridge.ts
3381
- var import_react_native11 = require("react-native");
3541
+ var import_react_native13 = require("react-native");
3382
3542
  var TIMEOUT_MS = 2e3;
3383
- var NativeBridge = import_react_native11.NativeModules.PaywalloFBBridge ?? null;
3543
+ var NativeBridge = import_react_native13.NativeModules.PaywalloFBBridge ?? null;
3384
3544
  var MetaBridge = class {
3385
3545
  async getAnonymousID() {
3386
3546
  if (!NativeBridge) return null;
@@ -3428,11 +3588,11 @@ var MetaBridge = class {
3428
3588
  var metaBridge = new MetaBridge();
3429
3589
 
3430
3590
  // src/domains/identity/InstallReferrerManager.ts
3431
- var import_react_native12 = require("react-native");
3591
+ var import_react_native14 = require("react-native");
3432
3592
  var REFERRER_TIMEOUT_MS = 5e3;
3433
3593
  var InstallReferrerManager = class {
3434
3594
  async getReferrer() {
3435
- if (import_react_native12.Platform.OS !== "android") {
3595
+ if (import_react_native14.Platform.OS !== "android") {
3436
3596
  return this.emptyResult();
3437
3597
  }
3438
3598
  let timerId;
@@ -3446,8 +3606,7 @@ var InstallReferrerManager = class {
3446
3606
  }
3447
3607
  async fetchReferrer() {
3448
3608
  try {
3449
- const Application = await import("expo-application");
3450
- const referrer = await Application.getInstallReferrerAsync();
3609
+ const referrer = await nativeDeviceInfo.getInstallReferrer();
3451
3610
  if (!referrer) return this.emptyResult();
3452
3611
  const capped = referrer.slice(0, 2048);
3453
3612
  const params = this.parseReferrer(capped);
@@ -3492,17 +3651,45 @@ var InstallReferrerManager = class {
3492
3651
  var installReferrerManager = new InstallReferrerManager();
3493
3652
 
3494
3653
  // src/domains/session/SessionManager.ts
3495
- var import_react_native13 = require("react-native");
3654
+ var import_react_native15 = require("react-native");
3496
3655
  init_uuid();
3497
3656
 
3657
+ // src/utils/deviceInfo.ts
3658
+ async function collectDeviceInfo() {
3659
+ const result = await nativeDeviceInfo.getDeviceInfo();
3660
+ return {
3661
+ appVersion: result.appVersion,
3662
+ deviceModel: result.model,
3663
+ osVersion: result.systemVersion
3664
+ };
3665
+ }
3666
+
3498
3667
  // src/domains/session/sessionTracking.ts
3499
3668
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
3500
3669
  const distinctId = identityManager.getDistinctId();
3501
- await apiClient.trackEvent("$session_start", distinctId, {
3502
- sessionId,
3503
- timestamp: sessionStartedAt?.toISOString() ?? null
3504
- }).catch(() => {
3505
- });
3670
+ const deviceInfo = await collectDeviceInfo().catch(() => ({
3671
+ appVersion: "unknown",
3672
+ deviceModel: "unknown",
3673
+ osVersion: "unknown"
3674
+ }));
3675
+ await Promise.all([
3676
+ apiClient.startSession(
3677
+ distinctId,
3678
+ sessionId,
3679
+ void 0,
3680
+ deviceInfo.appVersion,
3681
+ deviceInfo.deviceModel,
3682
+ deviceInfo.osVersion
3683
+ ),
3684
+ apiClient.trackEvent("$session_start", distinctId, {
3685
+ sessionId,
3686
+ timestamp: sessionStartedAt?.toISOString() ?? null,
3687
+ appVersion: deviceInfo.appVersion,
3688
+ deviceModel: deviceInfo.deviceModel,
3689
+ osVersion: deviceInfo.osVersion
3690
+ }).catch(() => {
3691
+ })
3692
+ ]);
3506
3693
  }
3507
3694
  async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
3508
3695
  const distinctId = identityManager.getDistinctId();
@@ -3599,6 +3786,9 @@ var SessionManager = class {
3599
3786
  if (!this.sessionId || !this.sessionStartedAt) {
3600
3787
  return;
3601
3788
  }
3789
+ if (this.apiClient) {
3790
+ await this.apiClient.flushEventBatch();
3791
+ }
3602
3792
  await this.trackSessionEnd();
3603
3793
  this.sessionId = null;
3604
3794
  this.sessionStartedAt = null;
@@ -3661,7 +3851,7 @@ var SessionManager = class {
3661
3851
  if (this.appStateSubscription) {
3662
3852
  this.appStateSubscription.remove();
3663
3853
  }
3664
- this.appStateSubscription = import_react_native13.AppState.addEventListener("change", this.handleAppStateChange);
3854
+ this.appStateSubscription = import_react_native15.AppState.addEventListener("change", this.handleAppStateChange);
3665
3855
  }
3666
3856
  async handleForeground() {
3667
3857
  if (this.backgroundTimestamp) {
@@ -3683,6 +3873,9 @@ var SessionManager = class {
3683
3873
  async handleBackground() {
3684
3874
  this.backgroundTimestamp = Date.now();
3685
3875
  await this.trackAppBackground();
3876
+ if (this.apiClient) {
3877
+ await this.apiClient.flushEventBatch();
3878
+ }
3686
3879
  }
3687
3880
  async checkEmergencyPaywall() {
3688
3881
  if (!this.apiClient || !this.emergencyPaywallHandler) {
@@ -3731,16 +3924,26 @@ var sessionManager = new SessionManager();
3731
3924
  // src/domains/identity/InstallTracker.ts
3732
3925
  var InstallTracker = class {
3733
3926
  constructor(debug = false, advertisingIdManager) {
3927
+ this._trackingInProgress = false;
3734
3928
  this.debug = debug;
3735
3929
  this.advertisingIdManager = advertisingIdManager ?? new AdvertisingIdManager();
3736
3930
  }
3737
3931
  async trackIfNeeded(apiClient, requestATT = false) {
3932
+ if (this._trackingInProgress) return;
3933
+ this._trackingInProgress = true;
3934
+ try {
3935
+ await this._doTrack(apiClient, requestATT);
3936
+ } finally {
3937
+ this._trackingInProgress = false;
3938
+ }
3939
+ }
3940
+ async _doTrack(apiClient, requestATT) {
3738
3941
  const storage = new SecureStorage(this.debug);
3739
3942
  const alreadyTracked = await storage.get("@panel:install_tracked");
3740
3943
  if (alreadyTracked) return;
3741
3944
  let fallbackTracked = null;
3742
3945
  try {
3743
- fallbackTracked = await import_async_storage3.default.getItem("@paywallo:install_tracked");
3946
+ fallbackTracked = await nativeStorage.get("@paywallo:install_tracked");
3744
3947
  } catch {
3745
3948
  }
3746
3949
  if (fallbackTracked) return;
@@ -3748,47 +3951,47 @@ var InstallTracker = class {
3748
3951
  const sessionId = sessionManager.getSessionId();
3749
3952
  const installedAt = Date.now();
3750
3953
  if (!distinctId) return;
3751
- const deviceInfo = import_react_native_device_info2.default;
3752
3954
  let deviceData = {};
3753
3955
  try {
3754
- const screen = import_react_native14.Dimensions.get("screen");
3956
+ const screen = import_react_native16.Dimensions.get("screen");
3755
3957
  const screenWidth = Math.round(screen.width ?? 0);
3756
3958
  const screenHeight = Math.round(screen.height ?? 0);
3757
3959
  const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
3758
3960
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
3759
- const deviceModel = deviceInfo?.getModel() || "unknown";
3760
- const osVersion = deviceInfo?.getSystemVersion() || "unknown";
3761
- const appVersion = deviceInfo?.getVersion() || "unknown";
3762
- const buildNumber = deviceInfo?.getBuildNumber() || "0";
3961
+ const info = await nativeDeviceInfo.getDeviceInfo();
3962
+ const deviceModel = info.modelId || info.model || "unknown";
3963
+ const osVersion = info.systemVersion || "unknown";
3964
+ const appVersion = info.appVersion || "unknown";
3965
+ const buildNumber = info.buildNumber || "0";
3763
3966
  let carrier = "unknown";
3764
3967
  try {
3765
- carrier = await deviceInfo?.getCarrier?.() ?? "unknown";
3968
+ carrier = info.carrier ?? "unknown";
3766
3969
  } catch {
3767
3970
  }
3768
3971
  let totalDisk = 0;
3769
3972
  try {
3770
- totalDisk = Number(await deviceInfo?.getTotalDiskCapacity?.()) || 0;
3973
+ totalDisk = Number(info.totalDisk) || 0;
3771
3974
  } catch {
3772
3975
  }
3773
3976
  let freeDisk = 0;
3774
3977
  try {
3775
- freeDisk = Number(await deviceInfo?.getFreeDiskStorage?.()) || 0;
3978
+ freeDisk = Number(info.freeDisk) || 0;
3776
3979
  } catch {
3777
3980
  }
3778
3981
  let totalRam = 0;
3779
3982
  try {
3780
- totalRam = Number(await deviceInfo?.getTotalMemory?.()) || 0;
3983
+ totalRam = Number(info.totalRam) || 0;
3781
3984
  } catch {
3782
3985
  }
3783
3986
  let brand = "unknown";
3784
3987
  try {
3785
- brand = deviceInfo?.getBrand?.() ?? "unknown";
3988
+ brand = info.brand ?? "unknown";
3786
3989
  } catch {
3787
3990
  }
3788
3991
  let installerPackage = null;
3789
3992
  try {
3790
- if (import_react_native14.Platform.OS === "android") {
3791
- installerPackage = await deviceInfo?.getInstallerPackageName?.() ?? null;
3993
+ if (import_react_native16.Platform.OS === "android") {
3994
+ installerPackage = info.installerPackage ?? null;
3792
3995
  }
3793
3996
  } catch {
3794
3997
  }
@@ -3827,37 +4030,34 @@ var InstallTracker = class {
3827
4030
  });
3828
4031
  }
3829
4032
  }
3830
- try {
3831
- await apiClient.trackEvent("$app_installed", distinctId, {
3832
- installedAt,
3833
- platform: import_react_native14.Platform.OS,
3834
- ...sessionId && { sessionId },
3835
- ...deviceData,
3836
- ...effectiveAnonId && { fbAnonId: effectiveAnonId },
3837
- ...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
3838
- ...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
3839
- ...referrer.fbclid && { referrerFbclid: referrer.fbclid },
3840
- ...referrer.utmSource && { referrerUtmSource: referrer.utmSource },
3841
- ...referrer.utmMedium && { referrerUtmMedium: referrer.utmMedium },
3842
- ...referrer.utmCampaign && { referrerUtmCampaign: referrer.utmCampaign },
3843
- ...adIds.idfa && { idfa: adIds.idfa },
3844
- ...adIds.idfv && { idfv: adIds.idfv },
3845
- ...adIds.gaid && { gaid: adIds.gaid },
3846
- attStatus: adIds.attStatus
4033
+ await apiClient.trackEvent("$app_installed", distinctId, {
4034
+ installedAt,
4035
+ platform: import_react_native16.Platform.OS,
4036
+ ...sessionId && { sessionId },
4037
+ ...deviceData,
4038
+ ...effectiveAnonId && { fbAnonId: effectiveAnonId },
4039
+ ...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
4040
+ ...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
4041
+ ...referrer.fbclid && { referrerFbclid: referrer.fbclid },
4042
+ ...referrer.utmSource && { referrerUtmSource: referrer.utmSource },
4043
+ ...referrer.utmMedium && { referrerUtmMedium: referrer.utmMedium },
4044
+ ...referrer.utmCampaign && { referrerUtmCampaign: referrer.utmCampaign },
4045
+ ...adIds.idfa && { idfa: adIds.idfa },
4046
+ ...adIds.idfv && { idfv: adIds.idfv },
4047
+ ...adIds.gaid && { gaid: adIds.gaid },
4048
+ attStatus: adIds.attStatus
4049
+ });
4050
+ if (import_react_native16.Platform.OS === "ios") {
4051
+ void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId, installedAt).catch(() => {
3847
4052
  });
3848
- if (import_react_native14.Platform.OS === "ios") {
3849
- void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId).catch(() => {
3850
- });
3851
- }
3852
- await storage.set("@panel:install_tracked", String(installedAt));
3853
- try {
3854
- await import_async_storage3.default.setItem("@paywallo:install_tracked", String(installedAt));
3855
- } catch {
3856
- }
4053
+ }
4054
+ await storage.set("@panel:install_tracked", String(installedAt));
4055
+ try {
4056
+ await nativeStorage.set("@paywallo:install_tracked", String(installedAt));
3857
4057
  } catch {
3858
4058
  }
3859
4059
  }
3860
- async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
4060
+ async callDeferredMatch(apiClient, deviceData, adIds, anonId, installedAt) {
3861
4061
  const storage = new SecureStorage(this.debug);
3862
4062
  const alreadyMatched = await storage.get("@panel:deferred_match_done");
3863
4063
  if (alreadyMatched) return;
@@ -3865,7 +4065,7 @@ var InstallTracker = class {
3865
4065
  const timer = setTimeout(() => controller.abort(), 5e3);
3866
4066
  try {
3867
4067
  const baseUrl = apiClient.getBaseUrl();
3868
- const appKey = apiClient.appKey;
4068
+ const appKey = apiClient.getAppKey();
3869
4069
  await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
3870
4070
  method: "POST",
3871
4071
  headers: { "Content-Type": "application/json", "X-App-Key": appKey },
@@ -3877,7 +4077,8 @@ var InstallTracker = class {
3877
4077
  screenHeight: deviceData.screenHeight,
3878
4078
  timezone: deviceData.timezone,
3879
4079
  locale: deviceData.locale,
3880
- fbAnonId: anonId
4080
+ fbAnonId: anonId,
4081
+ installTimestamp: new Date(installedAt).toISOString()
3881
4082
  }),
3882
4083
  signal: controller.signal
3883
4084
  });
@@ -4002,7 +4203,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4002
4203
  error: error instanceof Error ? error.message : String(error)
4003
4204
  });
4004
4205
  } catch (err) {
4005
- if (config.debug) console.warn("[Paywallo] reportInitFailure error", err);
4206
+ if (config.debug) console.error("[Paywallo] reportInitFailure error", err);
4006
4207
  }
4007
4208
  }
4008
4209
  reportInitSuccess(config, attempts) {
@@ -4011,7 +4212,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4011
4212
  void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
4012
4213
  }
4013
4214
  } catch (err) {
4014
- if (config.debug) console.warn("[Paywallo] reportInitSuccess error", err);
4215
+ if (config.debug) console.error("[Paywallo] reportInitSuccess error", err);
4015
4216
  }
4016
4217
  }
4017
4218
  async resolveSessionFlags(keys, apiClient, distinctId) {
@@ -4038,7 +4239,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4038
4239
  ]);
4039
4240
  this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
4040
4241
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
4041
- affiliateManager.initialize(this.apiClient, config.debug);
4042
4242
  subscriptionManager.init({
4043
4243
  serverUrl: this.apiClient.getBaseUrl(),
4044
4244
  appKey: config.appKey,
@@ -4059,18 +4259,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4059
4259
  if (config.sessionFlags && config.sessionFlags.length > 0) {
4060
4260
  await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
4061
4261
  }
4262
+ setNativeStorageDebug(config.debug ?? false);
4263
+ setNativeDeviceInfoDebug(config.debug ?? false);
4062
4264
  this.config = config;
4063
4265
  if (this.resolveReady) {
4064
4266
  this.resolveReady();
4065
4267
  this.resolveReady = null;
4066
4268
  }
4067
- if (config.debug) console.info("[Paywallo INIT] complete");
4068
- if (this.apiClient) {
4269
+ if (config.debug) console.log("[Paywallo INIT] complete");
4270
+ if (distinctId) {
4271
+ this.apiClient.getSubscriptionStatus(distinctId).then(() => {
4272
+ if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
4273
+ }).catch(() => {
4274
+ });
4275
+ }
4276
+ {
4069
4277
  const apiClientRef = this.apiClient;
4070
4278
  if (config.autoPreloadCampaign) {
4071
4279
  this.autoPreloadedPlacement = config.autoPreloadCampaign;
4072
4280
  this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
4073
- if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
4281
+ if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
4074
4282
  void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
4075
4283
  });
4076
4284
  } else {
@@ -4078,7 +4286,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4078
4286
  const placement = primary?.placement ?? primary?.paywall?.placement;
4079
4287
  if (placement) {
4080
4288
  this.autoPreloadedPlacement = placement;
4081
- if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
4289
+ if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
4082
4290
  void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
4083
4291
  });
4084
4292
  return placement;
@@ -4090,21 +4298,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4090
4298
  }
4091
4299
  async identify(options) {
4092
4300
  this.ensureInitialized();
4093
- if (this.config?.debug) console.info("[Paywallo IDENTIFY]", options);
4301
+ if (this.config?.debug) console.log("[Paywallo IDENTIFY]", options ? { hasEmail: "email" in options && !!options.email, hasProperties: "properties" in options } : void 0);
4094
4302
  await identityManager.identify(options);
4095
4303
  }
4096
4304
  async track(eventName, options) {
4097
4305
  const apiClient = this.getApiClientOrThrow();
4098
4306
  const distinctId = identityManager.getDistinctId();
4099
4307
  if (!distinctId) {
4100
- if (this.config?.debug) console.info(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
4308
+ if (this.config?.debug) console.log(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
4101
4309
  return;
4102
4310
  }
4103
4311
  if (!isValidEventName(eventName)) {
4104
4312
  const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
4105
4313
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
4106
4314
  }
4107
- if (this.config?.debug && eventName.startsWith("$purchase")) console.info(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
4315
+ if (this.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
4108
4316
  const sessionId = sessionManager.getSessionId();
4109
4317
  await apiClient.trackEvent(eventName, distinctId, {
4110
4318
  ...identityManager.getProperties(),
@@ -4163,14 +4371,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4163
4371
  try {
4164
4372
  const distinctId = identityManager.getDistinctId();
4165
4373
  if (!distinctId) {
4166
- if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
4374
+ if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
4167
4375
  return { variant: null };
4168
4376
  }
4169
4377
  const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
4170
- if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
4378
+ if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
4171
4379
  return result;
4172
4380
  } catch (error) {
4173
- if (this.config?.debug) console.warn(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
4381
+ if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
4174
4382
  if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
4175
4383
  return { variant: null };
4176
4384
  }
@@ -4203,11 +4411,11 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4203
4411
  }
4204
4412
  async presentCampaign(placement, context) {
4205
4413
  this.ensureInitialized();
4206
- if (this.config?.debug) console.info(`[Paywallo CAMPAIGN] presenting ${placement}`);
4414
+ if (this.config?.debug) console.log(`[Paywallo CAMPAIGN] presenting ${placement}`);
4207
4415
  const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
4208
4416
  if (this.config?.debug) {
4209
- if (result.error) console.warn(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
4210
- else console.info(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
4417
+ if (result.error) console.log(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
4418
+ else console.log(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
4211
4419
  }
4212
4420
  return result;
4213
4421
  }
@@ -4217,20 +4425,20 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4217
4425
  if (this.activeChecker) {
4218
4426
  const result = await this.activeChecker();
4219
4427
  const isActive2 = result === true;
4220
- if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
4428
+ if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
4221
4429
  return isActive2;
4222
4430
  }
4223
4431
  const distinctId = identityManager.getDistinctId();
4224
4432
  if (!distinctId || !this.apiClient) {
4225
- if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
4433
+ if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
4226
4434
  return false;
4227
4435
  }
4228
4436
  const status = await this.apiClient.getSubscriptionStatus(distinctId);
4229
4437
  const isActive = status.hasActiveSubscription === true;
4230
- if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (server):", isActive);
4438
+ if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (server):", isActive);
4231
4439
  return isActive;
4232
4440
  } catch (error) {
4233
- if (this.config?.debug) console.warn("[Paywallo SUB] hasActiveSubscription error", error);
4441
+ if (this.config?.debug) console.error("[Paywallo SUB] hasActiveSubscription error", error);
4234
4442
  if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
4235
4443
  return false;
4236
4444
  }
@@ -4251,9 +4459,9 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4251
4459
  async restorePurchases() {
4252
4460
  this.ensureInitialized();
4253
4461
  if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
4254
- if (this.config?.debug) console.info("[Paywallo RESTORE] starting");
4462
+ if (this.config?.debug) console.log("[Paywallo RESTORE] starting");
4255
4463
  const result = await this.restoreHandler();
4256
- if (this.config?.debug) console.info("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
4464
+ if (this.config?.debug) console.log("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
4257
4465
  return result;
4258
4466
  }
4259
4467
  async reset() {
@@ -4263,6 +4471,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4263
4471
  async fullReset() {
4264
4472
  const wasDebug = this.config?.debug ?? false;
4265
4473
  await sessionManager.endSession();
4474
+ sessionManager.destroy();
4266
4475
  await identityManager.reset();
4267
4476
  await subscriptionCache.invalidateAll();
4268
4477
  queueProcessor.dispose();
@@ -4271,6 +4480,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4271
4480
  campaignGateService.clearPreloaded();
4272
4481
  this.cleanupNetworkRecovery();
4273
4482
  this.sessionFlagsMap.clear();
4483
+ this.apiClient?.dispose();
4274
4484
  this.apiClient = null;
4275
4485
  this.config = null;
4276
4486
  this.initPromise = null;
@@ -4349,6 +4559,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4349
4559
  registerEmergencyPaywallHandler(h) {
4350
4560
  sessionManager.registerEmergencyPaywallHandler(h);
4351
4561
  }
4562
+ async getEmergencyPaywall() {
4563
+ if (!this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized");
4564
+ return this.apiClient.getEmergencyPaywall();
4565
+ }
4352
4566
  async requireSubscription(paywallPlacement) {
4353
4567
  this.ensureInitialized();
4354
4568
  if (await this.hasActiveSubscription()) return true;
@@ -4603,6 +4817,7 @@ function usePurchase() {
4603
4817
 
4604
4818
  // src/hooks/useSubscription.ts
4605
4819
  var import_react13 = require("react");
4820
+ var EMPTY_ENTITLEMENTS = [];
4606
4821
  function useSubscription() {
4607
4822
  const [status, setStatus] = (0, import_react13.useState)(null);
4608
4823
  const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
@@ -4638,7 +4853,8 @@ function useSubscription() {
4638
4853
  setStatus(s);
4639
4854
  setIsLoading(false);
4640
4855
  }).catch((err) => {
4641
- setError(err instanceof Error ? err : new Error(String(err)));
4856
+ const e = err instanceof PurchaseError ? err : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, String(err));
4857
+ setError(e);
4642
4858
  setIsLoading(false);
4643
4859
  });
4644
4860
  const removeListener = subscriptionManager.addListener((newStatus) => {
@@ -4653,7 +4869,7 @@ function useSubscription() {
4653
4869
  isActive: status?.hasActiveSubscription ?? false,
4654
4870
  isLoading,
4655
4871
  error,
4656
- entitlements: status?.entitlements ?? [],
4872
+ entitlements: status?.entitlements ?? EMPTY_ENTITLEMENTS,
4657
4873
  refresh,
4658
4874
  restore
4659
4875
  };
@@ -4661,7 +4877,7 @@ function useSubscription() {
4661
4877
 
4662
4878
  // src/PaywalloProvider.tsx
4663
4879
  var import_react18 = require("react");
4664
- var import_react_native15 = require("react-native");
4880
+ var import_react_native17 = require("react-native");
4665
4881
 
4666
4882
  // src/hooks/useCampaignPreload.ts
4667
4883
  var import_react14 = require("react");
@@ -4785,7 +5001,7 @@ function useCampaignPreload() {
4785
5001
  campaignId: campaign.campaignId,
4786
5002
  variantKey: campaign.variantKey
4787
5003
  });
4788
- if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
5004
+ if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
4789
5005
  return { success: true };
4790
5006
  } catch (error) {
4791
5007
  return {
@@ -4820,7 +5036,7 @@ function useProductLoader() {
4820
5036
  for (const p of loaded) map.set(p.productId, p);
4821
5037
  setProducts(map);
4822
5038
  } catch (err) {
4823
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] refreshProducts failed", err);
5039
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] refreshProducts failed", err);
4824
5040
  } finally {
4825
5041
  setIsLoadingProducts(false);
4826
5042
  }
@@ -4860,7 +5076,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4860
5076
  clearPreloadedWebView();
4861
5077
  resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
4862
5078
  } catch (error) {
4863
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded purchase failed", error);
5079
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded purchase failed", error);
4864
5080
  setShowingPreloadedWebView(false);
4865
5081
  clearPreloadedWebView();
4866
5082
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
@@ -4881,7 +5097,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4881
5097
  clearPreloadedWebView();
4882
5098
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
4883
5099
  } catch (error) {
4884
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded restore failed", error);
5100
+ if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded restore failed", error);
4885
5101
  setShowingPreloadedWebView(false);
4886
5102
  clearPreloadedWebView();
4887
5103
  resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
@@ -4893,12 +5109,29 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4893
5109
  // src/hooks/useSubscriptionSync.ts
4894
5110
  var import_react17 = require("react");
4895
5111
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
4896
- var ACTIVE_STATUSES = ["active", "grace_period"];
5112
+ var ACTIVE_STATUSES = ["active", "in_grace_period"];
5113
+ var PUBLIC_TO_DOMAIN_STATUS = {
5114
+ active: "active",
5115
+ expired: "expired",
5116
+ in_billing_retry: "billing_retry",
5117
+ in_grace_period: "grace_period",
5118
+ revoked: "unknown",
5119
+ cancelled: "canceled"
5120
+ };
5121
+ var DOMAIN_TO_PUBLIC_STATUS = {
5122
+ active: "active",
5123
+ expired: "expired",
5124
+ billing_retry: "in_billing_retry",
5125
+ grace_period: "in_grace_period",
5126
+ canceled: "cancelled",
5127
+ paused: "expired",
5128
+ unknown: "expired"
5129
+ };
4897
5130
  function toDomainSubscriptionInfo(sub) {
4898
5131
  return {
4899
5132
  id: "",
4900
5133
  productId: sub.productId,
4901
- status: sub.status,
5134
+ status: PUBLIC_TO_DOMAIN_STATUS[sub.status] ?? "unknown",
4902
5135
  expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
4903
5136
  originalPurchaseDate: /* @__PURE__ */ new Date(0),
4904
5137
  latestPurchaseDate: /* @__PURE__ */ new Date(0),
@@ -4912,7 +5145,7 @@ function toDomainResponse(status) {
4912
5145
  return {
4913
5146
  hasActiveSubscription: status.hasActiveSubscription,
4914
5147
  subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
4915
- entitlements: []
5148
+ entitlements: status.entitlements ?? []
4916
5149
  };
4917
5150
  }
4918
5151
  function isSubscriptionStillActive(sub) {
@@ -4921,6 +5154,10 @@ function isSubscriptionStillActive(sub) {
4921
5154
  if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
4922
5155
  return true;
4923
5156
  }
5157
+ function subscriptionInfoToSub(s) {
5158
+ const status = DOMAIN_TO_PUBLIC_STATUS[s.status] ?? "expired";
5159
+ return { productId: s.productId, status, expiresAt: s.expiresAt ?? null, platform: s.platform, autoRenewEnabled: s.willRenew, inGracePeriod: status === "in_grace_period" };
5160
+ }
4924
5161
  async function resolveOfflineFromCache(distinctId) {
4925
5162
  try {
4926
5163
  const cached = await subscriptionCache.get(distinctId);
@@ -4961,23 +5198,12 @@ function useSubscriptionSync() {
4961
5198
  const distinctId = PaywalloClient.getDistinctId();
4962
5199
  if (!apiClient || !distinctId) return false;
4963
5200
  const cached = cacheRef.current;
4964
- if (cached && cached.expiresAt > Date.now()) {
4965
- return isActiveCacheEntry(cached);
4966
- }
5201
+ if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
4967
5202
  try {
4968
5203
  const persisted = await subscriptionCache.get(distinctId);
4969
5204
  if (persisted && !persisted.isStale) {
4970
- const persistedSub = persisted.data.subscription;
4971
- const sub = persistedSub ? {
4972
- productId: persistedSub.productId,
4973
- status: persistedSub.status,
4974
- expiresAt: persistedSub.expiresAt ?? null,
4975
- platform: persistedSub.platform,
4976
- autoRenewEnabled: persistedSub.willRenew,
4977
- inGracePeriod: persistedSub.status === "grace_period"
4978
- } : null;
4979
- const stillActive = isSubscriptionStillActive(sub);
4980
- if (stillActive) {
5205
+ const sub = persisted.data.subscription ? subscriptionInfoToSub(persisted.data.subscription) : null;
5206
+ if (isSubscriptionStillActive(sub)) {
4981
5207
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4982
5208
  setSubscription(sub);
4983
5209
  return true;
@@ -4986,17 +5212,14 @@ function useSubscriptionSync() {
4986
5212
  } catch {
4987
5213
  }
4988
5214
  try {
4989
- const status = await apiClient.getSubscriptionStatus(distinctId);
4990
- const sub = status.subscription ? {
4991
- ...status.subscription,
4992
- expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null
4993
- } : null;
5215
+ const apiStatus = await apiClient.getSubscriptionStatus(distinctId);
5216
+ const sub = apiStatus.subscription ? { ...apiStatus.subscription, expiresAt: apiStatus.subscription.expiresAt ? new Date(apiStatus.subscription.expiresAt) : null } : null;
4994
5217
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4995
5218
  setSubscription(sub);
4996
- void subscriptionCache.set(distinctId, toDomainResponse(status));
4997
- return status.hasActiveSubscription === true;
5219
+ void subscriptionCache.set(distinctId, toDomainResponse(apiStatus));
5220
+ return apiStatus.hasActiveSubscription === true;
4998
5221
  } catch {
4999
- return await resolveOfflineFromCache(distinctId);
5222
+ return resolveOfflineFromCache(distinctId);
5000
5223
  }
5001
5224
  }, []);
5002
5225
  const getSubscription = (0, import_react17.useCallback)(async () => {
@@ -5011,16 +5234,7 @@ function useSubscriptionSync() {
5011
5234
  try {
5012
5235
  const persisted = await subscriptionCache.get(distinctId);
5013
5236
  if (!persisted?.data.subscription) return null;
5014
- const s = persisted.data.subscription;
5015
- const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
5016
- return {
5017
- productId: s.productId,
5018
- status: normalisedStatus,
5019
- expiresAt: s.expiresAt ?? null,
5020
- platform: s.platform,
5021
- autoRenewEnabled: s.willRenew,
5022
- inGracePeriod: normalisedStatus === "in_grace_period"
5023
- };
5237
+ return subscriptionInfoToSub(persisted.data.subscription);
5024
5238
  } catch {
5025
5239
  return null;
5026
5240
  }
@@ -5042,6 +5256,8 @@ function PaywalloProvider({ children, config }) {
5042
5256
  const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
5043
5257
  const autoPreloadTriggeredRef = (0, import_react18.useRef)(false);
5044
5258
  const preloadedWebViewRef = (0, import_react18.useRef)(preloadedWebView);
5259
+ const pollTimerRef = (0, import_react18.useRef)(null);
5260
+ const pollCancelledRef = (0, import_react18.useRef)(false);
5045
5261
  (0, import_react18.useEffect)(() => {
5046
5262
  preloadedWebViewRef.current = preloadedWebView;
5047
5263
  }, [preloadedWebView]);
@@ -5050,21 +5266,21 @@ function PaywalloProvider({ children, config }) {
5050
5266
  });
5051
5267
  }, []);
5052
5268
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
5053
- const SCREEN_HEIGHT3 = (0, import_react18.useMemo)(() => import_react_native15.Dimensions.get("window").height, []);
5054
- const slideAnim = (0, import_react18.useRef)(new import_react_native15.Animated.Value(SCREEN_HEIGHT3)).current;
5269
+ const SCREEN_HEIGHT = (0, import_react18.useMemo)(() => import_react_native17.Dimensions.get("window").height, []);
5270
+ const slideAnim = (0, import_react18.useRef)(new import_react_native17.Animated.Value(SCREEN_HEIGHT)).current;
5055
5271
  const handlePreloadedWebViewReady = (0, import_react18.useCallback)(() => {
5056
5272
  if (PaywalloClient.getConfig()?.debug) {
5057
- console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
5273
+ console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
5058
5274
  }
5059
5275
  baseHandlePreloadedWebViewReady();
5060
- }, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
5276
+ }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
5061
5277
  const handlePreloadedClose = (0, import_react18.useCallback)(() => {
5062
5278
  const placement = preloadedWebView?.placement;
5063
- 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(() => {
5279
+ import_react_native17.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native17.Easing.in(import_react_native17.Easing.cubic), useNativeDriver: true }).start(() => {
5064
5280
  baseHandlePreloadedClose();
5065
5281
  if (placement) triggerRepreload(placement);
5066
5282
  });
5067
- }, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
5283
+ }, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT]);
5068
5284
  const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
5069
5285
  preloadedWebView,
5070
5286
  resolvePreloadedPaywall,
@@ -5082,21 +5298,28 @@ function PaywalloProvider({ children, config }) {
5082
5298
  setIsPreloadPurchasing(false);
5083
5299
  }
5084
5300
  }, [rawPreloadedPurchase]);
5301
+ const configRef = (0, import_react18.useRef)(config);
5085
5302
  (0, import_react18.useEffect)(() => {
5303
+ let mounted = true;
5086
5304
  initLocalization({ detectDevice: true });
5087
- PaywalloClient.init(config).then(async () => {
5305
+ PaywalloClient.init(configRef.current).then(async () => {
5306
+ if (!mounted) return;
5088
5307
  setDistinctId(PaywalloClient.getDistinctId());
5089
5308
  setIsInitialized(true);
5090
5309
  if (!autoPreloadTriggeredRef.current) {
5091
5310
  autoPreloadTriggeredRef.current = true;
5092
5311
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
5093
- if (placement) void preloadCampaign(placement).catch(() => {
5312
+ if (mounted && placement) void preloadCampaign(placement).catch(() => {
5094
5313
  });
5095
5314
  }
5096
5315
  }).catch((err) => {
5316
+ if (!mounted) return;
5097
5317
  setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
5098
5318
  });
5099
- }, [config, preloadCampaign]);
5319
+ return () => {
5320
+ mounted = false;
5321
+ };
5322
+ }, [preloadCampaign]);
5100
5323
  const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
5101
5324
  const api = PaywalloClient.getApiClient();
5102
5325
  return api ? api.getPaywall(p) : null;
@@ -5197,15 +5420,17 @@ function PaywalloProvider({ children, config }) {
5197
5420
  }, [activePaywall]);
5198
5421
  const bridgePaywallPresenter = (0, import_react18.useCallback)((placement) => {
5199
5422
  if (PaywalloClient.getConfig()?.debug) {
5200
- console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
5423
+ console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
5201
5424
  }
5202
5425
  if (preloadedWebViewRef.current?.placement === placement) {
5203
5426
  if (preloadedWebViewRef.current.loaded) {
5204
5427
  return presentPreloadedWebView();
5205
5428
  }
5429
+ pollCancelledRef.current = false;
5206
5430
  return new Promise((resolve) => {
5207
5431
  let elapsed = 0;
5208
5432
  const poll = () => {
5433
+ if (pollCancelledRef.current) return;
5209
5434
  if (preloadedWebViewRef.current?.loaded) {
5210
5435
  resolve(presentPreloadedWebView());
5211
5436
  return;
@@ -5215,9 +5440,9 @@ function PaywalloProvider({ children, config }) {
5215
5440
  resolve(presentCampaign(placement));
5216
5441
  return;
5217
5442
  }
5218
- setTimeout(poll, 100);
5443
+ pollTimerRef.current = setTimeout(poll, 100);
5219
5444
  };
5220
- setTimeout(poll, 100);
5445
+ pollTimerRef.current = setTimeout(poll, 100);
5221
5446
  });
5222
5447
  }
5223
5448
  return presentCampaign(placement);
@@ -5235,6 +5460,18 @@ function PaywalloProvider({ children, config }) {
5235
5460
  PaywalloClient.registerActiveChecker(hasActiveSubscription);
5236
5461
  PaywalloClient.registerRestoreHandler(restorePurchases);
5237
5462
  PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
5463
+ return () => {
5464
+ pollCancelledRef.current = true;
5465
+ if (pollTimerRef.current !== null) {
5466
+ clearTimeout(pollTimerRef.current);
5467
+ pollTimerRef.current = null;
5468
+ }
5469
+ PaywalloClient.registerPaywallPresenter(null);
5470
+ PaywalloClient.registerSubscriptionGetter(null);
5471
+ PaywalloClient.registerActiveChecker(null);
5472
+ PaywalloClient.registerRestoreHandler(null);
5473
+ PaywalloClient.registerEmergencyPaywallHandler(null);
5474
+ };
5238
5475
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
5239
5476
  const isReady = isInitialized && !isLoadingProducts;
5240
5477
  const noOp = (0, import_react18.useCallback)(() => {
@@ -5275,10 +5512,10 @@ function PaywalloProvider({ children, config }) {
5275
5512
  ]);
5276
5513
  (0, import_react18.useLayoutEffect)(() => {
5277
5514
  if (showingPreloadedWebView) {
5278
- slideAnim.setValue(SCREEN_HEIGHT3);
5279
- import_react_native15.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native15.Easing.out(import_react_native15.Easing.cubic), useNativeDriver: true }).start();
5515
+ slideAnim.setValue(SCREEN_HEIGHT);
5516
+ import_react_native17.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native17.Easing.out(import_react_native17.Easing.cubic), useNativeDriver: true }).start();
5280
5517
  }
5281
- }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
5518
+ }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
5282
5519
  const preloadStyle = [
5283
5520
  styles3.preloadOverlay,
5284
5521
  showingPreloadedWebView ? styles3.visible : styles3.hidden,
@@ -5286,7 +5523,7 @@ function PaywalloProvider({ children, config }) {
5286
5523
  ];
5287
5524
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
5288
5525
  children,
5289
- /* @__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)(
5526
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native17.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5290
5527
  PaywallWebView,
5291
5528
  {
5292
5529
  paywallId: preloadedWebView.paywallId,
@@ -5316,7 +5553,7 @@ function PaywalloProvider({ children, config }) {
5316
5553
  ) })
5317
5554
  ] });
5318
5555
  }
5319
- var styles3 = import_react_native15.StyleSheet.create({
5556
+ var styles3 = import_react_native17.StyleSheet.create({
5320
5557
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
5321
5558
  hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
5322
5559
  visible: { zIndex: 9999, opacity: 1 }
@@ -5445,10 +5682,7 @@ var Paywallo = PaywalloClient;
5445
5682
  var index_default = Paywallo;
5446
5683
  // Annotate the CommonJS export names for ESM import in node:
5447
5684
  0 && (module.exports = {
5448
- AFFILIATE_ERROR_CODES,
5449
5685
  ANALYTICS_ERROR_CODES,
5450
- AffiliateError,
5451
- AffiliateManager,
5452
5686
  AnalyticsError,
5453
5687
  ApiClient,
5454
5688
  CAMPAIGN_ERROR_CODES,
@@ -5466,7 +5700,6 @@ var index_default = Paywallo;
5466
5700
  PaywallError,
5467
5701
  PaywallModal,
5468
5702
  Paywallo,
5469
- PaywalloAffiliate,
5470
5703
  PaywalloClient,
5471
5704
  PaywalloContext,
5472
5705
  PaywalloError,
@@ -5476,7 +5709,6 @@ var index_default = Paywallo;
5476
5709
  SessionError,
5477
5710
  SessionManager,
5478
5711
  SubscriptionCache,
5479
- affiliateManager,
5480
5712
  createPurchaseError,
5481
5713
  detectDeviceLanguage,
5482
5714
  getCurrentLanguage,