@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.mjs CHANGED
@@ -231,6 +231,7 @@ var SecureStorage = class {
231
231
  var secureStorage = new SecureStorage();
232
232
 
233
233
  // src/domains/identity/IdentityManager.ts
234
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
234
235
  var DEVICE_ID_KEY = "device_id";
235
236
  var ANON_ID_KEY = "anon_id";
236
237
  var USER_EMAIL_KEY = "user_email";
@@ -287,8 +288,15 @@ var IdentityManager = class {
287
288
  }
288
289
  }
289
290
  if (email) {
290
- this.email = email;
291
- await this.secureStorage.set(USER_EMAIL_KEY, email);
291
+ if (!EMAIL_REGEX.test(email)) {
292
+ if (this.debug) {
293
+ console.warn("[Paywallo] Invalid email format, skipping email field");
294
+ }
295
+ email = void 0;
296
+ } else {
297
+ this.email = email;
298
+ await this.secureStorage.set(USER_EMAIL_KEY, email);
299
+ }
292
300
  }
293
301
  if (properties) {
294
302
  this.properties = { ...this.properties, ...properties };
@@ -619,17 +627,25 @@ var IAPService = class {
619
627
  error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
620
628
  };
621
629
  }
630
+ const debug = PaywalloClient.getConfig()?.debug;
631
+ if (debug) console.info(`[Paywallo PURCHASE] starting ${productId}`);
622
632
  try {
623
633
  const purchase = await nativeStoreKit.purchase(productId);
624
634
  if (!purchase) {
635
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} cancelled`);
625
636
  return { success: false };
626
637
  }
638
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
627
639
  await this.finishTransaction(purchase.transactionId);
628
- this.validateWithServer(purchase, productId, options).catch((err) => {
629
- if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] background validation failed", err);
640
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} finished`);
641
+ this.validateWithServer(purchase, productId, options).then(() => {
642
+ if (debug) console.info(`[Paywallo PURCHASE] ${productId} server validated`);
643
+ }).catch((err) => {
644
+ if (debug) console.warn("[Paywallo PURCHASE] background validation failed", err);
630
645
  });
631
646
  return { success: true, purchase };
632
647
  } catch (error) {
648
+ if (debug) console.warn(`[Paywallo PURCHASE] ${productId} failed`, error);
633
649
  const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
634
650
  return { success: false, error: e };
635
651
  }
@@ -1494,227 +1510,6 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1494
1510
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1495
1511
  }
1496
1512
 
