@virex-tech/paywallo-sdk 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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,
@@ -308,6 +303,7 @@ var SecureStorage = class {
308
303
  var secureStorage = new SecureStorage();
309
304
 
310
305
  // src/domains/identity/IdentityManager.ts
306
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
311
307
  var DEVICE_ID_KEY = "device_id";
312
308
  var ANON_ID_KEY = "anon_id";
313
309
  var USER_EMAIL_KEY = "user_email";
@@ -364,8 +360,15 @@ var IdentityManager = class {
364
360
  }
365
361
  }
366
362
  if (email) {
367
- this.email = email;
368
- await this.secureStorage.set(USER_EMAIL_KEY, email);
363
+ if (!EMAIL_REGEX.test(email)) {
364
+ if (this.debug) {
365
+ console.warn("[Paywallo] Invalid email format, skipping email field");
366
+ }
367
+ email = void 0;
368
+ } else {
369
+ this.email = email;
370
+ await this.secureStorage.set(USER_EMAIL_KEY, email);
371
+ }
369
372
  }
370
373
  if (properties) {
371
374
  this.properties = { ...this.properties, ...properties };
@@ -696,17 +699,25 @@ var IAPService = class {
696
699
  error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
697
700
  };
698
701
  }
702
+ const debug = PaywalloClient.getConfig()?.debug;
703
+ if (debug) console.info(`[Paywallo PURCHASE] starting ${productId}`);
699
704
  try {
700
705
  const purchase = await nativeStoreKit.purchase(productId);
701
706
  if (!purchase) {
707
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} cancelled`);
702
708
  return { success: false };
703
709
  }
710
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
704
711
  await this.finishTransaction(purchase.transactionId);
705
- this.validateWithServer(purchase, productId, options).catch((err) => {
706
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] background validation failed", err);
712
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} finished`);
713
+ this.validateWithServer(purchase, productId, options).then(() => {
714
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} server validated`);
715
+ }).catch((err) => {
716
+ if (debug) console.warn("[Paywallo PURCHASE] background validation failed", err);
707
717
  });
708
718
  return { success: true, purchase };
709
719
  } catch (error) {
720
+ if (debug) console.warn(`[Paywallo PURCHASE] ${productId} failed`, error);
710
721
  const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
711
722
  return { success: false, error: e };
712
723
  }
@@ -1564,227 +1575,6 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1564
1575
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1565
1576
  }
1566
1577
 
1567
- // src/domains/affiliate/AffiliateError.ts
1568
- var AffiliateError = class extends PaywalloError {
1569
- constructor(code, message) {
1570
- super("affiliate", code, message);
1571
- this.name = "AffiliateError";
1572
- }
1573
- };
1574
- var AFFILIATE_ERROR_CODES = {
1575
- NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
1576
- COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
1577
- COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
1578
- BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
1579
- WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
1580
- NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
1581
- };
1582
-
1583
- // src/domains/affiliate/affiliateHttp.ts
1584
- function buildQueryString(params) {
1585
- if (!params) return "";
1586
- const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
1587
- if (entries.length === 0) return "";
1588
- return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
1589
- }
1590
- async function affiliateGet(apiClient, path, params) {
1591
- const response = await apiClient.get(`${path}${buildQueryString(params)}`);
1592
- return response.data;
1593
- }
1594
- async function affiliatePost(apiClient, path, body) {
1595
- const response = await apiClient.post(path, body);
1596
- return response.data;
1597
- }
1598
-
1599
- // src/domains/affiliate/affiliateDateMappers.ts
1600
- function mapRawCoupon(raw) {
1601
- return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
1602
- }
1603
- function mapRawCommission(raw) {
1604
- return { ...raw, createdAt: new Date(raw.createdAt) };
1605
- }
1606
- function mapRawWithdrawal(raw) {
1607
- return { ...raw, createdAt: new Date(raw.createdAt) };
1608
- }
1609
-
1610
- // src/domains/affiliate/AffiliateManager.ts
1611
- var AffiliateManager = class {
1612
- constructor() {
1613
- this.apiClient = null;
1614
- this.debug = false;
1615
- }
1616
- initialize(apiClient, debug = false) {
1617
- this.apiClient = apiClient;
1618
- this.debug = debug;
1619
- this.log("AffiliateManager initialized");
1620
- }
1621
- async registerCoupon(code) {
1622
- this.ensureInitialized();
1623
- const distinctId = identityManager.getDistinctId();
1624
- try {
1625
- const response = await this.post("/api/v1/affiliate/register-coupon", {
1626
- code,
1627
- distinctId
1628
- });
1629
- if (response.success && response.coupon) {
1630
- this.log("Coupon registered:", code);
1631
- return { success: true, coupon: mapRawCoupon(response.coupon) };
1632
- }
1633
- return {
1634
- success: false,
1635
- error: response.error
1636
- };
1637
- } catch (error) {
1638
- this.log("Failed to register coupon:", error);
1639
- return {
1640
- success: false,
1641
- error: error instanceof Error ? error.message : "Unknown error"
1642
- };
1643
- }
1644
- }
1645
- async applyCoupon(code) {
1646
- this.ensureInitialized();
1647
- const distinctId = identityManager.getDistinctId();
1648
- try {
1649
- const response = await this.post("/api/v1/affiliate/apply-coupon", {
1650
- code,
1651
- distinctId
1652
- });
1653
- if (response.success) {
1654
- this.log("Coupon applied:", code);
1655
- }
1656
- return response;
1657
- } catch (error) {
1658
- this.log("Failed to apply coupon:", error);
1659
- return {
1660
- success: false,
1661
- error: error instanceof Error ? error.message : "Unknown error"
1662
- };
1663
- }
1664
- }
1665
- async getMyCoupon() {
1666
- this.ensureInitialized();
1667
- const distinctId = identityManager.getDistinctId();
1668
- try {
1669
- const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
1670
- if (response.coupon) {
1671
- return { coupon: mapRawCoupon(response.coupon) };
1672
- }
1673
- return { coupon: null };
1674
- } catch (error) {
1675
- this.log("Failed to get my coupon:", error);
1676
- return { coupon: null };
1677
- }
1678
- }
1679
- async getAppliedCoupon() {
1680
- this.ensureInitialized();
1681
- const distinctId = identityManager.getDistinctId();
1682
- try {
1683
- const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
1684
- return response;
1685
- } catch (error) {
1686
- this.log("Failed to get applied coupon:", error);
1687
- return { coupon: null };
1688
- }
1689
- }
1690
- async getBalance() {
1691
- this.ensureInitialized();
1692
- const distinctId = identityManager.getDistinctId();
1693
- try {
1694
- const response = await this.get("/api/v1/affiliate/balance", {
1695
- distinctId
1696
- });
1697
- return response;
1698
- } catch (error) {
1699
- this.log("Failed to get balance:", error);
1700
- return {
1701
- total: 0,
1702
- pending: 0,
1703
- available: 0,
1704
- withdrawn: 0
1705
- };
1706
- }
1707
- }
1708
- async getCommissions() {
1709
- this.ensureInitialized();
1710
- const distinctId = identityManager.getDistinctId();
1711
- try {
1712
- const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
1713
- return response.commissions.map(mapRawCommission);
1714
- } catch (error) {
1715
- this.log("Failed to get commissions:", error);
1716
- return [];
1717
- }
1718
- }
1719
- async requestWithdrawal(amount) {
1720
- this.ensureInitialized();
1721
- const distinctId = identityManager.getDistinctId();
1722
- try {
1723
- const response = await this.post("/api/v1/affiliate/withdraw", {
1724
- amount,
1725
- distinctId
1726
- });
1727
- if (response.success && response.withdrawal) {
1728
- this.log("Withdrawal requested:", amount);
1729
- return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
1730
- }
1731
- return {
1732
- success: false,
1733
- error: response.error
1734
- };
1735
- } catch (error) {
1736
- this.log("Failed to request withdrawal:", error);
1737
- return {
1738
- success: false,
1739
- error: error instanceof Error ? error.message : "Unknown error"
1740
- };
1741
- }
1742
- }
1743
- async getWithdrawals() {
1744
- this.ensureInitialized();
1745
- const distinctId = identityManager.getDistinctId();
1746
- try {
1747
- const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
1748
- return response.withdrawals.map(mapRawWithdrawal);
1749
- } catch (error) {
1750
- this.log("Failed to get withdrawals:", error);
1751
- return [];
1752
- }
1753
- }
1754
- async get(path, params) {
1755
- this.ensureInitialized();
1756
- return affiliateGet(this.apiClient, path, params);
1757
- }
1758
- async post(path, body) {
1759
- this.ensureInitialized();
1760
- return affiliatePost(this.apiClient, path, body);
1761
- }
1762
- ensureInitialized() {
1763
- if (!this.apiClient) {
1764
- throw new AffiliateError(
1765
- AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
1766
- "AffiliateManager not initialized. Call initialize() first"
1767
- );
1768
- }
1769
- }
1770
- log(...args) {
1771
- if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
1772
- }
1773
- };
1774
- var affiliateManager = new AffiliateManager();
1775
-
1776
- // src/domains/affiliate/PaywalloAffiliate.ts
1777
- var PaywalloAffiliate = {
1778
- registerCoupon: (code) => affiliateManager.registerCoupon(code),
1779
- applyCoupon: (code) => affiliateManager.applyCoupon(code),
1780
- getMyCoupon: () => affiliateManager.getMyCoupon(),
1781
- getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
1782
- getBalance: () => affiliateManager.getBalance(),
1783
- getCommissions: () => affiliateManager.getCommissions(),
1784
- requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
1785
- getWithdrawals: () => affiliateManager.getWithdrawals()
1786
- };
1787
-
1788
1578
  // src/domains/subscription/SubscriptionCache.ts
1789
1579
  var LEGACY_CACHE_KEY = "subscription_cache";
1790
1580
  var CACHE_KEY_PREFIX = "subscription_cache:";
@@ -1894,9 +1684,6 @@ var SubscriptionCache = class {
1894
1684
  };
1895
1685
  var subscriptionCache = new SubscriptionCache();
1896
1686
 
1897
- // src/domains/subscription/SubscriptionManager.ts
1898
- var import_react_native7 = require("react-native");
1899
-
1900
1687
  // src/domains/session/SessionError.ts
1901
1688
  var SessionError = class extends PaywalloError {
1902
1689
  constructor(code, message) {
@@ -1984,11 +1771,10 @@ var SubscriptionManagerClass = class {
1984
1771
  if (!this.config) {
1985
1772
  return this.getEmptyStatus();
1986
1773
  }
1987
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
1774
+ const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
1988
1775
  if (this.userId) {
1989
- url.searchParams.set("userId", this.userId);
1776
+ url.searchParams.set("distinctId", this.userId);
1990
1777
  }
1991
- url.searchParams.set("platform", import_react_native7.Platform.OS);
1992
1778
  const response = await fetch(url.toString(), {
1993
1779
  method: "GET",
1994
1780
  headers: {
@@ -2043,7 +1829,7 @@ var SubscriptionManagerClass = class {
2043
1829
  var subscriptionManager = new SubscriptionManagerClass();
2044
1830
 
2045
1831
  // src/core/ApiClient.ts
2046
- var import_react_native9 = require("react-native");
1832
+ var import_react_native8 = require("react-native");
2047
1833
 
2048
1834
  // src/core/apiUrlUtils.ts
2049
1835
  var DEFAULT_WEB_URL = "https://paywallo.com.br";
@@ -2293,7 +2079,7 @@ var HttpClient = class {
2293
2079
  };
2294
2080
 
2295
2081
  // src/core/network/NetworkMonitor.ts
2296
- var import_react_native8 = require("react-native");
2082
+ var import_react_native7 = require("react-native");
2297
2083
 
2298
2084
  // src/core/network/types.ts
2299
2085
  var DEFAULT_NETWORK_CONFIG = {
@@ -2330,7 +2116,7 @@ var NetworkMonitorClass = class {
2330
2116
  }, this.config.checkInterval);
2331
2117
  }
2332
2118
  setupAppStateListener() {
2333
- this.appStateSubscription = import_react_native8.AppState.addEventListener("change", (state) => {
2119
+ this.appStateSubscription = import_react_native7.AppState.addEventListener("change", (state) => {
2334
2120
  if (state === "active") {
2335
2121
  void this.checkConnectivity();
2336
2122
  }
@@ -2726,9 +2512,78 @@ var QueueProcessorClass = class {
2726
2512
  this.log("Offline, skipping queue processing");
2727
2513
  return { processed: 0, failed: 0 };
2728
2514
  }
2729
- return offlineQueue.processQueue(async (item) => {
2515
+ const allItems = offlineQueue.getQueue();
2516
+ const eventItems = allItems.filter(
2517
+ (i) => i.url.includes("/api/v1/events") && !i.url.includes("/batch")
2518
+ );
2519
+ const eventItemIds = new Set(eventItems.map((i) => i.id));
2520
+ const { processed: batchProcessed, failed: batchFailed, retryIds } = await this.processEventBatches(eventItems);
2521
+ const batchResult = { processed: batchProcessed, failed: batchFailed };
2522
+ const individualResult = await offlineQueue.processQueue(async (item) => {
2523
+ if (eventItemIds.has(item.id)) {
2524
+ if (retryIds.has(item.id)) {
2525
+ return { success: false, permanent: false };
2526
+ }
2527
+ return { success: true, permanent: false };
2528
+ }
2730
2529
  return this.processItem(item);
2731
2530
  });
2531
+ this.log("Queue processing complete", {
2532
+ batchProcessed: batchResult.processed,
2533
+ batchFailed: batchResult.failed,
2534
+ individualProcessed: individualResult.processed,
2535
+ individualFailed: individualResult.failed
2536
+ });
2537
+ return {
2538
+ processed: batchResult.processed + individualResult.processed,
2539
+ failed: batchResult.failed + individualResult.failed
2540
+ };
2541
+ }
2542
+ async processEventBatches(eventItems) {
2543
+ if (eventItems.length === 0 || !this.httpClient) {
2544
+ return { processed: 0, failed: 0, retryIds: /* @__PURE__ */ new Set() };
2545
+ }
2546
+ const BATCH_SIZE = 50;
2547
+ let processed = 0;
2548
+ let failed = 0;
2549
+ const retryIds = /* @__PURE__ */ new Set();
2550
+ for (let i = 0; i < eventItems.length; i += BATCH_SIZE) {
2551
+ const batch = eventItems.slice(i, i + BATCH_SIZE);
2552
+ const bodies = batch.map((item) => item.body);
2553
+ const headers = batch[0].headers;
2554
+ this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
2555
+ const response = await this.httpClient.request("/api/v1/events/batch", {
2556
+ method: "POST",
2557
+ body: { events: bodies },
2558
+ headers,
2559
+ skipRetry: false
2560
+ });
2561
+ if (response.ok) {
2562
+ for (const item of batch) {
2563
+ await offlineQueue.removeItem(item.id);
2564
+ processed++;
2565
+ }
2566
+ this.log("Event batch processed successfully", { size: batch.length });
2567
+ } else if (response.status >= 400 && response.status < 500) {
2568
+ for (const item of batch) {
2569
+ await offlineQueue.removeItem(item.id);
2570
+ failed++;
2571
+ }
2572
+ this.log("Event batch failed permanently (4xx) - removing from queue", {
2573
+ status: response.status,
2574
+ size: batch.length
2575
+ });
2576
+ } else {
2577
+ for (const item of batch) {
2578
+ retryIds.add(item.id);
2579
+ }
2580
+ this.log("Event batch failed with server error - will retry", {
2581
+ status: response.status,
2582
+ size: batch.length
2583
+ });
2584
+ }
2585
+ }
2586
+ return { processed, failed, retryIds };
2732
2587
  }
2733
2588
  async processItem(item) {
2734
2589
  if (!this.httpClient) {
@@ -2879,10 +2734,14 @@ var ApiCache = class {
2879
2734
  };
2880
2735
 
2881
2736
  // src/core/ApiClient.ts
2882
- var SDK_VERSION = "1.4.0";
2737
+ var SDK_VERSION = "1.5.0";
2883
2738
  var _ApiClient = class _ApiClient {
2884
2739
  constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2885
2740
  this.cache = new ApiCache();
2741
+ this.eventBatch = [];
2742
+ this.batchTimer = null;
2743
+ this.BATCH_MAX_SIZE = 10;
2744
+ this.BATCH_FLUSH_MS = 5e3;
2886
2745
  this.appKey = appKey;
2887
2746
  this.baseUrl = apiUrl;
2888
2747
  this.webUrl = deriveWebUrl(this.baseUrl);
@@ -2935,30 +2794,79 @@ var _ApiClient = class _ApiClient {
2935
2794
  message,
2936
2795
  context,
2937
2796
  sdkVersion: SDK_VERSION,
2938
- platform: import_react_native9.Platform.OS
2797
+ platform: import_react_native8.Platform.OS
2939
2798
  }, true);
2940
2799
  } catch (err) {
2941
2800
  if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
2942
2801
  }
2943
2802
  }
2944
2803
  async trackEvent(eventName, distinctId, properties, timestamp) {
2945
- await this.postWithQueue("/api/v1/events", {
2804
+ const EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
2805
+ if (!EVENT_NAME_REGEX.test(eventName)) {
2806
+ if (this.debug) {
2807
+ console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
2808
+ }
2809
+ }
2810
+ const event = {
2946
2811
  eventName,
2947
2812
  distinctId,
2948
- properties: { ...properties, platform: import_react_native9.Platform.OS },
2813
+ properties: { ...properties, platform: import_react_native8.Platform.OS },
2949
2814
  timestamp: timestamp ?? Date.now(),
2950
2815
  environment: this.environment.toLowerCase()
2951
- }, `event:${eventName}`);
2816
+ };
2817
+ this.eventBatch.push(event);
2818
+ if (this.eventBatch.length >= this.BATCH_MAX_SIZE) {
2819
+ if (this.batchTimer !== null) {
2820
+ clearTimeout(this.batchTimer);
2821
+ this.batchTimer = null;
2822
+ }
2823
+ await this.flushEventBatch();
2824
+ return;
2825
+ }
2826
+ if (this.batchTimer !== null) {
2827
+ clearTimeout(this.batchTimer);
2828
+ }
2829
+ this.batchTimer = setTimeout(() => {
2830
+ this.batchTimer = null;
2831
+ void this.flushEventBatch();
2832
+ }, this.BATCH_FLUSH_MS);
2833
+ }
2834
+ async flushEventBatch() {
2835
+ if (this.eventBatch.length === 0) return;
2836
+ const batch = this.eventBatch.splice(0, this.eventBatch.length);
2837
+ if (this.batchTimer !== null) {
2838
+ clearTimeout(this.batchTimer);
2839
+ this.batchTimer = null;
2840
+ }
2841
+ try {
2842
+ await this.postWithQueue(
2843
+ "/api/v1/events/batch",
2844
+ { events: batch },
2845
+ `event_batch:${batch.length}`
2846
+ );
2847
+ } catch (error) {
2848
+ this.log("Batch send failed, falling back to individual sends:", error);
2849
+ for (const event of batch) {
2850
+ await this.postWithQueue(
2851
+ "/api/v1/events",
2852
+ event,
2853
+ `event:${event.eventName}`
2854
+ );
2855
+ }
2856
+ }
2952
2857
  }
2953
2858
  async identify(distinctId, properties, email, deviceId) {
2954
- await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native9.Platform.OS, ...deviceId && { deviceId } }, "identify");
2859
+ await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native8.Platform.OS, ...deviceId && { deviceId } }, "identify");
2955
2860
  }
2956
- async startSession(distinctId, sessionId, platform) {
2861
+ async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
2957
2862
  await this.postWithQueue("/api/v1/sessions/start", {
2958
2863
  distinctId,
2959
2864
  sessionId,
2960
- platform: platform ?? import_react_native9.Platform.OS,
2961
- environment: this.environment.toLowerCase()
2865
+ platform: platform ?? import_react_native8.Platform.OS,
2866
+ environment: this.environment.toLowerCase(),
2867
+ appVersion: appVersion ?? "unknown",
2868
+ deviceModel: deviceModel ?? "unknown",
2869
+ osVersion: osVersion ?? String(import_react_native8.Platform.Version)
2962
2870
  }, `session.start:${sessionId}`);
2963
2871
  return { success: true };
2964
2872
  }
@@ -2978,6 +2886,7 @@ var _ApiClient = class _ApiClient {
2978
2886
  try {
2979
2887
  const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
2980
2888
  if (res.status === 404) {
2889
+ this.log("Flag not found (404):", flagKey);
2981
2890
  const v = { variant: null };
2982
2891
  this.cache.setVariant(key, v, this.cache.nullTTL);
2983
2892
  return v;
@@ -3004,6 +2913,7 @@ var _ApiClient = class _ApiClient {
3004
2913
  try {
3005
2914
  const res = await this.get(`/api/v1/paywalls/${placement}`);
3006
2915
  if (res.status === 404) {
2916
+ this.log("Paywall not found (404):", placement);
3007
2917
  this.cache.setPaywall(placement, null, this.cache.nullTTL);
3008
2918
  return null;
3009
2919
  }
@@ -3055,6 +2965,7 @@ var _ApiClient = class _ApiClient {
3055
2965
  try {
3056
2966
  const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
3057
2967
  if (res.status === 404) {
2968
+ this.log("Primary campaign not found (404)");
3058
2969
  this.cache.setCampaign(key, null, this.cache.nullTTL);
3059
2970
  return null;
3060
2971
  }
@@ -3105,7 +3016,7 @@ var _ApiClient = class _ApiClient {
3105
3016
  headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
3106
3017
  });
3107
3018
  if (!res.ok) {
3108
- this.log("Non-OK response for evaluateFlags:", res.status);
3019
+ this.log("evaluateFlags failed:", res.status, keys);
3109
3020
  return {};
3110
3021
  }
3111
3022
  const raw = res.data;
@@ -3263,7 +3174,7 @@ var CampaignGateService = class {
3263
3174
  const isActive = await hasActive();
3264
3175
  if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3265
3176
  if (isActive) return true;
3266
- const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
3177
+ const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
3267
3178
  return r.purchased || r.restored;
3268
3179
  }
3269
3180
  async preloadCampaign(apiClient, placement, context) {
@@ -3301,7 +3212,7 @@ var CampaignGateService = class {
3301
3212
  var campaignGateService = new CampaignGateService();
3302
3213
 
3303
3214
  // src/domains/identity/AdvertisingIdManager.ts
3304
- var import_react_native10 = require("react-native");
3215
+ var import_react_native9 = require("react-native");
3305
3216
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
3306
3217
  var AdvertisingIdManager = class {
3307
3218
  constructor() {
@@ -3312,7 +3223,7 @@ var AdvertisingIdManager = class {
3312
3223
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
3313
3224
  try {
3314
3225
  const tracking = await import("expo-tracking-transparency");
3315
- if (import_react_native10.Platform.OS === "ios") {
3226
+ if (import_react_native9.Platform.OS === "ios") {
3316
3227
  if (!tracking.isAvailable()) {
3317
3228
  const idfv2 = await this.collectIdfv();
3318
3229
  this.cached = { ...fallback, idfv: idfv2 };
@@ -3331,7 +3242,7 @@ var AdvertisingIdManager = class {
3331
3242
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
3332
3243
  return this.cached;
3333
3244
  }
3334
- if (import_react_native10.Platform.OS === "android") {
3245
+ if (import_react_native9.Platform.OS === "android") {
3335
3246
  const androidStatus = await tracking.getTrackingPermissionsAsync();
3336
3247
  const optedIn = androidStatus.granted;
3337
3248
  const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
@@ -3367,9 +3278,9 @@ var import_async_storage3 = __toESM(require("@react-native-async-storage/async-s
3367
3278
  var import_react_native_device_info2 = __toESM(require("react-native-device-info"));
3368
3279
 
3369
3280
  // src/domains/identity/MetaBridge.ts
3370
- var import_react_native11 = require("react-native");
3281
+ var import_react_native10 = require("react-native");
3371
3282
  var TIMEOUT_MS = 2e3;
3372
- var NativeBridge = import_react_native11.NativeModules.PaywalloFBBridge ?? null;
3283
+ var NativeBridge = import_react_native10.NativeModules.PaywalloFBBridge ?? null;
3373
3284
  var MetaBridge = class {
3374
3285
  async getAnonymousID() {
3375
3286
  if (!NativeBridge) return null;
@@ -3417,11 +3328,11 @@ var MetaBridge = class {
3417
3328
  var metaBridge = new MetaBridge();
3418
3329
 
3419
3330
  // src/domains/identity/InstallReferrerManager.ts
3420
- var import_react_native12 = require("react-native");
3331
+ var import_react_native11 = require("react-native");
3421
3332
  var REFERRER_TIMEOUT_MS = 5e3;
3422
3333
  var InstallReferrerManager = class {
3423
3334
  async getReferrer() {
3424
- if (import_react_native12.Platform.OS !== "android") {
3335
+ if (import_react_native11.Platform.OS !== "android") {
3425
3336
  return this.emptyResult();
3426
3337
  }
3427
3338
  let timerId;
@@ -3484,14 +3395,77 @@ var installReferrerManager = new InstallReferrerManager();
3484
3395
  var import_react_native13 = require("react-native");
3485
3396
  init_uuid();
3486
3397
 
3398
+ // src/utils/deviceInfo.ts
3399
+ var import_react_native12 = require("react-native");
3400
+ async function getAppVersion() {
3401
+ try {
3402
+ const app = await import("expo-application");
3403
+ if (app.nativeApplicationVersion) {
3404
+ return app.nativeApplicationVersion;
3405
+ }
3406
+ } catch {
3407
+ }
3408
+ try {
3409
+ const DeviceInfo2 = (await import("react-native-device-info")).default;
3410
+ const v = DeviceInfo2.getVersion();
3411
+ if (v) return v;
3412
+ } catch {
3413
+ }
3414
+ return "unknown";
3415
+ }
3416
+ async function getDeviceModel() {
3417
+ try {
3418
+ const DeviceInfo2 = (await import("react-native-device-info")).default;
3419
+ const model = DeviceInfo2.getModel();
3420
+ if (model) return model;
3421
+ } catch {
3422
+ }
3423
+ return "unknown";
3424
+ }
3425
+ function getOsVersion() {
3426
+ const v = import_react_native12.Platform.Version;
3427
+ if (typeof v === "number") return String(v);
3428
+ if (typeof v === "string" && v.length > 0) return v;
3429
+ return "unknown";
3430
+ }
3431
+ async function collectDeviceInfo() {
3432
+ const [appVersion, deviceModel] = await Promise.all([
3433
+ getAppVersion(),
3434
+ getDeviceModel()
3435
+ ]);
3436
+ return {
3437
+ appVersion,
3438
+ deviceModel,
3439
+ osVersion: getOsVersion()
3440
+ };
3441
+ }
3442
+
3487
3443
  // src/domains/session/sessionTracking.ts
3488
3444
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
3489
3445
  const distinctId = identityManager.getDistinctId();
3490
- await apiClient.trackEvent("$session_start", distinctId, {
3491
- sessionId,
3492
- timestamp: sessionStartedAt?.toISOString() ?? null
3493
- }).catch(() => {
3494
- });
3446
+ const deviceInfo = await collectDeviceInfo().catch(() => ({
3447
+ appVersion: "unknown",
3448
+ deviceModel: "unknown",
3449
+ osVersion: "unknown"
3450
+ }));
3451
+ await Promise.all([
3452
+ apiClient.startSession(
3453
+ distinctId,
3454
+ sessionId,
3455
+ void 0,
3456
+ deviceInfo.appVersion,
3457
+ deviceInfo.deviceModel,
3458
+ deviceInfo.osVersion
3459
+ ),
3460
+ apiClient.trackEvent("$session_start", distinctId, {
3461
+ sessionId,
3462
+ timestamp: sessionStartedAt?.toISOString() ?? null,
3463
+ appVersion: deviceInfo.appVersion,
3464
+ deviceModel: deviceInfo.deviceModel,
3465
+ osVersion: deviceInfo.osVersion
3466
+ }).catch(() => {
3467
+ })
3468
+ ]);
3495
3469
  }
3496
3470
  async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
3497
3471
  const distinctId = identityManager.getDistinctId();
@@ -3588,6 +3562,9 @@ var SessionManager = class {
3588
3562
  if (!this.sessionId || !this.sessionStartedAt) {
3589
3563
  return;
3590
3564
  }
3565
+ if (this.apiClient) {
3566
+ await this.apiClient.flushEventBatch();
3567
+ }
3591
3568
  await this.trackSessionEnd();
3592
3569
  this.sessionId = null;
3593
3570
  this.sessionStartedAt = null;
@@ -3672,6 +3649,9 @@ var SessionManager = class {
3672
3649
  async handleBackground() {
3673
3650
  this.backgroundTimestamp = Date.now();
3674
3651
  await this.trackAppBackground();
3652
+ if (this.apiClient) {
3653
+ await this.apiClient.flushEventBatch();
3654
+ }
3675
3655
  }
3676
3656
  async checkEmergencyPaywall() {
3677
3657
  if (!this.apiClient || !this.emergencyPaywallHandler) {
@@ -4027,7 +4007,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4027
4007
  ]);
4028
4008
  this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
4029
4009
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
4030
- affiliateManager.initialize(this.apiClient, config.debug);
4031
4010
  subscriptionManager.init({
4032
4011
  serverUrl: this.apiClient.getBaseUrl(),
4033
4012
  appKey: config.appKey,
@@ -4054,6 +4033,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4054
4033
  this.resolveReady = null;
4055
4034
  }
4056
4035
  if (config.debug) console.info("[Paywallo INIT] complete");
4036
+ if (distinctId) {
4037
+ this.apiClient.getSubscriptionStatus(distinctId).then(() => {
4038
+ if (config.debug) console.info("[Paywallo SUB] preloaded on boot");
4039
+ }).catch(() => {
4040
+ });
4041
+ }
4057
4042
  if (this.apiClient) {
4058
4043
  const apiClientRef = this.apiClient;
4059
4044
  if (config.autoPreloadCampaign) {
@@ -4079,18 +4064,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4079
4064
  }
4080
4065
  async identify(options) {
4081
4066
  this.ensureInitialized();
4067
+ if (this.config?.debug) console.info("[Paywallo IDENTIFY]", options);
4082
4068
  await identityManager.identify(options);
4083
4069
  }
4084
4070
  async track(eventName, options) {
4085
4071
  const apiClient = this.getApiClientOrThrow();
4086
4072
  const distinctId = identityManager.getDistinctId();
4087
4073
  if (!distinctId) {
4074
+ if (this.config?.debug) console.info(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
4088
4075
  return;
4089
4076
  }
4090
4077
  if (!isValidEventName(eventName)) {
4091
4078
  const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
4092
4079
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
4093
4080
  }
4081
+ if (this.config?.debug && eventName.startsWith("$purchase")) console.info(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
4094
4082
  const sessionId = sessionManager.getSessionId();
4095
4083
  await apiClient.trackEvent(eventName, distinctId, {
4096
4084
  ...identityManager.getProperties(),
@@ -4111,14 +4099,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4111
4099
  const now = Date.now();
4112
4100
  const timeOnPreviousStep = this.lastOnboardingStepTimestamp !== null ? Math.round((now - this.lastOnboardingStepTimestamp) / 1e3) : 0;
4113
4101
  this.lastOnboardingStepTimestamp = now;
4114
- const sessionId = sessionManager.getSessionId();
4115
4102
  await apiClient.trackEvent("$onboarding_step", distinctId, {
4116
- ...identityManager.getProperties(),
4117
4103
  stepIndex: index,
4118
4104
  stepName: name,
4119
- timestamp: now,
4120
- timeOnPreviousStep,
4121
- ...sessionId && { sessionId }
4105
+ timeOnPreviousStep
4122
4106
  });
4123
4107
  }
4124
4108
  async coreAction(actionName) {
@@ -4153,10 +4137,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4153
4137
  try {
4154
4138
  const distinctId = identityManager.getDistinctId();
4155
4139
  if (!distinctId) {
4140
+ if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
4156
4141
  return { variant: null };
4157
4142
  }
4158
- return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
4143
+ const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
4144
+ if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
4145
+ return result;
4159
4146
  } catch (error) {
4147
+ if (this.config?.debug) console.warn(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
4160
4148
  if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
4161
4149
  return { variant: null };
4162
4150
  }
@@ -4189,21 +4177,34 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4189
4177
  }
4190
4178
  async presentCampaign(placement, context) {
4191
4179
  this.ensureInitialized();
4192
- return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
4180
+ if (this.config?.debug) console.info(`[Paywallo CAMPAIGN] presenting ${placement}`);
4181
+ const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
4182
+ if (this.config?.debug) {
4183
+ if (result.error) console.warn(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
4184
+ else console.info(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
4185
+ }
4186
+ return result;
4193
4187
  }
4194
4188
  async hasActiveSubscription() {
4195
4189
  this.ensureInitialized();
4196
4190
  try {
4197
4191
  if (this.activeChecker) {
4198
- return await this.activeChecker();
4192
+ const result = await this.activeChecker();
4193
+ const isActive2 = result === true;
4194
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
4195
+ return isActive2;
4199
4196
  }
4200
4197
  const distinctId = identityManager.getDistinctId();
4201
4198
  if (!distinctId || !this.apiClient) {
4199
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
4202
4200
  return false;
4203
4201
  }
4204
4202
  const status = await this.apiClient.getSubscriptionStatus(distinctId);
4205
- return status.hasActiveSubscription;
4203
+ const isActive = status.hasActiveSubscription === true;
4204
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (server):", isActive);
4205
+ return isActive;
4206
4206
  } catch (error) {
4207
+ if (this.config?.debug) console.warn("[Paywallo SUB] hasActiveSubscription error", error);
4207
4208
  if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
4208
4209
  return false;
4209
4210
  }
@@ -4224,7 +4225,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4224
4225
  async restorePurchases() {
4225
4226
  this.ensureInitialized();
4226
4227
  if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
4227
- return this.restoreHandler();
4228
+ if (this.config?.debug) console.info("[Paywallo RESTORE] starting");
4229
+ const result = await this.restoreHandler();
4230
+ if (this.config?.debug) console.info("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
4231
+ return result;
4228
4232
  }
4229
4233
  async reset() {
4230
4234
  await identityManager.reset();
@@ -4319,6 +4323,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4319
4323
  registerEmergencyPaywallHandler(h) {
4320
4324
  sessionManager.registerEmergencyPaywallHandler(h);
4321
4325
  }
4326
+ async getEmergencyPaywall() {
4327
+ if (!this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized");
4328
+ return this.apiClient.getEmergencyPaywall();
4329
+ }
4322
4330
  async requireSubscription(paywallPlacement) {
4323
4331
  this.ensureInitialized();
4324
4332
  if (await this.hasActiveSubscription()) return true;
@@ -4863,7 +4871,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4863
4871
  // src/hooks/useSubscriptionSync.ts
4864
4872
  var import_react17 = require("react");
4865
4873
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
4866
- var ACTIVE_STATUSES = ["active", "grace_period"];
4874
+ var ACTIVE_STATUSES = ["active", "in_grace_period"];
4867
4875
  function toDomainSubscriptionInfo(sub) {
4868
4876
  return {
4869
4877
  id: "",
@@ -4882,7 +4890,7 @@ function toDomainResponse(status) {
4882
4890
  return {
4883
4891
  hasActiveSubscription: status.hasActiveSubscription,
4884
4892
  subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
4885
- entitlements: []
4893
+ entitlements: status.entitlements ?? []
4886
4894
  };
4887
4895
  }
4888
4896
  function isSubscriptionStillActive(sub) {
@@ -4938,13 +4946,14 @@ function useSubscriptionSync() {
4938
4946
  const persisted = await subscriptionCache.get(distinctId);
4939
4947
  if (persisted && !persisted.isStale) {
4940
4948
  const persistedSub = persisted.data.subscription;
4949
+ const persistedStatus = persistedSub?.status;
4941
4950
  const sub = persistedSub ? {
4942
4951
  productId: persistedSub.productId,
4943
- status: persistedSub.status,
4952
+ status: persistedStatus,
4944
4953
  expiresAt: persistedSub.expiresAt ?? null,
4945
4954
  platform: persistedSub.platform,
4946
4955
  autoRenewEnabled: persistedSub.willRenew,
4947
- inGracePeriod: persistedSub.status === "grace_period"
4956
+ inGracePeriod: persistedStatus === "in_grace_period"
4948
4957
  } : null;
4949
4958
  const stillActive = isSubscriptionStillActive(sub);
4950
4959
  if (stillActive) {
@@ -4964,7 +4973,7 @@ function useSubscriptionSync() {
4964
4973
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4965
4974
  setSubscription(sub);
4966
4975
  void subscriptionCache.set(distinctId, toDomainResponse(status));
4967
- return status.hasActiveSubscription;
4976
+ return status.hasActiveSubscription === true;
4968
4977
  } catch {
4969
4978
  return await resolveOfflineFromCache(distinctId);
4970
4979
  }
@@ -4982,14 +4991,14 @@ function useSubscriptionSync() {
4982
4991
  const persisted = await subscriptionCache.get(distinctId);
4983
4992
  if (!persisted?.data.subscription) return null;
4984
4993
  const s = persisted.data.subscription;
4985
- const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
4994
+ const status = s.status;
4986
4995
  return {
4987
4996
  productId: s.productId,
4988
- status: normalisedStatus,
4997
+ status,
4989
4998
  expiresAt: s.expiresAt ?? null,
4990
4999
  platform: s.platform,
4991
5000
  autoRenewEnabled: s.willRenew,
4992
- inGracePeriod: normalisedStatus === "in_grace_period"
5001
+ inGracePeriod: status === "in_grace_period"
4993
5002
  };
4994
5003
  } catch {
4995
5004
  return null;
@@ -5415,10 +5424,7 @@ var Paywallo = PaywalloClient;
5415
5424
  var index_default = Paywallo;
5416
5425
  // Annotate the CommonJS export names for ESM import in node:
5417
5426
  0 && (module.exports = {
5418
- AFFILIATE_ERROR_CODES,
5419
5427
  ANALYTICS_ERROR_CODES,
5420
- AffiliateError,
5421
- AffiliateManager,
5422
5428
  AnalyticsError,
5423
5429
  ApiClient,
5424
5430
  CAMPAIGN_ERROR_CODES,
@@ -5436,7 +5442,6 @@ var index_default = Paywallo;
5436
5442
  PaywallError,
5437
5443
  PaywallModal,
5438
5444
  Paywallo,
5439
- PaywalloAffiliate,
5440
5445
  PaywalloClient,
5441
5446
  PaywalloContext,
5442
5447
  PaywalloError,
@@ -5446,7 +5451,6 @@ var index_default = Paywallo;
5446
5451
  SessionError,
5447
5452
  SessionManager,
5448
5453
  SubscriptionCache,
5449
- affiliateManager,
5450
5454
  createPurchaseError,
5451
5455
  detectDeviceLanguage,
5452
5456
  getCurrentLanguage,