@virex-tech/paywallo-sdk 1.4.1 → 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 };
@@ -1502,227 +1510,6 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
1502
1510
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
1503
1511
  }
1504
1512
 
1505
- // src/domains/affiliate/AffiliateError.ts
1506
- var AffiliateError = class extends PaywalloError {
1507
- constructor(code, message) {
1508
- super("affiliate", code, message);
1509
- this.name = "AffiliateError";
1510
- }
1511
- };
1512
- var AFFILIATE_ERROR_CODES = {
1513
- NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
1514
- COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
1515
- COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
1516
- BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
1517
- WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
1518
- NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
1519
- };
1520
-
1521
- // src/domains/affiliate/affiliateHttp.ts
1522
- function buildQueryString(params) {
1523
- if (!params) return "";
1524
- const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
1525
- if (entries.length === 0) return "";
1526
- return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
1527
- }
1528
- async function affiliateGet(apiClient, path, params) {
1529
- const response = await apiClient.get(`${path}${buildQueryString(params)}`);
1530
- return response.data;
1531
- }
1532
- async function affiliatePost(apiClient, path, body) {
1533
- const response = await apiClient.post(path, body);
1534
- return response.data;
1535
- }
1536
-
1537
- // src/domains/affiliate/affiliateDateMappers.ts
1538
- function mapRawCoupon(raw) {
1539
- return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
1540
- }
1541
- function mapRawCommission(raw) {
1542
- return { ...raw, createdAt: new Date(raw.createdAt) };
1543
- }
1544
- function mapRawWithdrawal(raw) {
1545
- return { ...raw, createdAt: new Date(raw.createdAt) };
1546
- }
1547
-
1548
- // src/domains/affiliate/AffiliateManager.ts
1549
- var AffiliateManager = class {
1550
- constructor() {
1551
- this.apiClient = null;
1552
- this.debug = false;
1553
- }
1554
- initialize(apiClient, debug = false) {
1555
- this.apiClient = apiClient;
1556
- this.debug = debug;
1557
- this.log("AffiliateManager initialized");
1558
- }
1559
- async registerCoupon(code) {
1560
- this.ensureInitialized();
1561
- const distinctId = identityManager.getDistinctId();
1562
- try {
1563
- const response = await this.post("/api/v1/affiliate/register-coupon", {
1564
- code,
1565
- distinctId
1566
- });
1567
- if (response.success && response.coupon) {
1568
- this.log("Coupon registered:", code);
1569
- return { success: true, coupon: mapRawCoupon(response.coupon) };
1570
- }
1571
- return {
1572
- success: false,
1573
- error: response.error
1574
- };
1575
- } catch (error) {
1576
- this.log("Failed to register coupon:", error);
1577
- return {
1578
- success: false,
1579
- error: error instanceof Error ? error.message : "Unknown error"
1580
- };
1581
- }
1582
- }
1583
- async applyCoupon(code) {
1584
- this.ensureInitialized();
1585
- const distinctId = identityManager.getDistinctId();
1586
- try {
1587
- const response = await this.post("/api/v1/affiliate/apply-coupon", {
1588
- code,
1589
- distinctId
1590
- });
1591
- if (response.success) {
1592
- this.log("Coupon applied:", code);
1593
- }
1594
- return response;
1595
- } catch (error) {
1596
- this.log("Failed to apply coupon:", error);
1597
- return {
1598
- success: false,
1599
- error: error instanceof Error ? error.message : "Unknown error"
1600
- };
1601
- }
1602
- }
1603
- async getMyCoupon() {
1604
- this.ensureInitialized();
1605
- const distinctId = identityManager.getDistinctId();
1606
- try {
1607
- const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
1608
- if (response.coupon) {
1609
- return { coupon: mapRawCoupon(response.coupon) };
1610
- }
1611
- return { coupon: null };
1612
- } catch (error) {
1613
- this.log("Failed to get my coupon:", error);
1614
- return { coupon: null };
1615
- }
1616
- }
1617
- async getAppliedCoupon() {
1618
- this.ensureInitialized();
1619
- const distinctId = identityManager.getDistinctId();
1620
- try {
1621
- const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
1622
- return response;
1623
- } catch (error) {
1624
- this.log("Failed to get applied coupon:", error);
1625
- return { coupon: null };
1626
- }
1627
- }
1628
- async getBalance() {
1629
- this.ensureInitialized();
1630
- const distinctId = identityManager.getDistinctId();
1631
- try {
1632
- const response = await this.get("/api/v1/affiliate/balance", {
1633
- distinctId
1634
- });
1635
- return response;
1636
- } catch (error) {
1637
- this.log("Failed to get balance:", error);
1638
- return {
1639
- total: 0,
1640
- pending: 0,
1641
- available: 0,
1642
- withdrawn: 0
1643
- };
1644
- }
1645
- }
1646
- async getCommissions() {
1647
- this.ensureInitialized();
1648
- const distinctId = identityManager.getDistinctId();
1649
- try {
1650
- const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
1651
- return response.commissions.map(mapRawCommission);
1652
- } catch (error) {
1653
- this.log("Failed to get commissions:", error);
1654
- return [];
1655
- }
1656
- }
1657
- async requestWithdrawal(amount) {
1658
- this.ensureInitialized();
1659
- const distinctId = identityManager.getDistinctId();
1660
- try {
1661
- const response = await this.post("/api/v1/affiliate/withdraw", {
1662
- amount,
1663
- distinctId
1664
- });
1665
- if (response.success && response.withdrawal) {
1666
- this.log("Withdrawal requested:", amount);
1667
- return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
1668
- }
1669
- return {
1670
- success: false,
1671
- error: response.error
1672
- };
1673
- } catch (error) {
1674
- this.log("Failed to request withdrawal:", error);
1675
- return {
1676
- success: false,
1677
- error: error instanceof Error ? error.message : "Unknown error"
1678
- };
1679
- }
1680
- }
1681
- async getWithdrawals() {
1682
- this.ensureInitialized();
1683
- const distinctId = identityManager.getDistinctId();
1684
- try {
1685
- const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
1686
- return response.withdrawals.map(mapRawWithdrawal);
1687
- } catch (error) {
1688
- this.log("Failed to get withdrawals:", error);
1689
- return [];
1690
- }
1691
- }
1692
- async get(path, params) {
1693
- this.ensureInitialized();
1694
- return affiliateGet(this.apiClient, path, params);
1695
- }
1696
- async post(path, body) {
1697
- this.ensureInitialized();
1698
- return affiliatePost(this.apiClient, path, body);
1699
- }
1700
- ensureInitialized() {
1701
- if (!this.apiClient) {
1702
- throw new AffiliateError(
1703
- AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
1704
- "AffiliateManager not initialized. Call initialize() first"
1705
- );
1706
- }
1707
- }
1708
- log(...args) {
1709
- if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
1710
- }
1711
- };
1712
- var affiliateManager = new AffiliateManager();
1713
-
1714
- // src/domains/affiliate/PaywalloAffiliate.ts
1715
- var PaywalloAffiliate = {
1716
- registerCoupon: (code) => affiliateManager.registerCoupon(code),
1717
- applyCoupon: (code) => affiliateManager.applyCoupon(code),
1718
- getMyCoupon: () => affiliateManager.getMyCoupon(),
1719
- getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
1720
- getBalance: () => affiliateManager.getBalance(),
1721
- getCommissions: () => affiliateManager.getCommissions(),
1722
- requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
1723
- getWithdrawals: () => affiliateManager.getWithdrawals()
1724
- };
1725
-
1726
1513
  // src/domains/subscription/SubscriptionCache.ts