1497
- // src/domains/affiliate/AffiliateError.ts
1498
- var AffiliateError = class extends PaywalloError {
1499
- constructor(code, message) {
1500
- super("affiliate", code, message);
1501
- this.name = "AffiliateError";
1502
- }
1503
- };
1504
- var AFFILIATE_ERROR_CODES = {
1505
- NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
1506
- COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
1507
- COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
1508
- BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
1509
- WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
1510
- NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
1511
- };
1512
-
1513
- // src/domains/affiliate/affiliateHttp.ts
1514
- function buildQueryString(params) {
1515
- if (!params) return "";
1516
- const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
1517
- if (entries.length === 0) return "";
1518
- return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
1519
- }
1520
- async function affiliateGet(apiClient, path, params) {
1521
- const response = await apiClient.get(`${path}${buildQueryString(params)}`);
1522
- return response.data;
1523
- }
1524
- async function affiliatePost(apiClient, path, body) {
1525
- const response = await apiClient.post(path, body);
1526
- return response.data;
1527
- }
1528
-
1529
- // src/domains/affiliate/affiliateDateMappers.ts
1530
- function mapRawCoupon(raw) {
1531
- return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
1532
- }
1533
- function mapRawCommission(raw) {
1534
- return { ...raw, createdAt: new Date(raw.createdAt) };
1535
- }
1536
- function mapRawWithdrawal(raw) {
1537
- return { ...raw, createdAt: new Date(raw.createdAt) };
1538
- }
1539
-
1540
- // src/domains/affiliate/AffiliateManager.ts
1541
- var AffiliateManager = class {
1542
- constructor() {
1543
- this.apiClient = null;
1544
- this.debug = false;
1545
- }
1546
- initialize(apiClient, debug = false) {
1547
- this.apiClient = apiClient;
1548
- this.debug = debug;
1549
- this.log("AffiliateManager initialized");
1550
- }
1551
- async registerCoupon(code) {
1552
- this.ensureInitialized();
1553
- const distinctId = identityManager.getDistinctId();
1554
- try {
1555
- const response = await this.post("/api/v1/affiliate/register-coupon", {
1556
- code,
1557
- distinctId
1558
- });
1559
- if (response.success && response.coupon) {
1560
- this.log("Coupon registered:", code);
1561
- return { success: true, coupon: mapRawCoupon(response.coupon) };
1562
- }
1563
- return {
1564
- success: false,
1565
- error: response.error
1566
- };
1567
- } catch (error) {
1568
- this.log("Failed to register coupon:", error);
1569
- return {
1570
- success: false,
1571
- error: error instanceof Error ? error.message : "Unknown error"
1572
- };
1573
- }
1574
- }
1575
- async applyCoupon(code) {
1576
- this.ensureInitialized();
1577
- const distinctId = identityManager.getDistinctId();
1578
- try {
1579
- const response = await this.post("/api/v1/affiliate/apply-coupon", {
1580
- code,
1581
- distinctId
1582
- });
1583
- if (response.success) {
1584
- this.log("Coupon applied:", code);
1585
- }
1586
- return response;
1587
- } catch (error) {
1588
- this.log("Failed to apply coupon:", error);
1589
- return {
1590
- success: false,
1591
- error: error instanceof Error ? error.message : "Unknown error"
1592
- };
1593
- }
1594
- }
1595
- async getMyCoupon() {
1596
- this.ensureInitialized();
1597
- const distinctId = identityManager.getDistinctId();
1598
- try {
1599
- const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
1600
- if (response.coupon) {
1601
- return { coupon: mapRawCoupon(response.coupon) };
1602
- }
1603
- return { coupon: null };
1604
- } catch (error) {
1605
- this.log("Failed to get my coupon:", error);
1606
- return { coupon: null };
1607
- }
1608
- }
1609
- async getAppliedCoupon() {
1610
- this.ensureInitialized();
1611
- const distinctId = identityManager.getDistinctId();
1612
- try {
1613
- const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
1614
- return response;
1615
- } catch (error) {
1616
- this.log("Failed to get applied coupon:", error);
1617
- return { coupon: null };
1618
- }
1619
- }
1620
- async getBalance() {
1621
- this.ensureInitialized();
1622
- const distinctId = identityManager.getDistinctId();
1623
- try {
1624
- const response = await this.get("/api/v1/affiliate/balance", {
1625
- distinctId
1626
- });
1627
- return response;
1628
- } catch (error) {
1629
- this.log("Failed to get balance:", error);
1630
- return {
1631
- total: 0,
1632
- pending: 0,
1633
- available: 0,
1634
- withdrawn: 0
1635
- };
1636
- }
1637
- }
1638
- async getCommissions() {
1639
- this.ensureInitialized();
1640
- const distinctId = identityManager.getDistinctId();
1641
- try {
1642
- const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
1643
- return response.commissions.map(mapRawCommission);
1644
- } catch (error) {
1645
- this.log("Failed to get commissions:", error);
1646
- return [];
1647
- }
1648
- }
1649
- async requestWithdrawal(amount) {
1650
- this.ensureInitialized();
1651
- const distinctId = identityManager.getDistinctId();
1652
- try {
1653
- const response = await this.post("/api/v1/affiliate/withdraw", {
1654
- amount,
1655
- distinctId
1656
- });
1657
- if (response.success && response.withdrawal) {
1658
- this.log("Withdrawal requested:", amount);
1659
- return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
1660
- }
1661
- return {
1662
- success: false,
1663
- error: response.error
1664
- };
1665
- } catch (error) {
1666
- this.log("Failed to request withdrawal:", error);
1667
- return {
1668
- success: false,
1669
- error: error instanceof Error ? error.message : "Unknown error"
1670
- };
1671
- }
1672
- }
1673
- async getWithdrawals() {
1674
- this.ensureInitialized();
1675
- const distinctId = identityManager.getDistinctId();
1676
- try {
1677
- const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
1678
- return response.withdrawals.map(mapRawWithdrawal);
1679
- } catch (error) {
1680
- this.log("Failed to get withdrawals:", error);
1681
- return [];
1682
- }
1683
- }
1684
- async get(path, params) {
1685
- this.ensureInitialized();
1686
- return affiliateGet(this.apiClient, path, params);
1687
- }
1688
- async post(path, body) {
1689
- this.ensureInitialized();
1690
- return affiliatePost(this.apiClient, path, body);
1691
- }
1692
- ensureInitialized() {
1693
- if (!this.apiClient) {
1694
- throw new AffiliateError(
1695
- AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
1696
- "AffiliateManager not initialized. Call initialize() first"
1697
- );
1698
- }
1699
- }
1700
- log(...args) {
1701
- if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
1702
- }
1703
- };
1704
- var affiliateManager = new AffiliateManager();
1705
-
1706
- // src/domains/affiliate/PaywalloAffiliate.ts
1707
- var PaywalloAffiliate = {
1708
- registerCoupon: (code) => affiliateManager.registerCoupon(code),
1709
- applyCoupon: (code) => affiliateManager.applyCoupon(code),
1710
- getMyCoupon: () => affiliateManager.getMyCoupon(),
1711
- getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
1712
- getBalance: () => affiliateManager.getBalance(),
1713
- getCommissions: () => affiliateManager.getCommissions(),
1714
- requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
1715
- getWithdrawals: () => affiliateManager.getWithdrawals()
1716
- };
1717
-
1718
1513
  // src/domains/subscription/SubscriptionCache.ts
1719
1514
  var LEGACY_CACHE_KEY = "subscription_cache";
1720
1515
  var CACHE_KEY_PREFIX = "subscription_cache:";
@@ -1824,9 +1619,6 @@ var SubscriptionCache = class {
1824
1619
  };
1825
1620
  var subscriptionCache = new SubscriptionCache();
1826
1621
 
1827
- // src/domains/subscription/SubscriptionManager.ts
1828
- import { Platform as Platform4 } from "react-native";
1829
-
1830
1622
  // src/domains/session/SessionError.ts
1831
1623
  var SessionError = class extends PaywalloError {
1832
1624
  constructor(code, message) {
@@ -1914,11 +1706,10 @@ var SubscriptionManagerClass = class {
1914
1706
  if (!this.config) {
1915
1707
  return this.getEmptyStatus();
1916
1708
  }
1917
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
1709
+ const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
1918
1710
  if (this.userId) {
1919
- url.searchParams.set("userId", this.userId);
1711
+ url.searchParams.set("distinctId", this.userId);
1920
1712
  }
1921
- url.searchParams.set("platform", Platform4.OS);
1922
1713
  const response = await fetch(url.toString(), {
1923
1714
  method: "GET",
1924
1715
  headers: {
@@ -1973,7 +1764,7 @@ var SubscriptionManagerClass = class {
1973
1764
  var subscriptionManager = new SubscriptionManagerClass();
1974
1765
 
1975
1766
  // src/core/ApiClient.ts
1976
- import { Platform as Platform5 } from "react-native";
1767
+ import { Platform as Platform4 } from "react-native";
1977
1768
 
1978
1769
  // src/core/apiUrlUtils.ts
1979
1770
  var DEFAULT_WEB_URL = "https://paywallo.com.br";
@@ -2656,9 +2447,78 @@ var QueueProcessorClass = class {
2656
2447
  this.log("Offline, skipping queue processing");
2657
2448
  return { processed: 0, failed: 0 };
2658
2449
  }
2659
- return offlineQueue.processQueue(async (item) => {
2450
+ const allItems = offlineQueue.getQueue();
2451
+ const eventItems = allItems.filter(
2452
+ (i) => i.url.includes("/api/v1/events") && !i.url.includes("/batch")
2453
+ );
2454
+ const eventItemIds = new Set(eventItems.map((i) => i.id));
2455
+ const { processed: batchProcessed, failed: batchFailed, retryIds } = await this.processEventBatches(eventItems);
2456
+ const batchResult = { processed: batchProcessed, failed: batchFailed };
2457
+ const individualResult = await offlineQueue.processQueue(async (item) => {
2458
+ if (eventItemIds.has(item.id)) {
2459
+ if (retryIds.has(item.id)) {
2460
+ return { success: false, permanent: false };
2461
+ }
2462
+ return { success: true, permanent: false };
2463
+ }
2660
2464
  return this.processItem(item);
2661
2465
  });
2466
+ this.log("Queue processing complete", {
2467
+ batchProcessed: batchResult.processed,
2468
+ batchFailed: batchResult.failed,
2469
+ individualProcessed: individualResult.processed,
2470
+ individualFailed: individualResult.failed
2471
+ });
2472
+ return {
2473
+ processed: batchResult.processed + individualResult.processed,
2474
+ failed: batchResult.failed + individualResult.failed
2475
+ };
2476
+ }
2477
+ async processEventBatches(eventItems) {
2478
+ if (eventItems.length === 0 || !this.httpClient) {
2479
+ return { processed: 0, failed: 0, retryIds: /* @__PURE__ */ new Set() };
2480
+ }
2481
+ const BATCH_SIZE = 50;
2482
+ let processed = 0;
2483
+ let failed = 0;
2484
+ const retryIds = /* @__PURE__ */ new Set();
2485
+ for (let i = 0; i < eventItems.length; i += BATCH_SIZE) {
2486
+ const batch = eventItems.slice(i, i + BATCH_SIZE);
2487
+ const bodies = batch.map((item) => item.body);
2488
+ const headers = batch[0].headers;
2489
+ this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
2490
+ const response = await this.httpClient.request("/api/v1/events/batch", {
2491
+ method: "POST",
2492
+ body: { events: bodies },
2493
+ headers,
2494
+ skipRetry: false
2495
+ });
2496
+ if (response.ok) {
2497
+ for (const item of batch) {
2498
+ await offlineQueue.removeItem(item.id);
2499
+ processed++;
2500
+ }
2501
+ this.log("Event batch processed successfully", { size: batch.length });
2502
+ } else if (response.status >= 400 && response.status < 500) {
2503
+ for (const item of batch) {
2504
+ await offlineQueue.removeItem(item.id);
2505
+ failed++;
2506
+ }
2507
+ this.log("Event batch failed permanently (4xx) - removing from queue", {
2508
+ status: response.status,
2509
+ size: batch.length
2510
+ });
2511
+ } else {
2512
+ for (const item of batch) {
2513
+ retryIds.add(item.id);
2514
+ }
2515
+ this.log("Event batch failed with server error - will retry", {
2516
+ status: response.status,
2517
+ size: batch.length
2518
+ });
2519
+ }
2520
+ }
2521
+ return { processed, failed, retryIds };
2662
2522
  }
2663
2523
  async processItem(item) {
2664
2524
  if (!this.httpClient) {
@@ -2809,10 +2669,14 @@ var ApiCache = class {
2809
2669
  };
2810
2670
 
2811
2671
  // src/core/ApiClient.ts
2812
- var SDK_VERSION = "1.4.0";
2672
+ var SDK_VERSION = "1.5.0";
2813
2673
  var _ApiClient = class _ApiClient {
2814
2674
  constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2815
2675
  this.cache = new ApiCache();
2676
+ this.eventBatch = [];
2677
+ this.batchTimer = null;
2678
+ this.BATCH_MAX_SIZE = 10;
2679
+ this.BATCH_FLUSH_MS = 5e3;
2816
2680
  this.appKey = appKey;
2817
2681
  this.baseUrl = apiUrl;
2818
2682
  this.webUrl = deriveWebUrl(this.baseUrl);
@@ -2865,30 +2729,79 @@ var _ApiClient = class _ApiClient {
2865
2729
  message,
2866
2730
  context,
2867
2731
  sdkVersion: SDK_VERSION,
2868
- platform: Platform5.OS
2732
+ platform: Platform4.OS
2869
2733
  }, true);
2870
2734
  } catch (err) {
2871
2735
  if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
2872
2736
  }
2873
2737
  }
2874
2738
  async trackEvent(eventName, distinctId, properties, timestamp) {
2875
- await this.postWithQueue("/api/v1/events", {
2739
+ const EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
2740
+ if (!EVENT_NAME_REGEX.test(eventName)) {
2741
+ if (this.debug) {
2742
+ console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
2743
+ }
2744
+ }
2745
+ const event = {
2876
2746
  eventName,
2877
2747
  distinctId,
2878
- properties: { ...properties, platform: Platform5.OS },
2748
+ properties: { ...properties, platform: Platform4.OS },
2879
2749
  timestamp: timestamp ?? Date.now(),
2880
2750
  environment: this.environment.toLowerCase()
2881
- }, `event:${eventName}`);
2751
+ };
2752
+ this.eventBatch.push(event);
2753
+ if (this.eventBatch.length >= this.BATCH_MAX_SIZE) {
2754
+ if (this.batchTimer !== null) {
2755
+ clearTimeout(this.batchTimer);
2756
+ this.batchTimer = null;
2757
+ }
2758
+ await this.flushEventBatch();
2759
+ return;
2760
+ }
2761
+ if (this.batchTimer !== null) {
2762
+ clearTimeout(this.batchTimer);
2763
+ }
2764
+ this.batchTimer = setTimeout(() => {
2765
+ this.batchTimer = null;
2766
+ void this.flushEventBatch();
2767
+ }, this.BATCH_FLUSH_MS);
2768
+ }
2769
+ async flushEventBatch() {
2770
+ if (this.eventBatch.length === 0) return;
2771
+ const batch = this.eventBatch.splice(0, this.eventBatch.length);
2772
+ if (this.batchTimer !== null) {
2773
+ clearTimeout(this.batchTimer);
2774
+ this.batchTimer = null;
2775
+ }
2776
+ try {
2777
+ await this.postWithQueue(
2778
+ "/api/v1/events/batch",
2779
+ { events: batch },
2780
+ `event_batch:${batch.length}`
2781
+ );
2782
+ } catch (error) {
2783
+ this.log("Batch send failed, falling back to individual sends:", error);
2784
+ for (const event of batch) {
2785
+ await this.postWithQueue(
2786
+ "/api/v1/events",
2787
+ event,
2788
+ `event:${event.eventName}`
2789
+ );
2790
+ }
2791
+ }
2882
2792
  }
2883
2793
  async identify(distinctId, properties, email, deviceId) {
2884
- await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
2794
+ await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform4.OS, ...deviceId && { deviceId } }, "identify");
2885
2795
  }
2886
- async startSession(distinctId, sessionId, platform) {
2796
+ async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
2887
2797
  await this.postWithQueue("/api/v1/sessions/start", {
2888
2798
  distinctId,
2889
2799
  sessionId,
2890
- platform: platform ?? Platform5.OS,
2891
- environment: this.environment.toLowerCase()
2800
+ platform: platform ?? Platform4.OS,
2801
+ environment: this.environment.toLowerCase(),
2802
+ appVersion: appVersion ?? "unknown",
2803
+ deviceModel: deviceModel ?? "unknown",
2804
+ osVersion: osVersion ?? String(Platform4.Version)
2892
2805
  }, `session.start:${sessionId}`);
2893
2806
  return { success: true };
2894
2807
  }
@@ -2908,6 +2821,7 @@ var _ApiClient = class _ApiClient {
2908
2821
  try {
2909
2822
  const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
2910
2823
  if (res.status === 404) {
2824
+ this.log("Flag not found (404):", flagKey);
2911
2825
  const v = { variant: null };
2912
2826
  this.cache.setVariant(key, v, this.cache.nullTTL);
2913
2827
  return v;
@@ -2934,6 +2848,7 @@ var _ApiClient = class _ApiClient {
2934
2848
  try {
2935
2849
  const res = await this.get(`/api/v1/paywalls/${placement}`);
2936
2850
  if (res.status === 404) {
2851
+ this.log("Paywall not found (404):", placement);
2937
2852
  this.cache.setPaywall(placement, null, this.cache.nullTTL);
2938
2853
  return null;
2939
2854
  }
@@ -2985,6 +2900,7 @@ var _ApiClient = class _ApiClient {
2985
2900
  try {
2986
2901
  const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
2987
2902
  if (res.status === 404) {
2903
+ this.log("Primary campaign not found (404)");
2988
2904
  this.cache.setCampaign(key, null, this.cache.nullTTL);
2989
2905
  return null;
2990
2906
  }
@@ -3035,7 +2951,7 @@ var _ApiClient = class _ApiClient {
3035
2951
  headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
3036
2952
  });
3037
2953
  if (!res.ok) {
3038
- this.log("Non-OK response for evaluateFlags:", res.status);
2954
+ this.log("evaluateFlags failed:", res.status, keys);
3039
2955
  return {};
3040
2956
  }
3041
2957
  const raw = res.data;
@@ -3193,7 +3109,7 @@ var CampaignGateService = class {
3193
3109
  const isActive = await hasActive();
3194
3110
  if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3195
3111
  if (isActive) return true;
3196
- const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
3112
+ const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
3197
3113
  return r.purchased || r.restored;
3198
3114
  }
3199
3115
  async preloadCampaign(apiClient, placement, context) {
@@ -3231,7 +3147,7 @@ var CampaignGateService = class {
3231
3147
  var campaignGateService = new CampaignGateService();
3232
3148
 
3233
3149
  // src/domains/identity/AdvertisingIdManager.ts
3234
- import { Platform as Platform6 } from "react-native";
3150
+ import { Platform as Platform5 } from "react-native";
3235
3151
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
3236
3152
  var AdvertisingIdManager = class {
3237
3153
  constructor() {
@@ -3242,7 +3158,7 @@ var AdvertisingIdManager = class {
3242
3158
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
3243
3159
  try {
3244
3160
  const tracking = await import("expo-tracking-transparency");
3245
- if (Platform6.OS === "ios") {
3161
+ if (Platform5.OS === "ios") {
3246
3162
  if (!tracking.isAvailable()) {
3247
3163
  const idfv2 = await this.collectIdfv();
3248
3164
  this.cached = { ...fallback, idfv: idfv2 };
@@ -3261,7 +3177,7 @@ var AdvertisingIdManager = class {
3261
3177
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
3262
3178
  return this.cached;
3263
3179
  }
3264
- if (Platform6.OS === "android") {
3180
+ if (Platform5.OS === "android") {
3265
3181
  const androidStatus = await tracking.getTrackingPermissionsAsync();
3266
3182
  const optedIn = androidStatus.granted;
3267
3183
  const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
@@ -3347,11 +3263,11 @@ var MetaBridge = class {
3347
3263
  var metaBridge = new MetaBridge();
3348
3264
 
3349
3265
  // src/domains/identity/InstallReferrerManager.ts
3350
- import { Platform as Platform7 } from "react-native";
3266
+ import { Platform as Platform6 } from "react-native";
3351
3267
  var REFERRER_TIMEOUT_MS = 5e3;
3352
3268
  var InstallReferrerManager = class {
3353
3269
  async getReferrer() {
3354
- if (Platform7.OS !== "android") {
3270
+ if (Platform6.OS !== "android") {
3355
3271
  return this.emptyResult();
3356
3272
  }
3357
3273
  let timerId;
@@ -3414,14 +3330,77 @@ var installReferrerManager = new InstallReferrerManager();
3414
3330
  import { AppState as AppState2 } from "react-native";
3415
3331
  init_uuid();
3416
3332
 
3333
+ // src/utils/deviceInfo.ts
3334
+ import { Platform as Platform7 } from "react-native";
3335
+ async function getAppVersion() {
3336
+ try {
3337
+ const app = await import("expo-application");
3338
+ if (app.nativeApplicationVersion) {
3339
+ return app.nativeApplicationVersion;
3340
+ }
3341
+ } catch {
3342
+ }
3343
+ try {
3344
+ const DeviceInfo2 = (await import("react-native-device-info")).default;
3345
+ const v = DeviceInfo2.getVersion();
3346
+ if (v) return v;
3347
+ } catch {
3348
+ }
3349
+ return "unknown";
3350
+ }
3351
+ async function getDeviceModel() {
3352
+ try {
3353
+ const DeviceInfo2 = (await import("react-native-device-info")).default;
3354
+ const model = DeviceInfo2.getModel();
3355
+ if (model) return model;
3356
+ } catch {
3357
+ }
3358
+ return "unknown";
3359
+ }
3360
+ function getOsVersion() {
3361
+ const v = Platform7.Version;
3362
+ if (typeof v === "number") return String(v);
3363
+ if (typeof v === "string" && v.length > 0) return v;
3364
+ return "unknown";
3365
+ }
3366
+ async function collectDeviceInfo() {
3367
+ const [appVersion, deviceModel] = await Promise.all([
3368
+ getAppVersion(),
3369
+ getDeviceModel()
3370
+ ]);
3371
+ return {
3372
+ appVersion,
3373
+ deviceModel,
3374
+ osVersion: getOsVersion()
3375
+ };
3376
+ }
3377
+
3417
3378
  // src/domains/session/sessionTracking.ts
3418
3379
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
3419
3380
  const distinctId = identityManager.getDistinctId();
3420
- await apiClient.trackEvent("$session_start", distinctId, {
3421
- sessionId,
3422
- timestamp: sessionStartedAt?.toISOString() ?? null
3423
- }).catch(() => {
3424
- });
3381
+ const deviceInfo = await collectDeviceInfo().catch(() => ({
3382
+ appVersion: "unknown",
3383
+ deviceModel: "unknown",
3384
+ osVersion: "unknown"
3385
+ }));
3386
+ await Promise.all([
3387
+ apiClient.startSession(
3388
+ distinctId,
3389
+ sessionId,
3390
+ void 0,
3391
+ deviceInfo.appVersion,
3392
+ deviceInfo.deviceModel,
3393
+ deviceInfo.osVersion
3394
+ ),
3395
+ apiClient.trackEvent("$session_start", distinctId, {
3396
+ sessionId,
3397
+ timestamp: sessionStartedAt?.toISOString() ?? null,
3398
+ appVersion: deviceInfo.appVersion,
3399
+ deviceModel: deviceInfo.deviceModel,
3400
+ osVersion: deviceInfo.osVersion
3401
+ }).catch(() => {
3402
+ })
3403
+ ]);
3425
3404
  }
3426
3405
  async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
3427
3406
  const distinctId = identityManager.getDistinctId();
@@ -3518,6 +3497,9 @@ var SessionManager = class {
3518
3497
  if (!this.sessionId || !this.sessionStartedAt) {
3519
3498
  return;
3520
3499
  }
3500
+ if (this.apiClient) {
3501
+ await this.apiClient.flushEventBatch();
3502
+ }
3521
3503
  await this.trackSessionEnd();
3522
3504
  this.sessionId = null;
3523
3505
  this.sessionStartedAt = null;
@@ -3602,6 +3584,9 @@ var SessionManager = class {
3602
3584
  async handleBackground() {
3603
3585
  this.backgroundTimestamp = Date.now();
3604
3586
  await this.trackAppBackground();
3587
+ if (this.apiClient) {
3588
+ await this.apiClient.flushEventBatch();
3589
+ }
3605
3590
  }
3606
3591
  async checkEmergencyPaywall() {
3607
3592
  if (!this.apiClient || !this.emergencyPaywallHandler) {
@@ -3957,7 +3942,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3957
3942
  ]);
3958
3943
  this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
3959
3944
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3960
- affiliateManager.initialize(this.apiClient, config.debug);
3961
3945
  subscriptionManager.init({
3962
3946
  serverUrl: this.apiClient.getBaseUrl(),
3963
3947
  appKey: config.appKey,
@@ -3984,6 +3968,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3984
3968
  this.resolveReady = null;
3985
3969
  }
3986
3970
  if (config.debug) console.info("[Paywallo INIT] complete");
3971
+ if (distinctId) {
3972
+ this.apiClient.getSubscriptionStatus(distinctId).then(() => {
3973
+ if (config.debug) console.info("[Paywallo SUB] preloaded on boot");
3974
+ }).catch(() => {
3975
+ });
3976
+ }
3987
3977
  if (this.apiClient) {
3988
3978
  const apiClientRef = this.apiClient;
3989
3979
  if (config.autoPreloadCampaign) {
@@ -4009,18 +3999,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4009
3999
  }
4010
4000
  async identify(options) {
4011
4001
  this.ensureInitialized();
4002
+ if (this.config?.debug) console.info("[Paywallo IDENTIFY]", options);
4012
4003
  await identityManager.identify(options);
4013
4004
  }
4014
4005
  async track(eventName, options) {
4015
4006
  const apiClient = this.getApiClientOrThrow();
4016
4007
  const distinctId = identityManager.getDistinctId();
4017
4008
  if (!distinctId) {
4009
+ if (this.config?.debug) console.info(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
4018
4010
  return;
4019
4011
  }
4020
4012
  if (!isValidEventName(eventName)) {
4021
4013
  const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
4022
4014
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
4023
4015
  }
4016
+ if (this.config?.debug && eventName.startsWith("$purchase")) console.info(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
4024
4017
  const sessionId = sessionManager.getSessionId();
4025
4018
  await apiClient.trackEvent(eventName, distinctId, {
4026
4019
  ...identityManager.getProperties(),
@@ -4041,14 +4034,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4041
4034
  const now = Date.now();
4042
4035
  const timeOnPreviousStep = this.lastOnboardingStepTimestamp !== null ? Math.round((now - this.lastOnboardingStepTimestamp) / 1e3) : 0;
4043
4036
  this.lastOnboardingStepTimestamp = now;
4044
- const sessionId = sessionManager.getSessionId();
4045
4037
  await apiClient.trackEvent("$onboarding_step", distinctId, {
4046
- ...identityManager.getProperties(),
4047
4038
  stepIndex: index,
4048
4039
  stepName: name,
4049
- timestamp: now,
4050
- timeOnPreviousStep,
4051
- ...sessionId && { sessionId }
4040
+ timeOnPreviousStep
4052
4041
  });
4053
4042
  }
4054
4043
  async coreAction(actionName) {
@@ -4083,10 +4072,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4083
4072
  try {
4084
4073
  const distinctId = identityManager.getDistinctId();
4085
4074
  if (!distinctId) {
4075
+ if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
4086
4076
  return { variant: null };
4087
4077
  }
4088
- return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
4078
+ const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
4079
+ if (this.config?.debug) console.info(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
4080
+ return result;
4089
4081
  } catch (error) {
4082
+ if (this.config?.debug) console.warn(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
4090
4083
  if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
4091
4084
  return { variant: null };
4092
4085
  }
@@ -4119,21 +4112,34 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4119
4112
  }
4120
4113
  async presentCampaign(placement, context) {
4121
4114
  this.ensureInitialized();
4122
- return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
4115
+ if (this.config?.debug) console.info(`[Paywallo CAMPAIGN] presenting ${placement}`);
4116
+ const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
4117
+ if (this.config?.debug) {
4118
+ if (result.error) console.warn(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
4119
+ else console.info(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
4120
+ }
4121
+ return result;
4123
4122
  }
4124
4123
  async hasActiveSubscription() {
4125
4124
  this.ensureInitialized();
4126
4125
  try {
4127
4126
  if (this.activeChecker) {
4128
- return await this.activeChecker();
4127
+ const result = await this.activeChecker();
4128
+ const isActive2 = result === true;
4129
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
4130
+ return isActive2;
4129
4131
  }
4130
4132
  const distinctId = identityManager.getDistinctId();
4131
4133
  if (!distinctId || !this.apiClient) {
4134
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
4132
4135
  return false;
4133
4136
  }
4134
4137
  const status = await this.apiClient.getSubscriptionStatus(distinctId);
4135
- return status.hasActiveSubscription;
4138
+ const isActive = status.hasActiveSubscription === true;
4139
+ if (this.config?.debug) console.info("[Paywallo SUB] hasActiveSubscription (server):", isActive);
4140
+ return isActive;
4136
4141
  } catch (error) {
4142
+ if (this.config?.debug) console.warn("[Paywallo SUB] hasActiveSubscription error", error);
4137
4143
  if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
4138
4144
  return false;
4139
4145
  }
@@ -4154,7 +4160,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4154
4160
  async restorePurchases() {
4155
4161
  this.ensureInitialized();
4156
4162
  if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
4157
- return this.restoreHandler();
4163
+ if (this.config?.debug) console.info("[Paywallo RESTORE] starting");
4164
+ const result = await this.restoreHandler();
4165
+ if (this.config?.debug) console.info("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
4166
+ return result;
4158
4167
  }
4159
4168
  async reset() {
4160
4169
  await identityManager.reset();
@@ -4249,6 +4258,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4249
4258
  registerEmergencyPaywallHandler(h) {
4250
4259
  sessionManager.registerEmergencyPaywallHandler(h);
4251
4260
  }
4261
+ async getEmergencyPaywall() {
4262
+ if (!this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized");
4263
+ return this.apiClient.getEmergencyPaywall();
4264
+ }
4252
4265
  async requireSubscription(paywallPlacement) {
4253
4266
  this.ensureInitialized();
4254
4267
  if (await this.hasActiveSubscription()) return true;
@@ -4793,7 +4806,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4793
4806
  // src/hooks/useSubscriptionSync.ts
4794
4807
  import { useCallback as useCallback12, useRef as useRef7, useState as useState11 } from "react";
4795
4808
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
4796
- var ACTIVE_STATUSES = ["active", "grace_period"];
4809
+ var ACTIVE_STATUSES = ["active", "in_grace_period"];
4797
4810
  function toDomainSubscriptionInfo(sub) {
4798
4811
  return {
4799
4812
  id: "",
@@ -4812,7 +4825,7 @@ function toDomainResponse(status) {
4812
4825
  return {
4813
4826
  hasActiveSubscription: status.hasActiveSubscription,
4814
4827
  subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
4815
- entitlements: []
4828
+ entitlements: status.entitlements ?? []
4816
4829
  };
4817
4830
  }
4818
4831
  function isSubscriptionStillActive(sub) {
@@ -4868,13 +4881,14 @@ function useSubscriptionSync() {
4868
4881
  const persisted = await subscriptionCache.get(distinctId);
4869
4882
  if (persisted && !persisted.isStale) {
4870
4883
  const persistedSub = persisted.data.subscription;
4884
+ const persistedStatus = persistedSub?.status;
4871
4885
  const sub = persistedSub ? {
4872
4886
  productId: persistedSub.productId,
4873
- status: persistedSub.status,
4887
+ status: persistedStatus,
4874
4888
  expiresAt: persistedSub.expiresAt ?? null,
4875
4889
  platform: persistedSub.platform,
4876
4890
  autoRenewEnabled: persistedSub.willRenew,
4877
- inGracePeriod: persistedSub.status === "grace_period"
4891
+ inGracePeriod: persistedStatus === "in_grace_period"
4878
4892
  } : null;
4879
4893
  const stillActive = isSubscriptionStillActive(sub);
4880
4894
  if (stillActive) {
@@ -4894,7 +4908,7 @@ function useSubscriptionSync() {
4894
4908
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
4895
4909
  setSubscription(sub);
4896
4910
  void subscriptionCache.set(distinctId, toDomainResponse(status));
4897
- return status.hasActiveSubscription;
4911
+ return status.hasActiveSubscription === true;
4898
4912
  } catch {
4899
4913
  return await resolveOfflineFromCache(distinctId);
4900
4914
  }
@@ -4912,14 +4926,14 @@ function useSubscriptionSync() {
4912
4926
  const persisted = await subscriptionCache.get(distinctId);
4913
4927
  if (!persisted?.data.subscription) return null;
4914
4928
  const s = persisted.data.subscription;
4915
- const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
4929
+ const status = s.status;
4916
4930
  return {
4917
4931
  productId: s.productId,
4918
- status: normalisedStatus,
4932
+ status,
4919
4933
  expiresAt: s.expiresAt ?? null,
4920
4934
  platform: s.platform,
4921
4935
  autoRenewEnabled: s.willRenew,
4922
- inGracePeriod: normalisedStatus === "in_grace_period"
4936
+ inGracePeriod: status === "in_grace_period"
4923
4937
  };
4924
4938
  } catch {
4925
4939
  return null;
@@ -5344,10 +5358,7 @@ function hasVariables(text) {
5344
5358
  var Paywallo = PaywalloClient;
5345
5359
  var index_default = Paywallo;
5346
5360
  export {
5347
- AFFILIATE_ERROR_CODES,
5348
5361
  ANALYTICS_ERROR_CODES,
5349
- AffiliateError,
5350
- AffiliateManager,
5351
5362
  AnalyticsError,
5352
5363
  ApiClient,
5353
5364
  CAMPAIGN_ERROR_CODES,
@@ -5365,7 +5376,6 @@ export {
5365
5376
  PaywallError,
5366
5377
  PaywallModal,
5367
5378
  Paywallo,
5368
- PaywalloAffiliate,
5369
5379
  PaywalloClient,
5370
5380
  PaywalloContext,
5371
5381
  PaywalloError,
@@ -5375,7 +5385,6 @@ export {
5375
5385
  SessionError,
5376
5386
  SessionManager,
5377
5387
  SubscriptionCache,
5378
- affiliateManager,
5379
5388
  createPurchaseError,
5380
5389
  index_default as default,
5381
5390
  detectDeviceLanguage,