1727
1514
  var LEGACY_CACHE_KEY = "subscription_cache";
1728
1515
  var CACHE_KEY_PREFIX = "subscription_cache:";
@@ -1832,9 +1619,6 @@ var SubscriptionCache = class {
1832
1619
  };
1833
1620
  var subscriptionCache = new SubscriptionCache();
1834
1621
 
1835
- // src/domains/subscription/SubscriptionManager.ts
1836
- import { Platform as Platform4 } from "react-native";
1837
-
1838
1622
  // src/domains/session/SessionError.ts
1839
1623
  var SessionError = class extends PaywalloError {
1840
1624
  constructor(code, message) {
@@ -1922,11 +1706,10 @@ var SubscriptionManagerClass = class {
1922
1706
  if (!this.config) {
1923
1707
  return this.getEmptyStatus();
1924
1708
  }
1925
- const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
1709
+ const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
1926
1710
  if (this.userId) {
1927
- url.searchParams.set("userId", this.userId);
1711
+ url.searchParams.set("distinctId", this.userId);
1928
1712
  }
1929
- url.searchParams.set("platform", Platform4.OS);
1930
1713
  const response = await fetch(url.toString(), {
1931
1714
  method: "GET",
1932
1715
  headers: {
@@ -1981,7 +1764,7 @@ var SubscriptionManagerClass = class {
1981
1764
  var subscriptionManager = new SubscriptionManagerClass();
1982
1765
 
1983
1766
  // src/core/ApiClient.ts
1984
- import { Platform as Platform5 } from "react-native";
1767
+ import { Platform as Platform4 } from "react-native";
1985
1768
 
1986
1769
  // src/core/apiUrlUtils.ts
1987
1770
  var DEFAULT_WEB_URL = "https://paywallo.com.br";
@@ -2664,9 +2447,78 @@ var QueueProcessorClass = class {
2664
2447
  this.log("Offline, skipping queue processing");
2665
2448
  return { processed: 0, failed: 0 };
2666
2449
  }
2667
- 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
+ }
2668
2464
  return this.processItem(item);
2669
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 };
2670
2522
  }
2671
2523
  async processItem(item) {
2672
2524
  if (!this.httpClient) {
@@ -2817,10 +2669,14 @@ var ApiCache = class {
2817
2669
  };
2818
2670
 
2819
2671
  // src/core/ApiClient.ts
2820
- var SDK_VERSION = "1.4.1";
2672
+ var SDK_VERSION = "1.5.0";
2821
2673
  var _ApiClient = class _ApiClient {
2822
2674
  constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2823
2675
  this.cache = new ApiCache();
2676
+ this.eventBatch = [];
2677
+ this.batchTimer = null;
2678
+ this.BATCH_MAX_SIZE = 10;
2679
+ this.BATCH_FLUSH_MS = 5e3;
2824
2680
  this.appKey = appKey;
2825
2681
  this.baseUrl = apiUrl;
2826
2682
  this.webUrl = deriveWebUrl(this.baseUrl);
@@ -2873,30 +2729,79 @@ var _ApiClient = class _ApiClient {
2873
2729
  message,
2874
2730
  context,
2875
2731
  sdkVersion: SDK_VERSION,
2876
- platform: Platform5.OS
2732
+ platform: Platform4.OS
2877
2733
  }, true);
2878
2734
  } catch (err) {
2879
2735
  if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
2880
2736
  }
2881
2737
  }
2882
2738
  async trackEvent(eventName, distinctId, properties, timestamp) {
2883
- 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 = {
2884
2746
  eventName,
2885
2747
  distinctId,
2886
- properties: { ...properties, platform: Platform5.OS },
2748
+ properties: { ...properties, platform: Platform4.OS },
2887
2749
  timestamp: timestamp ?? Date.now(),
2888
2750
  environment: this.environment.toLowerCase()
2889
- }, `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
+ }
2890
2792
  }
2891
2793
  async identify(distinctId, properties, email, deviceId) {
2892
- 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");
2893
2795
  }
2894
- async startSession(distinctId, sessionId, platform) {
2796
+ async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
2895
2797
  await this.postWithQueue("/api/v1/sessions/start", {
2896
2798
  distinctId,
2897
2799
  sessionId,
2898
- platform: platform ?? Platform5.OS,
2899
- 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)
2900
2805
  }, `session.start:${sessionId}`);
2901
2806
  return { success: true };
2902
2807
  }
@@ -3204,7 +3109,7 @@ var CampaignGateService = class {
3204
3109
  const isActive = await hasActive();
3205
3110
  if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
3206
3111
  if (isActive) return true;
3207
- 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 });
3208
3113
  return r.purchased || r.restored;
3209
3114
  }
3210
3115
  async preloadCampaign(apiClient, placement, context) {
@@ -3242,7 +3147,7 @@ var CampaignGateService = class {
3242
3147
  var campaignGateService = new CampaignGateService();
3243
3148
 
3244
3149
  // src/domains/identity/AdvertisingIdManager.ts
3245
- import { Platform as Platform6 } from "react-native";
3150
+ import { Platform as Platform5 } from "react-native";
3246
3151
  var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
3247
3152
  var AdvertisingIdManager = class {
3248
3153
  constructor() {
@@ -3253,7 +3158,7 @@ var AdvertisingIdManager = class {
3253
3158
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
3254
3159
  try {
3255
3160
  const tracking = await import("expo-tracking-transparency");
3256
- if (Platform6.OS === "ios") {
3161
+ if (Platform5.OS === "ios") {
3257
3162
  if (!tracking.isAvailable()) {
3258
3163
  const idfv2 = await this.collectIdfv();
3259
3164
  this.cached = { ...fallback, idfv: idfv2 };
@@ -3272,7 +3177,7 @@ var AdvertisingIdManager = class {
3272
3177
  this.cached = { idfv, idfa, gaid: null, attStatus: status };
3273
3178
  return this.cached;
3274
3179
  }
3275
- if (Platform6.OS === "android") {
3180
+ if (Platform5.OS === "android") {
3276
3181
  const androidStatus = await tracking.getTrackingPermissionsAsync();
3277
3182
  const optedIn = androidStatus.granted;
3278
3183
  const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
@@ -3358,11 +3263,11 @@ var MetaBridge = class {
3358
3263
  var metaBridge = new MetaBridge();
3359
3264
 
3360
3265
  // src/domains/identity/InstallReferrerManager.ts
3361
- import { Platform as Platform7 } from "react-native";
3266
+ import { Platform as Platform6 } from "react-native";
3362
3267
  var REFERRER_TIMEOUT_MS = 5e3;
3363
3268
  var InstallReferrerManager = class {
3364
3269
  async getReferrer() {
3365
- if (Platform7.OS !== "android") {
3270
+ if (Platform6.OS !== "android") {
3366
3271
  return this.emptyResult();
3367
3272
  }
3368
3273
  let timerId;
@@ -3425,14 +3330,77 @@ var installReferrerManager = new InstallReferrerManager();
3425
3330
  import { AppState as AppState2 } from "react-native";
3426
3331
  init_uuid();
3427
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
+
3428
3378
  // src/domains/session/sessionTracking.ts
3429
3379
  async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
3430
3380
  const distinctId = identityManager.getDistinctId();
3431
- await apiClient.trackEvent("$session_start", distinctId, {
3432
- sessionId,
3433
- timestamp: sessionStartedAt?.toISOString() ?? null
3434
- }).catch(() => {
3435
- });
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
+ ]);
3436
3404
  }
3437
3405
  async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
3438
3406
  const distinctId = identityManager.getDistinctId();
@@ -3529,6 +3497,9 @@ var SessionManager = class {
3529
3497
  if (!this.sessionId || !this.sessionStartedAt) {
3530
3498
  return;
3531
3499
  }
3500
+ if (this.apiClient) {
3501
+ await this.apiClient.flushEventBatch();
3502
+ }
3532
3503
  await this.trackSessionEnd();
3533
3504
  this.sessionId = null;
3534
3505
  this.sessionStartedAt = null;
@@ -3613,6 +3584,9 @@ var SessionManager = class {
3613
3584
  async handleBackground() {
3614
3585
  this.backgroundTimestamp = Date.now();
3615
3586
  await this.trackAppBackground();
3587
+ if (this.apiClient) {
3588
+ await this.apiClient.flushEventBatch();
3589
+ }
3616
3590
  }
3617
3591
  async checkEmergencyPaywall() {
3618
3592
  if (!this.apiClient || !this.emergencyPaywallHandler) {
@@ -3968,7 +3942,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3968
3942
  ]);
3969
3943
  this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
3970
3944
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3971
- affiliateManager.initialize(this.apiClient, config.debug);
3972
3945
  subscriptionManager.init({
3973
3946
  serverUrl: this.apiClient.getBaseUrl(),
3974
3947
  appKey: config.appKey,
@@ -3995,6 +3968,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
3995
3968
  this.resolveReady = null;
3996
3969
  }
3997
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
+ }
3998
3977
  if (this.apiClient) {
3999
3978
  const apiClientRef = this.apiClient;
4000
3979
  if (config.autoPreloadCampaign) {
@@ -4279,6 +4258,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
4279
4258
  registerEmergencyPaywallHandler(h) {
4280
4259
  sessionManager.registerEmergencyPaywallHandler(h);
4281
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
+ }
4282
4265
  async requireSubscription(paywallPlacement) {
4283
4266
  this.ensureInitialized();
4284
4267
  if (await this.hasActiveSubscription()) return true;
@@ -4823,7 +4806,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
4823
4806
  // src/hooks/useSubscriptionSync.ts
4824
4807
  import { useCallback as useCallback12, useRef as useRef7, useState as useState11 } from "react";
4825
4808
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
4826
- var ACTIVE_STATUSES = ["active", "grace_period"];
4809
+ var ACTIVE_STATUSES = ["active", "in_grace_period"];
4827
4810
  function toDomainSubscriptionInfo(sub) {
4828
4811
  return {
4829
4812
  id: "",
@@ -4842,7 +4825,7 @@ function toDomainResponse(status) {
4842
4825
  return {
4843
4826
  hasActiveSubscription: status.hasActiveSubscription,
4844
4827
  subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
4845
- entitlements: []
4828
+ entitlements: status.entitlements ?? []
4846
4829
  };
4847
4830
  }
4848
4831
  function isSubscriptionStillActive(sub) {
@@ -4898,13 +4881,14 @@ function useSubscriptionSync() {
4898
4881
  const persisted = await subscriptionCache.get(distinctId);
4899
4882
  if (persisted && !persisted.isStale) {
4900
4883
  const persistedSub = persisted.data.subscription;
4884
+ const persistedStatus = persistedSub?.status;
4901
4885
  const sub = persistedSub ? {
4902
4886
  productId: persistedSub.productId,
4903
- status: persistedSub.status,
4887
+ status: persistedStatus,
4904
4888
  expiresAt: persistedSub.expiresAt ?? null,
4905
4889
  platform: persistedSub.platform,
4906
4890
  autoRenewEnabled: persistedSub.willRenew,
4907
- inGracePeriod: persistedSub.status === "grace_period"
4891
+ inGracePeriod: persistedStatus === "in_grace_period"
4908
4892
  } : null;
4909
4893
  const stillActive = isSubscriptionStillActive(sub);
4910
4894
  if (stillActive) {
@@ -4942,14 +4926,14 @@ function useSubscriptionSync() {
4942
4926
  const persisted = await subscriptionCache.get(distinctId);
4943
4927
  if (!persisted?.data.subscription) return null;
4944
4928
  const s = persisted.data.subscription;
4945
- const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
4929
+ const status = s.status;
4946
4930
  return {
4947
4931
  productId: s.productId,
4948
- status: normalisedStatus,
4932
+ status,
4949
4933
  expiresAt: s.expiresAt ?? null,
4950
4934
  platform: s.platform,
4951
4935
  autoRenewEnabled: s.willRenew,
4952
- inGracePeriod: normalisedStatus === "in_grace_period"
4936
+ inGracePeriod: status === "in_grace_period"
4953
4937
  };
4954
4938
  } catch {
4955
4939
  return null;
@@ -5374,10 +5358,7 @@ function hasVariables(text) {
5374
5358
  var Paywallo = PaywalloClient;
5375
5359
  var index_default = Paywallo;
5376
5360
  export {
5377
- AFFILIATE_ERROR_CODES,
5378
5361
  ANALYTICS_ERROR_CODES,
5379
- AffiliateError,
5380
- AffiliateManager,
5381
5362
  AnalyticsError,
5382
5363
  ApiClient,
5383
5364
  CAMPAIGN_ERROR_CODES,
@@ -5395,7 +5376,6 @@ export {
5395
5376
  PaywallError,
5396
5377
  PaywallModal,
5397
5378
  Paywallo,
5398
- PaywalloAffiliate,
5399
5379
  PaywalloClient,
5400
5380
  PaywalloContext,
5401
5381
  PaywalloError,
@@ -5405,7 +5385,6 @@ export {
5405
5385
  SessionError,
5406
5386
  SessionManager,
5407
5387
  SubscriptionCache,
5408
- affiliateManager,
5409
5388
  createPurchaseError,
5410
5389
  index_default as default,
5411
5390
  detectDeviceLanguage,