@virex-tech/paywallo-sdk 2.4.0 → 2.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
@@ -262,77 +262,102 @@ var init_StorageMigration = __esm({
262
262
  }
263
263
  });
264
264
 
265
+ // src/errors/ClientError.ts
266
+ var ClientError, CLIENT_ERROR_CODES;
267
+ var init_ClientError = __esm({
268
+ "src/errors/ClientError.ts"() {
269
+ "use strict";
270
+ init_PaywalloError();
271
+ ClientError = class extends PaywalloError {
272
+ constructor(code, message) {
273
+ super("client", code, message);
274
+ this.name = "ClientError";
275
+ }
276
+ };
277
+ CLIENT_ERROR_CODES = {
278
+ NOT_INITIALIZED: "CLIENT_NOT_INITIALIZED",
279
+ MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
280
+ INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
281
+ PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
282
+ INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
283
+ UNKNOWN: "CLIENT_UNKNOWN"
284
+ };
285
+ }
286
+ });
287
+
265
288
  // src/utils/NativeDeviceInfo.ts
289
+ function assertExpoModules() {
290
+ if (!Device || Device.osName === void 0) {
291
+ throw new ClientError(
292
+ "MISSING_EXPO_DEPS",
293
+ "Paywallo SDK 3.x requires expo-device, expo-localization and expo-application. Run: npx expo install expo-device expo-localization expo-application"
294
+ );
295
+ }
296
+ }
266
297
  function setNativeDeviceInfoDebug(debug) {
267
298
  _debug2 = debug;
268
299
  }
269
- function isDeviceInfoResult(value) {
270
- if (typeof value !== "object" || value === null) return false;
271
- const v = value;
272
- return typeof v["deviceId"] === "string" && typeof v["model"] === "string" && typeof v["systemVersion"] === "string" && typeof v["appVersion"] === "string" && typeof v["bundleId"] === "string";
273
- }
274
- function getNativeModule() {
275
- const mod = import_react_native2.NativeModules.PaywalloDevice;
276
- if (!mod) {
277
- if (!warnedOnce) {
278
- warnedOnce = true;
279
- if (_debug2) console.log("[Paywallo] NativeModules.PaywalloDevice not found \u2014 falling back to defaults");
280
- }
281
- return null;
282
- }
283
- return mod;
300
+ function buildSyncFields(idfv) {
301
+ const locale = Localization.getLocales()[0] ?? null;
302
+ const calendar = Localization.getCalendars()[0] ?? null;
303
+ const androidId = import_react_native2.Platform.OS === "android" ? Application.getAndroidId() ?? "unknown" : "unknown";
304
+ return {
305
+ deviceId: import_react_native2.Platform.OS === "ios" ? idfv ?? "unknown" : androidId,
306
+ model: Device.modelName ?? "unknown",
307
+ modelId: Device.modelId ?? Device.modelName ?? "unknown",
308
+ osName: Device.osName ?? import_react_native2.Platform.OS,
309
+ systemVersion: Device.osVersion ?? "unknown",
310
+ appVersion: Application.nativeApplicationVersion ?? "unknown",
311
+ buildNumber: Application.nativeBuildVersion ?? "0",
312
+ bundleId: Application.applicationId ?? "unknown",
313
+ brand: Device.brand ?? "unknown",
314
+ idfv,
315
+ regionCode: locale?.regionCode ?? null,
316
+ languageTag: locale?.languageTag ?? null,
317
+ languageCode: locale?.languageCode ?? null,
318
+ timeZone: calendar?.timeZone ?? null
319
+ };
284
320
  }
285
321
  async function getDeviceInfo() {
286
322
  if (cachedDeviceInfo !== null) return cachedDeviceInfo;
287
- const mod = getNativeModule();
288
- if (!mod) return FALLBACK;
289
- const raw = await mod.getDeviceInfo();
290
- if (!isDeviceInfoResult(raw)) return FALLBACK;
291
- cachedDeviceInfo = raw;
292
- return raw;
323
+ assertExpoModules();
324
+ const idfv = import_react_native2.Platform.OS === "ios" ? await Application.getIosIdForVendorAsync() : null;
325
+ if (_debug2) console.log("[Paywallo] device info collected, idfv present:", idfv != null);
326
+ cachedDeviceInfo = buildSyncFields(idfv);
327
+ return cachedDeviceInfo;
293
328
  }
294
329
  async function getInstallReferrer() {
295
- const mod = getNativeModule();
296
- if (!mod) return null;
297
330
  try {
298
- return await mod.getInstallReferrer();
331
+ return await Application.getInstallReferrerAsync();
299
332
  } catch {
300
333
  return null;
301
334
  }
302
335
  }
303
336
  function isAvailable2() {
304
- return import_react_native2.NativeModules.PaywalloDevice != null;
337
+ return typeof Device !== "undefined" && Device.osName !== void 0;
305
338
  }
306
339
  function clearCache() {
307
340
  cachedDeviceInfo = null;
308
341
  }
309
342
  function getCachedDeviceInfo() {
310
- return cachedDeviceInfo;
343
+ if (cachedDeviceInfo !== null) return cachedDeviceInfo;
344
+ try {
345
+ assertExpoModules();
346
+ return buildSyncFields(null);
347
+ } catch {
348
+ return null;
349
+ }
311
350
  }
312
- var import_react_native2, FALLBACK, _debug2, warnedOnce, cachedDeviceInfo, nativeDeviceInfo;
351
+ var import_react_native2, Device, Application, Localization, _debug2, cachedDeviceInfo, nativeDeviceInfo;
313
352
  var init_NativeDeviceInfo = __esm({
314
353
  "src/utils/NativeDeviceInfo.ts"() {
315
354
  "use strict";
316
355
  import_react_native2 = require("react-native");
317
- FALLBACK = {
318
- deviceId: "unknown",
319
- model: "unknown",
320
- modelId: "unknown",
321
- systemVersion: "0.0.0",
322
- appVersion: "0.0.0",
323
- buildNumber: "0",
324
- bundleId: "unknown",
325
- brand: "unknown",
326
- systemName: "unknown",
327
- totalDisk: 0,
328
- freeDisk: 0,
329
- totalRam: 0,
330
- carrier: "unknown",
331
- installerPackage: null,
332
- idfv: null
333
- };
356
+ Device = __toESM(require("expo-device"));
357
+ Application = __toESM(require("expo-application"));
358
+ Localization = __toESM(require("expo-localization"));
359
+ init_ClientError();
334
360
  _debug2 = false;
335
- warnedOnce = false;
336
361
  cachedDeviceInfo = null;
337
362
  nativeDeviceInfo = {
338
363
  getDeviceInfo,
@@ -347,6 +372,7 @@ var init_NativeDeviceInfo = __esm({
347
372
  // src/utils/uuid.ts
348
373
  var uuid_exports = {};
349
374
  __export(uuid_exports, {
375
+ deterministicUUID: () => deterministicUUID,
350
376
  generateUUID: () => generateUUID,
351
377
  isValidUUIDv4: () => isValidUUIDv4
352
378
  });
@@ -358,6 +384,28 @@ function generateUUID() {
358
384
  return v.toString(16);
359
385
  });
360
386
  }
387
+ function deterministicUUID(seed) {
388
+ const fnv1a32 = (s, basis) => {
389
+ let h = basis >>> 0;
390
+ for (let i = 0; i < s.length; i++) {
391
+ h = Math.imul(h ^ s.charCodeAt(i), 16777619) >>> 0;
392
+ }
393
+ return h;
394
+ };
395
+ const B = 2166136261;
396
+ const h0 = fnv1a32(seed, B);
397
+ const h1 = fnv1a32(seed, B ^ 252645135);
398
+ const h2 = fnv1a32(seed, B ^ 4042322160);
399
+ const h3 = fnv1a32(seed, B ^ 2863311530);
400
+ const x = (n, len) => (n >>> 0).toString(16).padStart(len, "0").slice(-len);
401
+ const s1 = x(h0, 8);
402
+ const s2 = x(h1 >>> 16, 4);
403
+ const s3 = "4" + x(h1 & 4095, 3);
404
+ const vb = (8 | h2 >>> 30 & 3).toString(16);
405
+ const s4 = vb + x(h2 >>> 18 & 4095, 3);
406
+ const s5 = x(h2 & 65535, 4) + x(h3, 8);
407
+ return `${s1}-${s2}-${s3}-${s4}-${s5}`;
408
+ }
361
409
  function isValidUUIDv4(value) {
362
410
  if (!value) return false;
363
411
  return UUID_V4_REGEX.test(value);
@@ -625,6 +673,10 @@ var init_IdentityManager = __esm({
625
673
  if (!this.initialized) return "";
626
674
  return this.distinctId ?? this.anonId ?? "";
627
675
  }
676
+ /** Public stable identifier — returns the same value as `getDistinctId()`. */
677
+ getId() {
678
+ return this.getDistinctId();
679
+ }
628
680
  getDeviceId() {
629
681
  return this.deviceId;
630
682
  }
@@ -1028,10 +1080,8 @@ async function collectDeviceInfo() {
1028
1080
  const result = await nativeDeviceInfo.getDeviceInfo();
1029
1081
  return {
1030
1082
  appVersion: result.appVersion,
1031
- deviceModel: result.model,
1032
- osVersion: result.systemVersion,
1033
- darwinVersion: result.darwinVersion,
1034
- webkitVersion: result.webkitVersion
1083
+ deviceModel: result.modelId,
1084
+ osVersion: result.systemVersion
1035
1085
  };
1036
1086
  }
1037
1087
  var init_deviceInfo = __esm({
@@ -1426,6 +1476,12 @@ async function markInstallTracked(storage, installedAt) {
1426
1476
  } catch {
1427
1477
  }
1428
1478
  }
1479
+ async function resolveInstallEventId(deviceKey, appKey) {
1480
+ if (deviceKey && deviceKey.trim().length > 0) {
1481
+ return deterministicUUID(`${appKey}:${deviceKey}`);
1482
+ }
1483
+ return getOrCreateInstallEventId();
1484
+ }
1429
1485
  async function getOrCreateInstallEventId() {
1430
1486
  const existing = await nativeStorage.get(INSTALL_KEYS.INSTALL_EVENT_ID);
1431
1487
  if (existing) return existing;
@@ -1446,6 +1502,7 @@ var init_InstallIdempotency = __esm({
1446
1502
  "src/domains/identity/InstallIdempotency.ts"() {
1447
1503
  "use strict";
1448
1504
  init_NativeStorage();
1505
+ init_uuid();
1449
1506
  INSTALL_KEYS = {
1450
1507
  /** Primary flag — set AFTER a successful $app_installed dispatch. */
1451
1508
  INSTALL_TRACKED: "@paywallo:install_tracked",
@@ -1470,18 +1527,180 @@ var init_InstallIdempotency = __esm({
1470
1527
  }
1471
1528
  });
1472
1529
 
1530
+ // src/domains/identity/MetaBridge.ts
1531
+ var import_react_native5, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
1532
+ var init_MetaBridge = __esm({
1533
+ "src/domains/identity/MetaBridge.ts"() {
1534
+ "use strict";
1535
+ import_react_native5 = require("react-native");
1536
+ TIMEOUT_MS = 2e3;
1537
+ NativeBridge = import_react_native5.NativeModules.PaywalloFBBridge ?? null;
1538
+ MetaBridge = class {
1539
+ constructor() {
1540
+ this.cachedAnonId = null;
1541
+ this.cachedDeferredLink = null;
1542
+ this.deferredLinkFetched = false;
1543
+ this.debug = false;
1544
+ }
1545
+ setDebug(debug) {
1546
+ this.debug = debug;
1547
+ }
1548
+ /** Sync read of the cached anonymous ID. Returns null until `getAnonymousID()` resolves once. */
1549
+ getCachedAnonymousID() {
1550
+ return this.cachedAnonId;
1551
+ }
1552
+ async getAnonymousID() {
1553
+ if (this.cachedAnonId) return this.cachedAnonId;
1554
+ if (!NativeBridge) return null;
1555
+ try {
1556
+ let timerId;
1557
+ const timeout = new Promise((resolve) => {
1558
+ timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1559
+ });
1560
+ const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
1561
+ clearTimeout(timerId);
1562
+ if (result) this.cachedAnonId = result;
1563
+ return result;
1564
+ } catch {
1565
+ return null;
1566
+ }
1567
+ }
1568
+ async fetchDeferredAppLink() {
1569
+ if (this.deferredLinkFetched) return this.cachedDeferredLink;
1570
+ if (!NativeBridge) return null;
1571
+ if (typeof NativeBridge.fetchDeferredAppLink !== "function") return null;
1572
+ try {
1573
+ let timerId;
1574
+ const timeout = new Promise((resolve) => {
1575
+ timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1576
+ });
1577
+ const result = await Promise.race([
1578
+ NativeBridge.fetchDeferredAppLink().catch(() => null),
1579
+ timeout
1580
+ ]);
1581
+ clearTimeout(timerId);
1582
+ this.deferredLinkFetched = true;
1583
+ this.cachedDeferredLink = result;
1584
+ return result;
1585
+ } catch {
1586
+ this.deferredLinkFetched = true;
1587
+ this.cachedDeferredLink = null;
1588
+ return null;
1589
+ }
1590
+ }
1591
+ async logEvent(name, params = {}) {
1592
+ if (!NativeBridge) return;
1593
+ try {
1594
+ await NativeBridge.logEvent(name, params);
1595
+ } catch (error) {
1596
+ console.error("[Paywallo:MetaBridge] logEvent failed", { name, error });
1597
+ }
1598
+ }
1599
+ async logPurchase(amount, currency, params = {}) {
1600
+ if (!NativeBridge) return;
1601
+ try {
1602
+ await NativeBridge.logPurchase(amount, currency, params);
1603
+ } catch (error) {
1604
+ console.error("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
1605
+ }
1606
+ }
1607
+ setUserID(userId) {
1608
+ if (!NativeBridge) return;
1609
+ try {
1610
+ NativeBridge.setUserID(userId);
1611
+ } catch (error) {
1612
+ console.error("[Paywallo:MetaBridge] setUserID failed", { error });
1613
+ }
1614
+ }
1615
+ flush() {
1616
+ if (!NativeBridge) return;
1617
+ try {
1618
+ NativeBridge.flush();
1619
+ } catch (error) {
1620
+ console.error("[Paywallo:MetaBridge] flush failed", { error });
1621
+ }
1622
+ }
1623
+ };
1624
+ metaBridge = new MetaBridge();
1625
+ }
1626
+ });
1627
+
1628
+ // src/domains/identity/deferredAppLink.ts
1629
+ function parseQuery(s) {
1630
+ const result = {};
1631
+ for (const pair of s.split("&")) {
1632
+ const eqIndex = pair.indexOf("=");
1633
+ if (eqIndex === -1) continue;
1634
+ try {
1635
+ const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
1636
+ const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
1637
+ if (key) result[key] = value;
1638
+ } catch {
1639
+ }
1640
+ }
1641
+ return result;
1642
+ }
1643
+ function parseDeferredAppLink(url) {
1644
+ if (!url || url.trim() === "") return null;
1645
+ const raw = url.slice(0, 2048);
1646
+ const cleanRaw = raw.split("#")[0];
1647
+ const qIndex = cleanRaw.indexOf("?");
1648
+ if (qIndex === -1) return null;
1649
+ const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
1650
+ let fbclid = nz(topParams["fbclid"]);
1651
+ let utmSource = nz(topParams["utm_source"]);
1652
+ let utmMedium = nz(topParams["utm_medium"]);
1653
+ let utmCampaign = nz(topParams["utm_campaign"]);
1654
+ let ttclid = nz(topParams["ttclid"]);
1655
+ let trackingId = nz(topParams["tracking_id"]);
1656
+ let targetUrl = null;
1657
+ if (topParams["target_url"]) {
1658
+ try {
1659
+ targetUrl = decodeURIComponent(topParams["target_url"]);
1660
+ } catch {
1661
+ targetUrl = topParams["target_url"];
1662
+ }
1663
+ }
1664
+ if (!fbclid && targetUrl) {
1665
+ const cleanTargetUrl = targetUrl.split("#")[0];
1666
+ const innerQ = cleanTargetUrl.indexOf("?");
1667
+ if (innerQ !== -1) {
1668
+ const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
1669
+ fbclid = fbclid ?? nz(inner["fbclid"]);
1670
+ utmSource = utmSource ?? nz(inner["utm_source"]);
1671
+ utmMedium = utmMedium ?? nz(inner["utm_medium"]);
1672
+ utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
1673
+ ttclid = ttclid ?? nz(inner["ttclid"]);
1674
+ trackingId = trackingId ?? nz(inner["tracking_id"]);
1675
+ }
1676
+ }
1677
+ if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
1678
+ return null;
1679
+ }
1680
+ return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
1681
+ }
1682
+ var nz;
1683
+ var init_deferredAppLink = __esm({
1684
+ "src/domains/identity/deferredAppLink.ts"() {
1685
+ "use strict";
1686
+ nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
1687
+ }
1688
+ });
1689
+
1473
1690
  // src/domains/identity/InstallReferrerManager.ts
1474
- var import_react_native5, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
1691
+ var import_react_native6, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
1475
1692
  var init_InstallReferrerManager = __esm({
1476
1693
  "src/domains/identity/InstallReferrerManager.ts"() {
1477
1694
  "use strict";
1478
- import_react_native5 = require("react-native");
1695
+ import_react_native6 = require("react-native");
1479
1696
  init_NativeDeviceInfo();
1697
+ init_MetaBridge();
1698
+ init_deferredAppLink();
1480
1699
  REFERRER_TIMEOUT_MS = 5e3;
1481
1700
  InstallReferrerManager = class {
1482
1701
  async getReferrer() {
1483
- if (import_react_native5.Platform.OS !== "android") {
1484
- return this.emptyResult();
1702
+ if (import_react_native6.Platform.OS !== "android") {
1703
+ return this.fetchDeferredReferrer();
1485
1704
  }
1486
1705
  let timerId;
1487
1706
  const timeoutPromise = new Promise((resolve) => {
@@ -1492,6 +1711,29 @@ var init_InstallReferrerManager = __esm({
1492
1711
  clearTimeout(timerId);
1493
1712
  return result;
1494
1713
  }
1714
+ async fetchDeferredReferrer() {
1715
+ try {
1716
+ const url = await metaBridge.fetchDeferredAppLink();
1717
+ const parsed = parseDeferredAppLink(url);
1718
+ if (!parsed) return this.emptyResult();
1719
+ return {
1720
+ rawReferrer: parsed.targetUrl ?? parsed.raw,
1721
+ trackingId: parsed.trackingId,
1722
+ fbclid: parsed.fbclid,
1723
+ utmSource: parsed.utmSource,
1724
+ utmMedium: parsed.utmMedium,
1725
+ utmCampaign: parsed.utmCampaign,
1726
+ ttclid: parsed.ttclid,
1727
+ tiktokCampaignId: null,
1728
+ tiktokAdgroupId: null,
1729
+ tiktokAdId: null,
1730
+ installReferrerRaw: parsed.raw,
1731
+ installReferrerSource: "meta_deferred"
1732
+ };
1733
+ } catch {
1734
+ return this.emptyResult();
1735
+ }
1736
+ }
1495
1737
  async fetchReferrer() {
1496
1738
  try {
1497
1739
  const referrer = await nativeDeviceInfo.getInstallReferrer();
@@ -1552,79 +1794,6 @@ var init_InstallReferrerManager = __esm({
1552
1794
  }
1553
1795
  });
1554
1796
 
1555
- // src/domains/identity/MetaBridge.ts
1556
- var import_react_native6, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
1557
- var init_MetaBridge = __esm({
1558
- "src/domains/identity/MetaBridge.ts"() {
1559
- "use strict";
1560
- import_react_native6 = require("react-native");
1561
- TIMEOUT_MS = 2e3;
1562
- NativeBridge = import_react_native6.NativeModules.PaywalloFBBridge ?? null;
1563
- MetaBridge = class {
1564
- constructor() {
1565
- this.cachedAnonId = null;
1566
- this.debug = false;
1567
- }
1568
- setDebug(debug) {
1569
- this.debug = debug;
1570
- }
1571
- /** Sync read of the cached anonymous ID. Returns null until `getAnonymousID()` resolves once. */
1572
- getCachedAnonymousID() {
1573
- return this.cachedAnonId;
1574
- }
1575
- async getAnonymousID() {
1576
- if (this.cachedAnonId) return this.cachedAnonId;
1577
- if (!NativeBridge) return null;
1578
- try {
1579
- let timerId;
1580
- const timeout = new Promise((resolve) => {
1581
- timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1582
- });
1583
- const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
1584
- clearTimeout(timerId);
1585
- if (result) this.cachedAnonId = result;
1586
- return result;
1587
- } catch {
1588
- return null;
1589
- }
1590
- }
1591
- async logEvent(name, params = {}) {
1592
- if (!NativeBridge) return;
1593
- try {
1594
- await NativeBridge.logEvent(name, params);
1595
- } catch (error) {
1596
- console.error("[Paywallo:MetaBridge] logEvent failed", { name, error });
1597
- }
1598
- }
1599
- async logPurchase(amount, currency, params = {}) {
1600
- if (!NativeBridge) return;
1601
- try {
1602
- await NativeBridge.logPurchase(amount, currency, params);
1603
- } catch (error) {
1604
- console.error("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
1605
- }
1606
- }
1607
- setUserID(userId) {
1608
- if (!NativeBridge) return;
1609
- try {
1610
- NativeBridge.setUserID(userId);
1611
- } catch (error) {
1612
- console.error("[Paywallo:MetaBridge] setUserID failed", { error });
1613
- }
1614
- }
1615
- flush() {
1616
- if (!NativeBridge) return;
1617
- try {
1618
- NativeBridge.flush();
1619
- } catch (error) {
1620
- console.error("[Paywallo:MetaBridge] flush failed", { error });
1621
- }
1622
- }
1623
- };
1624
- metaBridge = new MetaBridge();
1625
- }
1626
- });
1627
-
1628
1797
  // src/domains/identity/InstallTracker.ts
1629
1798
  var import_react_native7, InstallTracker;
1630
1799
  var init_InstallTracker = __esm({
@@ -1694,44 +1863,17 @@ var init_InstallTracker = __esm({
1694
1863
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
1695
1864
  const info = await nativeDeviceInfo.getDeviceInfo();
1696
1865
  const deviceModel = info.modelId || info.model || "unknown";
1697
- const osVersion = info.systemVersion || "unknown";
1866
+ const osVersion2 = info.systemVersion || "unknown";
1698
1867
  const appVersion = info.appVersion || "unknown";
1699
1868
  const buildNumber = info.buildNumber || "0";
1700
- let carrier = "unknown";
1701
- try {
1702
- carrier = info.carrier ?? "unknown";
1703
- } catch {
1704
- }
1705
- let totalDisk = 0;
1706
- try {
1707
- totalDisk = Number(info.totalDisk) || 0;
1708
- } catch {
1709
- }
1710
- let freeDisk = 0;
1869
+ let brand2 = "unknown";
1711
1870
  try {
1712
- freeDisk = Number(info.freeDisk) || 0;
1713
- } catch {
1714
- }
1715
- let totalRam = 0;
1716
- try {
1717
- totalRam = Number(info.totalRam) || 0;
1718
- } catch {
1719
- }
1720
- let brand = "unknown";
1721
- try {
1722
- brand = info.brand ?? "unknown";
1723
- } catch {
1724
- }
1725
- let installerPackage = null;
1726
- try {
1727
- if (import_react_native7.Platform.OS === "android") {
1728
- installerPackage = info.installerPackage ?? null;
1729
- }
1871
+ brand2 = info.brand ?? "unknown";
1730
1872
  } catch {
1731
1873
  }
1732
1874
  deviceData = {
1733
1875
  deviceModel,
1734
- osVersion,
1876
+ osVersion: osVersion2,
1735
1877
  appVersion,
1736
1878
  buildNumber,
1737
1879
  ...screenWidth > 0 && { screenWidth },
@@ -1739,21 +1881,20 @@ var init_InstallTracker = __esm({
1739
1881
  ...screenDensity > 0 && { screenDensity },
1740
1882
  timezone,
1741
1883
  locale,
1742
- ...carrier !== "unknown" && { carrier },
1743
- ...totalDisk > 0 && { totalDisk },
1744
- ...freeDisk > 0 && { freeDisk },
1745
- ...totalRam > 0 && { totalRam },
1746
- ...brand !== "unknown" && { brand },
1747
- ...installerPackage && { installerPackage }
1884
+ ...brand2 !== "unknown" && { brand: brand2 },
1885
+ ...info.regionCode && { country: info.regionCode }
1748
1886
  };
1749
1887
  } catch {
1750
1888
  }
1751
- const [fbAnonId, referrer, adIds, installEventId] = await Promise.all([
1889
+ const [fbAnonId, referrer, adIds] = await Promise.all([
1752
1890
  metaBridge.getAnonymousID(),
1753
1891
  installReferrerManager.getReferrer(),
1754
- this.advertisingIdManager.collect(requestATT),
1755
- getOrCreateInstallEventId()
1892
+ this.advertisingIdManager.collect(requestATT)
1756
1893
  ]);
1894
+ const installEventId = await resolveInstallEventId(
1895
+ adIds.idfv ?? adIds.gaid ?? null,
1896
+ apiClient.getAppKey()
1897
+ );
1757
1898
  if (referrer.fbclid || referrer.utmSource || referrer.utmMedium || referrer.utmCampaign || referrer.ttclid || referrer.tiktokCampaignId || referrer.tiktokAdgroupId || referrer.tiktokAdId) {
1758
1899
  attributionTracker.capture({
1759
1900
  ...referrer.fbclid && { fbclid: referrer.fbclid },
@@ -1834,6 +1975,7 @@ var init_InstallTracker = __esm({
1834
1975
  screenHeight: deviceData.screenHeight,
1835
1976
  timezone: deviceData.timezone,
1836
1977
  language: deviceData.locale,
1978
+ ...deviceData.country && { country: deviceData.country },
1837
1979
  fbAnonId: anonId,
1838
1980
  installTimestamp: new Date(installedAt).toISOString()
1839
1981
  }),
@@ -2089,7 +2231,7 @@ var DEFAULT_API_URL, DEFAULT_WEB_URL;
2089
2231
  var init_constants = __esm({
2090
2232
  "src/core/constants.ts"() {
2091
2233
  "use strict";
2092
- DEFAULT_API_URL = "https://panel.lucasqueiroga.shop";
2234
+ DEFAULT_API_URL = "https://incommensurably-unprotrudent-brooklynn.ngrok-free.dev";
2093
2235
  DEFAULT_WEB_URL = "https://paywallo.com.br";
2094
2236
  }
2095
2237
  });
@@ -2421,8 +2563,8 @@ var init_NotificationHandlerSetup = __esm({
2421
2563
  // src/core/version.ts
2422
2564
  function resolveVersion() {
2423
2565
  try {
2424
- if ("2.4.0") {
2425
- return "2.4.0";
2566
+ if ("2.5.0") {
2567
+ return "2.5.0";
2426
2568
  }
2427
2569
  } catch {
2428
2570
  }
@@ -2464,8 +2606,8 @@ async function getLocale() {
2464
2606
  return clampLocale(raw);
2465
2607
  } catch {
2466
2608
  try {
2467
- const { getLocales } = require("expo-localization");
2468
- const code = getLocales()[0]?.languageCode ?? "en";
2609
+ const { getLocales: getLocales2 } = require("expo-localization");
2610
+ const code = getLocales2()[0]?.languageCode ?? "en";
2469
2611
  return clampLocale(code);
2470
2612
  } catch {
2471
2613
  return "en";
@@ -3768,292 +3910,17 @@ var init_PaywallWebView = __esm({
3768
3910
  }
3769
3911
  });
3770
3912
 
3771
- // src/domains/iap/PurchaseError.ts
3772
- function createPurchaseError(code, message) {
3773
- return new PurchaseError(
3774
- PURCHASE_ERROR_CODES[code],
3775
- message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
3776
- code === "USER_CANCELLED"
3777
- );
3778
- }
3779
- var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
3780
- var init_PurchaseError = __esm({
3781
- "src/domains/iap/PurchaseError.ts"() {
3782
- "use strict";
3783
- init_PaywalloError();
3784
- PurchaseError = class extends PaywalloError {
3785
- constructor(code, message, userCancelled = false, httpStatus) {
3786
- super("purchase", code, message);
3787
- this.name = "PurchaseError";
3788
- this.userCancelled = userCancelled;
3789
- this.httpStatus = httpStatus;
3790
- }
3791
- };
3792
- PURCHASE_ERROR_CODES = {
3793
- NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
3794
- PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
3795
- PURCHASE_FAILED: "PURCHASE_FAILED",
3796
- RESTORE_FAILED: "RESTORE_FAILED",
3797
- VALIDATION_FAILED: "VALIDATION_FAILED",
3798
- USER_CANCELLED: "USER_CANCELLED",
3799
- NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
3800
- STORE_ERROR: "PURCHASE_STORE_ERROR",
3801
- PENDING_PURCHASE: "PURCHASE_PENDING",
3802
- DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
3803
- STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
3804
- };
3805
- DEFAULT_MESSAGES = {
3806
- NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
3807
- PRODUCT_NOT_FOUND: "Product not found in store.",
3808
- PURCHASE_FAILED: "Purchase failed. Please try again.",
3809
- RESTORE_FAILED: "Failed to restore purchases. Please try again.",
3810
- VALIDATION_FAILED: "Failed to validate purchase with server.",
3811
- USER_CANCELLED: "Purchase was cancelled.",
3812
- NETWORK_ERROR: "Network error. Please check your connection.",
3813
- STORE_ERROR: "Store error. Please try again later.",
3814
- PENDING_PURCHASE: "Purchase is pending approval.",
3815
- DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
3816
- STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
3817
- };
3818
- }
3819
- });
3820
-
3821
- // src/domains/iap/NativeStoreKit.ts
3822
- var NativeStoreKit_exports = {};
3823
- __export(NativeStoreKit_exports, {
3824
- NativeStoreKit: () => NativeStoreKit,
3825
- nativeStoreKit: () => nativeStoreKit
3826
- });
3827
- function isAvailable3() {
3828
- return PaywalloStoreKitNative != null;
3829
- }
3830
- function mapNativeProduct(native) {
3831
- const product = {
3832
- productId: native.productId,
3833
- title: native.title,
3834
- description: native.description,
3835
- price: native.price,
3836
- priceValue: native.priceValue,
3837
- currency: native.currency ?? "USD",
3838
- localizedPrice: native.localizedPrice,
3839
- type: mapProductType(native.type),
3840
- subscriptionPeriod: native.subscriptionPeriod,
3841
- introductoryPrice: native.introductoryPrice,
3842
- introductoryPriceValue: native.introductoryPriceValue,
3843
- freeTrialPeriod: native.freeTrialPeriod
3844
- };
3845
- if (native.subscriptionPeriod) {
3846
- const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
3847
- product.pricePerMonth = prices.perMonth;
3848
- product.pricePerWeek = prices.perWeek;
3849
- product.pricePerDay = prices.perDay;
3850
- }
3851
- return product;
3852
- }
3853
- function mapProductType(type) {
3854
- switch (type) {
3855
- case "subscription":
3856
- case "autoRenewable":
3857
- return "subscription";
3858
- case "consumable":
3859
- return "consumable";
3860
- default:
3861
- return "non_consumable";
3862
- }
3863
- }
3864
- function calculatePerPeriodPrices(priceValue, period) {
3865
- const totalDays = periodToDays(period);
3866
- if (totalDays <= 0) return {};
3867
- const perDay = priceValue / totalDays;
3868
- const perWeek = perDay * 7;
3869
- const perMonth = perDay * 30;
3870
- return {
3871
- perDay: perDay.toFixed(2),
3872
- perWeek: perWeek.toFixed(2),
3873
- perMonth: perMonth.toFixed(2)
3874
- };
3875
- }
3876
- function periodToDays(period) {
3877
- const match = period.match(/^P(\d+)([DWMY])$/);
3878
- if (!match) return 0;
3879
- const value = parseInt(match[1], 10);
3880
- switch (match[2]) {
3881
- case "D":
3882
- return value;
3883
- case "W":
3884
- return value * 7;
3885
- case "M":
3886
- return value * 30;
3887
- case "Y":
3888
- return value * 365;
3889
- default:
3890
- return 0;
3891
- }
3892
- }
3893
- function mapNativePurchase(native) {
3894
- return {
3895
- productId: native.productId,
3896
- transactionId: native.transactionId,
3897
- transactionDate: native.transactionDate,
3898
- receipt: native.receipt,
3899
- platform: import_react_native16.Platform.OS
3900
- };
3901
- }
3902
- function normalizeTransactionUpdate(raw) {
3903
- if (!raw || typeof raw !== "object") return null;
3904
- const r = raw;
3905
- const type = r.type;
3906
- if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
3907
- const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
3908
- const productId = typeof r.productId === "string" ? r.productId : null;
3909
- if (!transactionId) return null;
3910
- const update = {
3911
- type,
3912
- transactionId,
3913
- productId: productId ?? ""
3914
- };
3915
- if (typeof r.amount === "number") update.amount = r.amount;
3916
- if (typeof r.currency === "string") update.currency = r.currency;
3917
- return update;
3918
- }
3919
- var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3920
- var init_NativeStoreKit = __esm({
3921
- "src/domains/iap/NativeStoreKit.ts"() {
3922
- "use strict";
3923
- import_react_native16 = require("react-native");
3924
- init_PaywalloClient();
3925
- init_uuid();
3926
- init_IdentityManager();
3927
- init_PurchaseError();
3928
- TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
3929
- PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
3930
- nativeStoreKit = {
3931
- isAvailable: isAvailable3,
3932
- async getProducts(productIds) {
3933
- if (!PaywalloStoreKitNative) {
3934
- return [];
3935
- }
3936
- const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
3937
- return nativeProducts.map(mapNativeProduct);
3938
- },
3939
- async purchase(productId) {
3940
- if (!PaywalloStoreKitNative) {
3941
- throw new PurchaseError(
3942
- PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
3943
- "StoreKit native module not available"
3944
- );
3945
- }
3946
- const distinctId = identityManager.getDistinctId();
3947
- const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
3948
- const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
3949
- if (distinctId && appAccountToken === null) {
3950
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
3951
- }
3952
- try {
3953
- const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
3954
- if (!result || result.pending) {
3955
- return null;
3956
- }
3957
- return mapNativePurchase(result);
3958
- } catch (err) {
3959
- const message = err instanceof Error ? err.message : String(err);
3960
- if (message.toLowerCase().includes("cancel")) {
3961
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
3962
- }
3963
- throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
3964
- }
3965
- },
3966
- async finishTransaction(transactionId) {
3967
- if (!PaywalloStoreKitNative) return;
3968
- try {
3969
- await PaywalloStoreKitNative.finishTransaction(transactionId);
3970
- } catch (err) {
3971
- console.error("[Paywallo] finishTransaction native error", err);
3972
- }
3973
- },
3974
- async getActiveTransactions() {
3975
- if (!PaywalloStoreKitNative) return [];
3976
- try {
3977
- const results = await PaywalloStoreKitNative.getActiveTransactions();
3978
- return results.map(mapNativePurchase);
3979
- } catch (err) {
3980
- console.error("[Paywallo] getActiveTransactions failed", err);
3981
- return [];
3982
- }
3983
- },
3984
- /**
3985
- * Subscribes to native transaction update events (renewals, refunds,
3986
- * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
3987
- * `PurchasesUpdatedListener` + refund diff on Android.
3988
- *
3989
- * Returns a subscription with a `remove()` method. The caller is
3990
- * responsible for cleanup (typically on `Paywallo.reset()`).
3991
- *
3992
- * Returns `null` when the native module isn't available (non-mobile env,
3993
- * native module not linked) — callers should treat null as a no-op.
3994
- */
3995
- subscribeToTransactionUpdates(listener) {
3996
- if (!PaywalloStoreKitNative) return null;
3997
- try {
3998
- const EmitterCtor = import_react_native16.NativeEventEmitter;
3999
- const emitter = new EmitterCtor(PaywalloStoreKitNative);
4000
- const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
4001
- const update = normalizeTransactionUpdate(payload);
4002
- if (update) listener(update);
4003
- });
4004
- return { remove: () => sub.remove() };
4005
- } catch (err) {
4006
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
4007
- return null;
4008
- }
4009
- }
4010
- };
4011
- NativeStoreKit = nativeStoreKit;
4012
- }
4013
- });
4014
-
4015
3913
  // src/domains/iap/IAPTransactionEmitter.ts
4016
3914
  var IAPTransactionEmitter;
4017
3915
  var init_IAPTransactionEmitter = __esm({
4018
3916
  "src/domains/iap/IAPTransactionEmitter.ts"() {
4019
3917
  "use strict";
4020
3918
  init_PaywalloClient();
4021
- init_NativeStoreKit();
4022
3919
  IAPTransactionEmitter = class {
4023
3920
  constructor(getProductFromCache, getApiClientFn, getDebug) {
4024
3921
  this.getProductFromCache = getProductFromCache;
4025
3922
  this.getApiClientFn = getApiClientFn;
4026
3923
  this.getDebug = getDebug;
4027
- this.transactionUpdateSubscription = null;
4028
- }
4029
- /**
4030
- * Emits `transaction {type: "completed"}` after a server-validated purchase.
4031
- * Fire-and-forget — a tracking failure must never poison the purchase flow.
4032
- */
4033
- async emitTransactionCompleted(purchase, productId, options) {
4034
- try {
4035
- const apiClient = this.getApiClientFn();
4036
- if (!apiClient) return;
4037
- const distinctId = PaywalloClient.getDistinctId();
4038
- if (!distinctId) return;
4039
- const product = this.getProductFromCache(productId);
4040
- const properties = {
4041
- type: "completed",
4042
- tx_id: purchase.transactionId,
4043
- product_id: purchase.productId
4044
- };
4045
- if (product?.priceValue !== void 0 && product.priceValue > 0) {
4046
- properties.amount = product.priceValue;
4047
- }
4048
- if (product?.currency) {
4049
- properties.currency = product.currency;
4050
- }
4051
- if (options?.paywallId) properties.paywall_id = options.paywallId;
4052
- if (options?.variantId) properties.variant_id = options.variantId;
4053
- await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4054
- } catch (err) {
4055
- if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
4056
- }
4057
3924
  }
4058
3925
  /**
4059
3926
  * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
@@ -4075,96 +3942,23 @@ var init_IAPTransactionEmitter = __esm({
4075
3942
  };
4076
3943
  await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
4077
3944
  priority: "critical"
4078
- });
4079
- if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
4080
- } catch (err) {
4081
- if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
4082
- }
4083
- }
4084
- /**
4085
- * Forwards a native transaction update to the EventBatcher.
4086
- * Public so tests can exercise the wiring without a fake NativeEventEmitter.
4087
- */
4088
- async emitTransactionUpdate(update) {
4089
- try {
4090
- const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
4091
- if (!apiClient) {
4092
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
4093
- return;
4094
- }
4095
- const distinctId = PaywalloClient.getDistinctId();
4096
- if (!distinctId) {
4097
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
4098
- return;
4099
- }
4100
- const properties = {
4101
- type: update.type,
4102
- tx_id: update.transactionId,
4103
- product_id: update.productId
4104
- };
4105
- if (update.type !== "canceled") {
4106
- const cached2 = this.getProductFromCache(update.productId);
4107
- const amount = update.amount ?? cached2?.priceValue;
4108
- const currency = update.currency ?? cached2?.currency;
4109
- if (typeof amount === "number" && amount > 0) properties.amount = amount;
4110
- if (currency) properties.currency = currency;
4111
- }
4112
- await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4113
- if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
4114
- } catch (err) {
4115
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
4116
- }
4117
- }
4118
- /**
4119
- * Starts listening to native Transaction.updates. Idempotent.
4120
- */
4121
- startListener() {
4122
- if (this.transactionUpdateSubscription) return;
4123
- this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
4124
- (update) => {
4125
- void this.emitTransactionUpdate(update);
4126
- }
4127
- );
4128
- if (this.getDebug() && this.transactionUpdateSubscription) {
4129
- console.log("[Paywallo IAP] transaction update listener started");
4130
- }
4131
- }
4132
- /**
4133
- * Removes the native listener. Safe to call multiple times.
4134
- */
4135
- stopListener() {
4136
- if (!this.transactionUpdateSubscription) return;
4137
- try {
4138
- this.transactionUpdateSubscription.remove();
4139
- } catch (err) {
4140
- if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
4141
- }
4142
- this.transactionUpdateSubscription = null;
4143
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
4144
- }
4145
- };
4146
- }
4147
- });
4148
-
4149
- // src/errors/ClientError.ts
4150
- var ClientError, CLIENT_ERROR_CODES;
4151
- var init_ClientError = __esm({
4152
- "src/errors/ClientError.ts"() {
4153
- "use strict";
4154
- init_PaywalloError();
4155
- ClientError = class extends PaywalloError {
4156
- constructor(code, message) {
4157
- super("client", code, message);
4158
- this.name = "ClientError";
3945
+ });
3946
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
3947
+ } catch (err) {
3948
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
3949
+ }
3950
+ }
3951
+ /**
3952
+ * No-op. Native transaction update listener removed — sale events are sourced
3953
+ * exclusively from server-side webhooks (Apple/Google/Superwall/RevenueCat).
3954
+ */
3955
+ startListener() {
3956
+ }
3957
+ /**
3958
+ * No-op. See startListener.
3959
+ */
3960
+ stopListener() {
4159
3961
  }
4160
- };
4161
- CLIENT_ERROR_CODES = {
4162
- NOT_INITIALIZED: "CLIENT_NOT_INITIALIZED",
4163
- MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
4164
- INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
4165
- PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
4166
- INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
4167
- UNKNOWN: "CLIENT_UNKNOWN"
4168
3962
  };
4169
3963
  }
4170
3964
  });
@@ -4536,11 +4330,11 @@ var init_OfflineQueue = __esm({
4536
4330
  });
4537
4331
  return true;
4538
4332
  }
4539
- const delay = Math.min(
4333
+ const delay2 = Math.min(
4540
4334
  this.config.baseRetryDelayMs * Math.pow(2, item.attempts - 1),
4541
4335
  this.config.maxRetryDelayMs
4542
4336
  );
4543
- item.nextRetryAt = Date.now() + delay;
4337
+ item.nextRetryAt = Date.now() + delay2;
4544
4338
  await this.storage.saveToStorage(this.queue);
4545
4339
  return false;
4546
4340
  }
@@ -4651,11 +4445,11 @@ var init_types2 = __esm({
4651
4445
  });
4652
4446
 
4653
4447
  // src/core/network/NetworkMonitor.ts
4654
- var import_react_native17, NetworkMonitorClass, networkMonitor;
4448
+ var import_react_native16, NetworkMonitorClass, networkMonitor;
4655
4449
  var init_NetworkMonitor = __esm({
4656
4450
  "src/core/network/NetworkMonitor.ts"() {
4657
4451
  "use strict";
4658
- import_react_native17 = require("react-native");
4452
+ import_react_native16 = require("react-native");
4659
4453
  init_types2();
4660
4454
  NetworkMonitorClass = class {
4661
4455
  constructor() {
@@ -4684,7 +4478,7 @@ var init_NetworkMonitor = __esm({
4684
4478
  }, this.config.checkInterval);
4685
4479
  }
4686
4480
  setupAppStateListener() {
4687
- this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
4481
+ this.appStateSubscription = import_react_native16.AppState.addEventListener("change", (state) => {
4688
4482
  if (state === "active") {
4689
4483
  if (!this.checkIntervalId) {
4690
4484
  this.checkIntervalId = setInterval(() => {
@@ -5052,12 +4846,62 @@ var init_queue = __esm({
5052
4846
  }
5053
4847
  });
5054
4848
 
4849
+ // src/domains/iap/PurchaseError.ts
4850
+ function createPurchaseError(code, message) {
4851
+ return new PurchaseError(
4852
+ PURCHASE_ERROR_CODES[code],
4853
+ message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
4854
+ code === "USER_CANCELLED"
4855
+ );
4856
+ }
4857
+ var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
4858
+ var init_PurchaseError = __esm({
4859
+ "src/domains/iap/PurchaseError.ts"() {
4860
+ "use strict";
4861
+ init_PaywalloError();
4862
+ PurchaseError = class extends PaywalloError {
4863
+ constructor(code, message, userCancelled = false, httpStatus) {
4864
+ super("purchase", code, message);
4865
+ this.name = "PurchaseError";
4866
+ this.userCancelled = userCancelled;
4867
+ this.httpStatus = httpStatus;
4868
+ }
4869
+ };
4870
+ PURCHASE_ERROR_CODES = {
4871
+ NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
4872
+ PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
4873
+ PURCHASE_FAILED: "PURCHASE_FAILED",
4874
+ RESTORE_FAILED: "RESTORE_FAILED",
4875
+ VALIDATION_FAILED: "VALIDATION_FAILED",
4876
+ USER_CANCELLED: "USER_CANCELLED",
4877
+ NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
4878
+ STORE_ERROR: "PURCHASE_STORE_ERROR",
4879
+ PENDING_PURCHASE: "PURCHASE_PENDING",
4880
+ DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
4881
+ STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
4882
+ };
4883
+ DEFAULT_MESSAGES = {
4884
+ NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
4885
+ PRODUCT_NOT_FOUND: "Product not found in store.",
4886
+ PURCHASE_FAILED: "Purchase failed. Please try again.",
4887
+ RESTORE_FAILED: "Failed to restore purchases. Please try again.",
4888
+ VALIDATION_FAILED: "Failed to validate purchase with server.",
4889
+ USER_CANCELLED: "Purchase was cancelled.",
4890
+ NETWORK_ERROR: "Network error. Please check your connection.",
4891
+ STORE_ERROR: "Store error. Please try again later.",
4892
+ PENDING_PURCHASE: "Purchase is pending approval.",
4893
+ DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
4894
+ STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
4895
+ };
4896
+ }
4897
+ });
4898
+
5055
4899
  // src/domains/iap/IAPValidator.ts
5056
- var import_react_native18, IAPValidator;
4900
+ var import_react_native17, IAPValidator;
5057
4901
  var init_IAPValidator = __esm({
5058
4902
  "src/domains/iap/IAPValidator.ts"() {
5059
4903
  "use strict";
5060
- import_react_native18 = require("react-native");
4904
+ import_react_native17 = require("react-native");
5061
4905
  init_PaywalloClient();
5062
4906
  init_queue();
5063
4907
  init_PurchaseError();
@@ -5078,75 +4922,269 @@ var init_IAPValidator = __esm({
5078
4922
  if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
5079
4923
  }
5080
4924
  }
5081
- throw lastError;
5082
- }
5083
- async validateWithServer(purchase, productId, options) {
5084
- const apiClient = this.getApiClientFn();
5085
- const product = this.getProductFromCache(productId);
5086
- const platform = import_react_native18.Platform.OS;
5087
- const distinctId = PaywalloClient.getDistinctId();
5088
- return this.validateWithRetry(
5089
- () => apiClient.validatePurchase(
5090
- platform,
5091
- purchase.receipt,
5092
- purchase.productId,
5093
- purchase.transactionId,
5094
- product?.priceValue ?? 0,
5095
- product?.currency ?? "USD",
5096
- this.getDeviceCountry(),
5097
- distinctId,
5098
- options?.paywallPlacement,
5099
- options?.variantKey
5100
- )
5101
- );
5102
- }
5103
- /**
5104
- * Bucketizes a validation failure into `server4xx` (permanent) vs `network` (transient).
5105
- */
5106
- classifyValidationError(error) {
5107
- if (error instanceof PurchaseError && typeof error.httpStatus === "number") {
5108
- const status = error.httpStatus;
5109
- if (status >= 400 && status < 500) return "server4xx";
4925
+ throw lastError;
4926
+ }
4927
+ async validateWithServer(purchase, productId, options) {
4928
+ const apiClient = this.getApiClientFn();
4929
+ const product = this.getProductFromCache(productId);
4930
+ const platform = import_react_native17.Platform.OS;
4931
+ const distinctId = PaywalloClient.getDistinctId();
4932
+ return this.validateWithRetry(
4933
+ () => apiClient.validatePurchase(
4934
+ platform,
4935
+ purchase.receipt,
4936
+ purchase.productId,
4937
+ purchase.transactionId,
4938
+ product?.priceValue ?? 0,
4939
+ product?.currency ?? "USD",
4940
+ this.getDeviceCountry(),
4941
+ distinctId,
4942
+ options?.paywallPlacement,
4943
+ options?.variantKey
4944
+ )
4945
+ );
4946
+ }
4947
+ /**
4948
+ * Bucketizes a validation failure into `server4xx` (permanent) vs `network` (transient).
4949
+ */
4950
+ classifyValidationError(error) {
4951
+ if (error instanceof PurchaseError && typeof error.httpStatus === "number") {
4952
+ const status = error.httpStatus;
4953
+ if (status >= 400 && status < 500) return "server4xx";
4954
+ }
4955
+ return "network";
4956
+ }
4957
+ /**
4958
+ * Pushes the validation payload into the offline queue for durable retry.
4959
+ * The StoreKit transaction is intentionally left un-finished.
4960
+ */
4961
+ async enqueueValidationRetry(purchase, productId, options) {
4962
+ try {
4963
+ const apiClient = this.getApiClientFn();
4964
+ const product = this.getProductFromCache(productId);
4965
+ const platform = import_react_native17.Platform.OS;
4966
+ const distinctId = PaywalloClient.getDistinctId();
4967
+ const appKey = apiClient.getAppKey();
4968
+ const body = {
4969
+ platform,
4970
+ receipt_data: purchase.receipt,
4971
+ product_id: purchase.productId,
4972
+ transaction_id: purchase.transactionId,
4973
+ price_local: product?.priceValue ?? 0,
4974
+ currency: product?.currency ?? "USD",
4975
+ country: this.getDeviceCountry(),
4976
+ distinct_id: distinctId
4977
+ };
4978
+ if (options?.paywallPlacement !== void 0) body.paywall_placement = options.paywallPlacement;
4979
+ if (options?.variantKey !== void 0) body.variant_key = options.variantKey;
4980
+ await offlineQueue.enqueue(
4981
+ "POST",
4982
+ "/sdk/purchases/validate",
4983
+ body,
4984
+ { "X-App-Key": appKey },
4985
+ `validate:${purchase.transactionId}`,
4986
+ "critical"
4987
+ );
4988
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
4989
+ } catch (err) {
4990
+ console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
4991
+ }
4992
+ }
4993
+ };
4994
+ }
4995
+ });
4996
+
4997
+ // src/domains/iap/NativeStoreKit.ts
4998
+ var NativeStoreKit_exports = {};
4999
+ __export(NativeStoreKit_exports, {
5000
+ NativeStoreKit: () => NativeStoreKit,
5001
+ nativeStoreKit: () => nativeStoreKit
5002
+ });
5003
+ function isAvailable3() {
5004
+ return PaywalloStoreKitNative != null;
5005
+ }
5006
+ function mapNativeProduct(native) {
5007
+ const product = {
5008
+ productId: native.productId,
5009
+ title: native.title,
5010
+ description: native.description,
5011
+ price: native.price,
5012
+ priceValue: native.priceValue,
5013
+ currency: native.currency ?? "USD",
5014
+ localizedPrice: native.localizedPrice,
5015
+ type: mapProductType(native.type),
5016
+ subscriptionPeriod: native.subscriptionPeriod,
5017
+ introductoryPrice: native.introductoryPrice,
5018
+ introductoryPriceValue: native.introductoryPriceValue,
5019
+ freeTrialPeriod: native.freeTrialPeriod
5020
+ };
5021
+ if (native.subscriptionPeriod) {
5022
+ const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
5023
+ product.pricePerMonth = prices.perMonth;
5024
+ product.pricePerWeek = prices.perWeek;
5025
+ product.pricePerDay = prices.perDay;
5026
+ }
5027
+ return product;
5028
+ }
5029
+ function mapProductType(type) {
5030
+ switch (type) {
5031
+ case "subscription":
5032
+ case "autoRenewable":
5033
+ return "subscription";
5034
+ case "consumable":
5035
+ return "consumable";
5036
+ default:
5037
+ return "non_consumable";
5038
+ }
5039
+ }
5040
+ function calculatePerPeriodPrices(priceValue, period) {
5041
+ const totalDays = periodToDays(period);
5042
+ if (totalDays <= 0) return {};
5043
+ const perDay = priceValue / totalDays;
5044
+ const perWeek = perDay * 7;
5045
+ const perMonth = perDay * 30;
5046
+ return {
5047
+ perDay: perDay.toFixed(2),
5048
+ perWeek: perWeek.toFixed(2),
5049
+ perMonth: perMonth.toFixed(2)
5050
+ };
5051
+ }
5052
+ function periodToDays(period) {
5053
+ const match = period.match(/^P(\d+)([DWMY])$/);
5054
+ if (!match) return 0;
5055
+ const value = parseInt(match[1], 10);
5056
+ switch (match[2]) {
5057
+ case "D":
5058
+ return value;
5059
+ case "W":
5060
+ return value * 7;
5061
+ case "M":
5062
+ return value * 30;
5063
+ case "Y":
5064
+ return value * 365;
5065
+ default:
5066
+ return 0;
5067
+ }
5068
+ }
5069
+ function mapNativePurchase(native) {
5070
+ return {
5071
+ productId: native.productId,
5072
+ transactionId: native.transactionId,
5073
+ transactionDate: native.transactionDate,
5074
+ receipt: native.receipt,
5075
+ platform: import_react_native18.Platform.OS
5076
+ };
5077
+ }
5078
+ function normalizeTransactionUpdate(raw) {
5079
+ if (!raw || typeof raw !== "object") return null;
5080
+ const r = raw;
5081
+ const type = r.type;
5082
+ if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
5083
+ const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
5084
+ const productId = typeof r.productId === "string" ? r.productId : null;
5085
+ if (!transactionId) return null;
5086
+ const update = {
5087
+ type,
5088
+ transactionId,
5089
+ productId: productId ?? ""
5090
+ };
5091
+ if (typeof r.amount === "number") update.amount = r.amount;
5092
+ if (typeof r.currency === "string") update.currency = r.currency;
5093
+ return update;
5094
+ }
5095
+ var import_react_native18, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
5096
+ var init_NativeStoreKit = __esm({
5097
+ "src/domains/iap/NativeStoreKit.ts"() {
5098
+ "use strict";
5099
+ import_react_native18 = require("react-native");
5100
+ init_PaywalloClient();
5101
+ init_uuid();
5102
+ init_IdentityManager();
5103
+ init_PurchaseError();
5104
+ TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
5105
+ PaywalloStoreKitNative = import_react_native18.NativeModules.PaywalloStoreKit ?? null;
5106
+ nativeStoreKit = {
5107
+ isAvailable: isAvailable3,
5108
+ async getProducts(productIds) {
5109
+ if (!PaywalloStoreKitNative) {
5110
+ return [];
5111
+ }
5112
+ const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
5113
+ return nativeProducts.map(mapNativeProduct);
5114
+ },
5115
+ async purchase(productId) {
5116
+ if (!PaywalloStoreKitNative) {
5117
+ throw new PurchaseError(
5118
+ PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
5119
+ "StoreKit native module not available"
5120
+ );
5121
+ }
5122
+ const distinctId = identityManager.getDistinctId();
5123
+ const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
5124
+ const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
5125
+ if (distinctId && appAccountToken === null) {
5126
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
5127
+ }
5128
+ try {
5129
+ const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
5130
+ if (!result || result.pending) {
5131
+ return null;
5132
+ }
5133
+ return mapNativePurchase(result);
5134
+ } catch (err) {
5135
+ const message = err instanceof Error ? err.message : String(err);
5136
+ if (message.toLowerCase().includes("cancel")) {
5137
+ throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
5138
+ }
5139
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
5140
+ }
5141
+ },
5142
+ async finishTransaction(transactionId) {
5143
+ if (!PaywalloStoreKitNative) return;
5144
+ try {
5145
+ await PaywalloStoreKitNative.finishTransaction(transactionId);
5146
+ } catch (err) {
5147
+ console.error("[Paywallo] finishTransaction native error", err);
5110
5148
  }
5111
- return "network";
5112
- }
5149
+ },
5150
+ async getActiveTransactions() {
5151
+ if (!PaywalloStoreKitNative) return [];
5152
+ try {
5153
+ const results = await PaywalloStoreKitNative.getActiveTransactions();
5154
+ return results.map(mapNativePurchase);
5155
+ } catch (err) {
5156
+ console.error("[Paywallo] getActiveTransactions failed", err);
5157
+ return [];
5158
+ }
5159
+ },
5113
5160
  /**
5114
- * Pushes the validation payload into the offline queue for durable retry.
5115
- * The StoreKit transaction is intentionally left un-finished.
5161
+ * Subscribes to native transaction update events (renewals, refunds,
5162
+ * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
5163
+ * `PurchasesUpdatedListener` + refund diff on Android.
5164
+ *
5165
+ * Returns a subscription with a `remove()` method. The caller is
5166
+ * responsible for cleanup (typically on `Paywallo.reset()`).
5167
+ *
5168
+ * Returns `null` when the native module isn't available (non-mobile env,
5169
+ * native module not linked) — callers should treat null as a no-op.
5116
5170
  */
5117
- async enqueueValidationRetry(purchase, productId, options) {
5171
+ subscribeToTransactionUpdates(listener) {
5172
+ if (!PaywalloStoreKitNative) return null;
5118
5173
  try {
5119
- const apiClient = this.getApiClientFn();
5120
- const product = this.getProductFromCache(productId);
5121
- const platform = import_react_native18.Platform.OS;
5122
- const distinctId = PaywalloClient.getDistinctId();
5123
- const appKey = apiClient.getAppKey();
5124
- const body = {
5125
- platform,
5126
- receipt_data: purchase.receipt,
5127
- product_id: purchase.productId,
5128
- transaction_id: purchase.transactionId,
5129
- price_local: product?.priceValue ?? 0,
5130
- currency: product?.currency ?? "USD",
5131
- country: this.getDeviceCountry(),
5132
- distinct_id: distinctId
5133
- };
5134
- if (options?.paywallPlacement !== void 0) body.paywall_placement = options.paywallPlacement;
5135
- if (options?.variantKey !== void 0) body.variant_key = options.variantKey;
5136
- await offlineQueue.enqueue(
5137
- "POST",
5138
- "/sdk/purchases/validate",
5139
- body,
5140
- { "X-App-Key": appKey },
5141
- `validate:${purchase.transactionId}`,
5142
- "critical"
5143
- );
5144
- if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
5174
+ const EmitterCtor = import_react_native18.NativeEventEmitter;
5175
+ const emitter = new EmitterCtor(PaywalloStoreKitNative);
5176
+ const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
5177
+ const update = normalizeTransactionUpdate(payload);
5178
+ if (update) listener(update);
5179
+ });
5180
+ return { remove: () => sub.remove() };
5145
5181
  } catch (err) {
5146
- console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
5182
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
5183
+ return null;
5147
5184
  }
5148
5185
  }
5149
5186
  };
5187
+ NativeStoreKit = nativeStoreKit;
5150
5188
  }
5151
5189
  });
5152
5190
 
@@ -5250,8 +5288,8 @@ var init_IAPService = __esm({
5250
5288
  stopTransactionListener() {
5251
5289
  this.emitter.stopListener();
5252
5290
  }
5253
- async emitTransactionUpdate(update) {
5254
- return this.emitter.emitTransactionUpdate(update);
5291
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
5292
+ async emitTransactionUpdate(_update) {
5255
5293
  }
5256
5294
  async purchase(productId, options) {
5257
5295
  if (!nativeStoreKit.isAvailable()) {
@@ -5280,10 +5318,6 @@ var init_IAPService = __esm({
5280
5318
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
5281
5319
  await this.finishTransaction(purchase.transactionId);
5282
5320
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
5283
- await this.emitter.emitTransactionCompleted(purchase, productId, {
5284
- paywallId: options?.paywallId,
5285
- variantId: options?.variantId
5286
- });
5287
5321
  }).catch(async (err) => {
5288
5322
  const kind = this.validator.classifyValidationError(err);
5289
5323
  if (kind === "server4xx") {
@@ -6370,12 +6404,149 @@ var init_plan = __esm({
6370
6404
  }
6371
6405
  });
6372
6406
 
6373
- // src/domains/session/index.ts
6374
- var init_session = __esm({
6375
- "src/domains/session/index.ts"() {
6407
+ // src/domains/session/index.ts
6408
+ var init_session = __esm({
6409
+ "src/domains/session/index.ts"() {
6410
+ "use strict";
6411
+ init_SessionError();
6412
+ init_SessionManager();
6413
+ }
6414
+ });
6415
+
6416
+ // src/domains/onboarding/OnboardingError.ts
6417
+ var OnboardingError, ONBOARDING_ERROR_CODES;
6418
+ var init_OnboardingError = __esm({
6419
+ "src/domains/onboarding/OnboardingError.ts"() {
6420
+ "use strict";
6421
+ init_PaywalloError();
6422
+ OnboardingError = class extends PaywalloError {
6423
+ constructor(code, message) {
6424
+ super("onboarding", code, message);
6425
+ this.name = "OnboardingError";
6426
+ }
6427
+ };
6428
+ ONBOARDING_ERROR_CODES = {
6429
+ NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
6430
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
6431
+ };
6432
+ }
6433
+ });
6434
+
6435
+ // src/domains/onboarding/OnboardingManager.ts
6436
+ var OnboardingManager, onboardingManager;
6437
+ var init_OnboardingManager = __esm({
6438
+ "src/domains/onboarding/OnboardingManager.ts"() {
6439
+ "use strict";
6440
+ init_OnboardingError();
6441
+ OnboardingManager = class {
6442
+ constructor() {
6443
+ this.pipeline = null;
6444
+ this.debug = false;
6445
+ this.lastStep = null;
6446
+ this.finished = false;
6447
+ }
6448
+ /**
6449
+ * Injects runtime dependencies. Called by PaywalloClient during init.
6450
+ */
6451
+ injectDeps(config) {
6452
+ this.pipeline = config.pipeline;
6453
+ this.debug = config.debug ?? false;
6454
+ }
6455
+ /**
6456
+ * Tracks an onboarding step view.
6457
+ * Saves the step name internally for implicit drop detection on the backend.
6458
+ *
6459
+ * @param stepName - Unique name for this onboarding step.
6460
+ * @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
6461
+ */
6462
+ async step(stepName, order) {
6463
+ this.assertConfig();
6464
+ this.validateStepName(stepName, "step");
6465
+ this.lastStep = stepName;
6466
+ const stepPayload = { step_name: stepName };
6467
+ if (order !== void 0 && Number.isFinite(order) && order >= 0) {
6468
+ stepPayload.order = order;
6469
+ }
6470
+ const payload = {
6471
+ family: "onboarding",
6472
+ type: "step",
6473
+ ...stepPayload
6474
+ };
6475
+ return this.emit("onboarding", payload, "step");
6476
+ }
6477
+ /**
6478
+ * Tracks successful completion of the onboarding flow.
6479
+ * Marks the flow as finished.
6480
+ */
6481
+ async complete() {
6482
+ this.assertConfig();
6483
+ this.finished = true;
6484
+ return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
6485
+ }
6486
+ /**
6487
+ * Tracks an explicit drop (abandonment) at the given step.
6488
+ * Marks the flow as finished.
6489
+ *
6490
+ * @param stepName - The step name where the user dropped off.
6491
+ */
6492
+ async drop(stepName) {
6493
+ this.assertConfig();
6494
+ this.validateStepName(stepName, "drop");
6495
+ this.finished = true;
6496
+ const payload = {
6497
+ family: "onboarding",
6498
+ type: "drop",
6499
+ ...{ step_name: stepName }
6500
+ };
6501
+ return this.emit("onboarding", payload, "drop");
6502
+ }
6503
+ /** Returns the last step name seen, or null if no step was tracked yet. */
6504
+ getLastStep() {
6505
+ return this.lastStep;
6506
+ }
6507
+ /** Returns whether the flow has been marked as finished (complete or drop). */
6508
+ isFinished() {
6509
+ return this.finished;
6510
+ }
6511
+ async emit(eventName, properties, logLabel) {
6512
+ const pipeline = this.pipeline;
6513
+ const distinctId = pipeline.getDistinctId();
6514
+ if (!distinctId) {
6515
+ if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
6516
+ return;
6517
+ }
6518
+ await pipeline.trackEvent(eventName, distinctId, properties);
6519
+ if (this.debug) {
6520
+ console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
6521
+ }
6522
+ }
6523
+ assertConfig() {
6524
+ if (!this.pipeline) {
6525
+ throw new OnboardingError(
6526
+ ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
6527
+ "OnboardingManager not initialized. Call Paywallo.init() first."
6528
+ );
6529
+ }
6530
+ }
6531
+ validateStepName(stepName, method) {
6532
+ if (typeof stepName !== "string" || stepName.trim().length === 0) {
6533
+ throw new OnboardingError(
6534
+ ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
6535
+ `OnboardingManager.${method}(): stepName must be a non-empty string.`
6536
+ );
6537
+ }
6538
+ }
6539
+ };
6540
+ onboardingManager = new OnboardingManager();
6541
+ }
6542
+ });
6543
+
6544
+ // src/domains/onboarding/index.ts
6545
+ var init_onboarding = __esm({
6546
+ "src/domains/onboarding/index.ts"() {
6376
6547
  "use strict";
6377
- init_SessionError();
6378
- init_SessionManager();
6548
+ init_OnboardingError();
6549
+ init_OnboardingManager();
6379
6550
  }
6380
6551
  });
6381
6552
 
@@ -6536,268 +6707,124 @@ function validateEvent(eventName, properties) {
6536
6707
  return { ok: false, family, error: parsed.error };
6537
6708
  }
6538
6709
  function formatValidationError(error) {
6539
- return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
6540
- }
6541
- var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
6542
- var init_eventFamilies = __esm({
6543
- "src/core/events/eventFamilies.ts"() {
6544
- "use strict";
6545
- init_schemas();
6546
- CANONICAL_FAMILIES = [
6547
- "lifecycle",
6548
- "identify",
6549
- "paywall",
6550
- "transaction",
6551
- "onboarding",
6552
- "notification",
6553
- "custom"
6554
- ];
6555
- DEPRECATED_EVENT_NAMES = [
6556
- "$paywall_purchased",
6557
- "$paywall_product_selected",
6558
- "$core_action",
6559
- "$campaign_impression",
6560
- "$app_open",
6561
- "$app_background",
6562
- "$app_foreground",
6563
- "session.end"
6564
- ];
6565
- }
6566
- });
6567
-
6568
- // src/core/PaywalloAnalytics.ts
6569
- function isValidEventName(name) {
6570
- if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6571
- return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6572
- }
6573
- async function trackEvent(apiClient, state, eventName, options) {
6574
- const distinctId = identityManager.getDistinctId();
6575
- if (!distinctId) {
6576
- console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
6577
- return;
6578
- }
6579
- if (!isValidEventName(eventName)) {
6580
- const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
6581
- throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
6582
- }
6583
- if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
6584
- const sessionId = sessionManager.getSessionId();
6585
- const family = detectFamily(eventName);
6586
- const isCustomFamily = family === "custom";
6587
- const identityProps = isCustomFamily ? identityManager.getProperties() : {};
6588
- const sessionProp = isCustomFamily && sessionId ? { sessionId } : {};
6589
- await apiClient.trackEvent(
6590
- eventName,
6591
- distinctId,
6592
- { ...identityProps, ...options?.properties, ...sessionProp },
6593
- options?.timestamp,
6594
- options?.priority ? { priority: options.priority } : void 0
6595
- );
6596
- }
6597
- async function trackOnboardingStep(apiClient, state, index, name) {
6598
- const distinctId = identityManager.getDistinctId();
6599
- if (!distinctId) return;
6600
- if (!Number.isInteger(index) || index < 0) {
6601
- const msg = `Invalid stepIndex: "${index}". Must be a non-negative integer.`;
6602
- throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_STEP_INDEX, msg);
6603
- }
6604
- const now = Date.now();
6605
- const timeOnPreviousStep = state.lastOnboardingStepTimestamp !== null ? Math.round((now - state.lastOnboardingStepTimestamp) / 1e3) : 0;
6606
- state.lastOnboardingStepTimestamp = now;
6607
- await apiClient.trackEvent("$onboarding_step", distinctId, { stepIndex: index, stepName: name, timeOnPreviousStep });
6608
- }
6609
- function validateCoreAction(actionName) {
6610
- if (!actionName || typeof actionName !== "string") {
6611
- const msg = `Invalid actionName: "${actionName}". Must be a non-empty string.`;
6612
- throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_ACTION_NAME, msg);
6613
- }
6614
- }
6615
- var init_PaywalloAnalytics = __esm({
6616
- "src/core/PaywalloAnalytics.ts"() {
6617
- "use strict";
6618
- init_analytics();
6619
- init_identity();
6620
- init_session();
6621
- init_ClientError();
6622
- init_eventFamilies();
6623
- }
6624
- });
6625
-
6626
- // src/core/PaywalloFlags.ts
6627
- async function getVariantCached(apiClient, flagKey, defaultValue) {
6628
- const distinctId = identityManager.getDistinctId();
6629
- const result = await apiClient.getVariantFromCache(flagKey, distinctId);
6630
- if (result) return result;
6631
- void apiClient.getVariant(flagKey, distinctId).catch(() => {
6632
- });
6633
- return { variant: defaultValue ?? null, payload: void 0 };
6634
- }
6635
- async function getVariant(apiClient, state, flagKey) {
6636
- try {
6637
- const distinctId = identityManager.getDistinctId();
6638
- if (!distinctId) {
6639
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
6640
- return { variant: null };
6641
- }
6642
- const result = await apiClient.getVariant(flagKey, distinctId);
6643
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
6644
- return result;
6645
- } catch (error) {
6646
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
6647
- if (state.apiClient) void state.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
6648
- return { variant: null };
6649
- }
6650
- }
6651
- function evaluateFlags(apiClient, keys) {
6652
- return apiClient.evaluateFlags(keys, identityManager.getDistinctId());
6653
- }
6654
- async function getConditionalFlag(apiClient, state, flagKey, context) {
6655
- try {
6656
- const distinctId = identityManager.getDistinctId();
6657
- if (!distinctId) return false;
6658
- const result = await apiClient.getConditionalFlag(flagKey, { distinctId, ...context });
6659
- return result.value;
6660
- } catch (error) {
6661
- if (state.apiClient) void state.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
6662
- return false;
6663
- }
6664
- }
6665
- var init_PaywalloFlags = __esm({
6666
- "src/core/PaywalloFlags.ts"() {
6667
- "use strict";
6668
- init_identity();
6669
- }
6670
- });
6671
-
6672
- // src/domains/onboarding/OnboardingError.ts
6673
- var OnboardingError, ONBOARDING_ERROR_CODES;
6674
- var init_OnboardingError = __esm({
6675
- "src/domains/onboarding/OnboardingError.ts"() {
6676
- "use strict";
6677
- init_PaywalloError();
6678
- OnboardingError = class extends PaywalloError {
6679
- constructor(code, message) {
6680
- super("onboarding", code, message);
6681
- this.name = "OnboardingError";
6682
- }
6683
- };
6684
- ONBOARDING_ERROR_CODES = {
6685
- NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
6686
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
6687
- };
6688
- }
6689
- });
6690
-
6691
- // src/domains/onboarding/OnboardingManager.ts
6692
- var OnboardingManager, onboardingManager;
6693
- var init_OnboardingManager = __esm({
6694
- "src/domains/onboarding/OnboardingManager.ts"() {
6695
- "use strict";
6696
- init_OnboardingError();
6697
- OnboardingManager = class {
6698
- constructor() {
6699
- this.pipeline = null;
6700
- this.debug = false;
6701
- this.lastStep = null;
6702
- this.finished = false;
6703
- }
6704
- /**
6705
- * Injects runtime dependencies. Called by PaywalloClient during init.
6706
- */
6707
- injectDeps(config) {
6708
- this.pipeline = config.pipeline;
6709
- this.debug = config.debug ?? false;
6710
- }
6711
- /**
6712
- * Tracks an onboarding step view.
6713
- * Saves the step name internally for implicit drop detection on the backend.
6714
- *
6715
- * @param stepName - Unique name for this onboarding step.
6716
- */
6717
- async step(stepName) {
6718
- this.assertConfig();
6719
- this.validateStepName(stepName, "step");
6720
- this.lastStep = stepName;
6721
- const payload = {
6722
- family: "onboarding",
6723
- type: "step",
6724
- ...{ step_name: stepName }
6725
- };
6726
- return this.emit("onboarding", payload, "step");
6727
- }
6728
- /**
6729
- * Tracks successful completion of the onboarding flow.
6730
- * Marks the flow as finished.
6731
- */
6732
- async complete() {
6733
- this.assertConfig();
6734
- this.finished = true;
6735
- return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
6736
- }
6737
- /**
6738
- * Tracks an explicit drop (abandonment) at the given step.
6739
- * Marks the flow as finished.
6740
- *
6741
- * @param stepName - The step name where the user dropped off.
6742
- */
6743
- async drop(stepName) {
6744
- this.assertConfig();
6745
- this.validateStepName(stepName, "drop");
6746
- this.finished = true;
6747
- const payload = {
6748
- family: "onboarding",
6749
- type: "drop",
6750
- ...{ step_name: stepName }
6751
- };
6752
- return this.emit("onboarding", payload, "drop");
6753
- }
6754
- /** Returns the last step name seen, or null if no step was tracked yet. */
6755
- getLastStep() {
6756
- return this.lastStep;
6757
- }
6758
- /** Returns whether the flow has been marked as finished (complete or drop). */
6759
- isFinished() {
6760
- return this.finished;
6761
- }
6762
- async emit(eventName, properties, logLabel) {
6763
- const pipeline = this.pipeline;
6764
- const distinctId = pipeline.getDistinctId();
6765
- if (!distinctId) {
6766
- if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
6767
- return;
6768
- }
6769
- await pipeline.trackEvent(eventName, distinctId, properties);
6770
- if (this.debug) {
6771
- console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
6772
- }
6773
- }
6774
- assertConfig() {
6775
- if (!this.pipeline) {
6776
- throw new OnboardingError(
6777
- ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
6778
- "OnboardingManager not initialized. Call Paywallo.init() first."
6779
- );
6780
- }
6781
- }
6782
- validateStepName(stepName, method) {
6783
- if (typeof stepName !== "string" || stepName.trim().length === 0) {
6784
- throw new OnboardingError(
6785
- ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
6786
- `OnboardingManager.${method}(): stepName must be a non-empty string.`
6787
- );
6788
- }
6789
- }
6790
- };
6791
- onboardingManager = new OnboardingManager();
6710
+ return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
6711
+ }
6712
+ var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
6713
+ var init_eventFamilies = __esm({
6714
+ "src/core/events/eventFamilies.ts"() {
6715
+ "use strict";
6716
+ init_schemas();
6717
+ CANONICAL_FAMILIES = [
6718
+ "lifecycle",
6719
+ "identify",
6720
+ "paywall",
6721
+ "transaction",
6722
+ "onboarding",
6723
+ "notification",
6724
+ "custom"
6725
+ ];
6726
+ DEPRECATED_EVENT_NAMES = [
6727
+ "$paywall_purchased",
6728
+ "$paywall_product_selected",
6729
+ "$core_action",
6730
+ "$campaign_impression",
6731
+ "$app_open",
6732
+ "$app_background",
6733
+ "$app_foreground",
6734
+ "session.end"
6735
+ ];
6792
6736
  }
6793
6737
  });
6794
6738
 
6795
- // src/domains/onboarding/index.ts
6796
- var init_onboarding = __esm({
6797
- "src/domains/onboarding/index.ts"() {
6739
+ // src/core/PaywalloAnalytics.ts
6740
+ function isValidEventName(name) {
6741
+ if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6742
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6743
+ }
6744
+ async function trackEvent(apiClient, state, eventName, options) {
6745
+ const distinctId = identityManager.getDistinctId();
6746
+ if (!distinctId) {
6747
+ console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
6748
+ return;
6749
+ }
6750
+ if (!isValidEventName(eventName)) {
6751
+ const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
6752
+ throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
6753
+ }
6754
+ if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
6755
+ const sessionId = sessionManager.getSessionId();
6756
+ const family = detectFamily(eventName);
6757
+ const isCustomFamily = family === "custom";
6758
+ const identityProps = isCustomFamily ? identityManager.getProperties() : {};
6759
+ const sessionProp = isCustomFamily && sessionId ? { sessionId } : {};
6760
+ await apiClient.trackEvent(
6761
+ eventName,
6762
+ distinctId,
6763
+ { ...identityProps, ...options?.properties, ...sessionProp },
6764
+ options?.timestamp,
6765
+ options?.priority ? { priority: options.priority } : void 0
6766
+ );
6767
+ }
6768
+ function validateCoreAction(actionName) {
6769
+ if (!actionName || typeof actionName !== "string") {
6770
+ const msg = `Invalid actionName: "${actionName}". Must be a non-empty string.`;
6771
+ throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_ACTION_NAME, msg);
6772
+ }
6773
+ }
6774
+ var init_PaywalloAnalytics = __esm({
6775
+ "src/core/PaywalloAnalytics.ts"() {
6798
6776
  "use strict";
6799
- init_OnboardingError();
6800
- init_OnboardingManager();
6777
+ init_analytics();
6778
+ init_identity();
6779
+ init_session();
6780
+ init_ClientError();
6781
+ init_eventFamilies();
6782
+ }
6783
+ });
6784
+
6785
+ // src/core/PaywalloFlags.ts
6786
+ async function getVariantCached(apiClient, flagKey, defaultValue) {
6787
+ const distinctId = identityManager.getDistinctId();
6788
+ const result = await apiClient.getVariantFromCache(flagKey, distinctId);
6789
+ if (result) return result;
6790
+ void apiClient.getVariant(flagKey, distinctId).catch(() => {
6791
+ });
6792
+ return { variant: defaultValue ?? null, payload: void 0 };
6793
+ }
6794
+ async function getVariant(apiClient, state, flagKey) {
6795
+ try {
6796
+ const distinctId = identityManager.getDistinctId();
6797
+ if (!distinctId) {
6798
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
6799
+ return { variant: null };
6800
+ }
6801
+ const result = await apiClient.getVariant(flagKey, distinctId);
6802
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
6803
+ return result;
6804
+ } catch (error) {
6805
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
6806
+ if (state.apiClient) void state.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
6807
+ return { variant: null };
6808
+ }
6809
+ }
6810
+ function evaluateFlags(apiClient, keys) {
6811
+ return apiClient.evaluateFlags(keys, identityManager.getDistinctId());
6812
+ }
6813
+ async function getConditionalFlag(apiClient, state, flagKey, context) {
6814
+ try {
6815
+ const distinctId = identityManager.getDistinctId();
6816
+ if (!distinctId) return false;
6817
+ const result = await apiClient.getConditionalFlag(flagKey, { distinctId, ...context });
6818
+ return result.value;
6819
+ } catch (error) {
6820
+ if (state.apiClient) void state.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
6821
+ return false;
6822
+ }
6823
+ }
6824
+ var init_PaywalloFlags = __esm({
6825
+ "src/core/PaywalloFlags.ts"() {
6826
+ "use strict";
6827
+ init_identity();
6801
6828
  }
6802
6829
  });
6803
6830
 
@@ -7132,6 +7159,36 @@ var init_SubscriptionManager = __esm({
7132
7159
  }
7133
7160
  });
7134
7161
 
7162
+ // src/utils/detectEnvironment.ts
7163
+ async function detectEnvironment() {
7164
+ if (cached !== null) return cached;
7165
+ if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
7166
+ cached = "sandbox";
7167
+ return cached;
7168
+ }
7169
+ if (!nativeModule) {
7170
+ cached = "production";
7171
+ return cached;
7172
+ }
7173
+ try {
7174
+ const result = await nativeModule.detect();
7175
+ cached = result === "sandbox" ? "sandbox" : "production";
7176
+ return cached;
7177
+ } catch {
7178
+ cached = "production";
7179
+ return cached;
7180
+ }
7181
+ }
7182
+ var import_react_native23, nativeModule, cached;
7183
+ var init_detectEnvironment = __esm({
7184
+ "src/utils/detectEnvironment.ts"() {
7185
+ "use strict";
7186
+ import_react_native23 = require("react-native");
7187
+ nativeModule = import_react_native23.NativeModules.PaywalloEnv ?? null;
7188
+ cached = null;
7189
+ }
7190
+ });
7191
+
7135
7192
  // src/domains/iap/apiPurchaseMethods.ts
7136
7193
  async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
7137
7194
  const response = await client.post(
@@ -7471,21 +7528,42 @@ function buildV2Envelope(events, providerContext) {
7471
7528
  if (family === "custom") {
7472
7529
  payload.event_name = event.eventName;
7473
7530
  }
7474
- return {
7475
- id: generateUUID(),
7476
- ts: event.timestamp,
7477
- name: family,
7478
- payload
7479
- };
7531
+ const candidateId = payload.installEventId;
7532
+ let id;
7533
+ if (typeof candidateId === "string" && isValidUUIDv4(candidateId)) {
7534
+ id = candidateId;
7535
+ delete payload.installEventId;
7536
+ } else {
7537
+ id = generateUUID();
7538
+ }
7539
+ return { id, ts: event.timestamp, name: family, payload };
7480
7540
  });
7481
7541
  if (context.sdk_version === void 0) context.sdk_version = SDK_VERSION;
7482
- if (context.platform === void 0) context.platform = import_react_native23.Platform.OS;
7542
+ if (context.platform === void 0) context.platform = import_react_native24.Platform.OS;
7483
7543
  if (context.distinct_id === void 0 && events.length > 0) {
7484
7544
  context.distinct_id = events[0].distinctId;
7485
7545
  }
7486
7546
  if (!context.distinct_id || context.distinct_id.length === 0) {
7487
7547
  console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
7488
7548
  }
7549
+ const idsFromProps = {};
7550
+ const existingIds = context.ids;
7551
+ for (const event of events) {
7552
+ for (const { key, aliases } of IDS_PROMO_ALIASES) {
7553
+ if (existingIds?.[key] !== void 0) continue;
7554
+ if (idsFromProps[key] !== void 0) continue;
7555
+ for (const alias of aliases) {
7556
+ const val = event.properties[alias];
7557
+ if (typeof val === "string" && val.length > 0) {
7558
+ idsFromProps[key] = val;
7559
+ break;
7560
+ }
7561
+ }
7562
+ }
7563
+ }
7564
+ if (Object.keys(idsFromProps).length > 0) {
7565
+ context.ids = { ...idsFromProps, ...context.ids };
7566
+ }
7489
7567
  return { context, events: v2Events };
7490
7568
  }
7491
7569
  function resolveProviderContext(provider) {
@@ -7519,11 +7597,11 @@ function splitPayloadAndContext(properties) {
7519
7597
  }
7520
7598
  return { payload, promoted };
7521
7599
  }
7522
- var import_react_native23, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7600
+ var import_react_native24, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, IDS_PROMO_ALIASES, CONTEXT_KEY_ALIASES;
7523
7601
  var init_eventEnvelopeV2 = __esm({
7524
7602
  "src/core/eventEnvelopeV2.ts"() {
7525
7603
  "use strict";
7526
- import_react_native23 = require("react-native");
7604
+ import_react_native24 = require("react-native");
7527
7605
  import_zod2 = require("zod");
7528
7606
  init_uuid();
7529
7607
  init_eventFamilies();
@@ -7547,6 +7625,7 @@ var init_eventEnvelopeV2 = __esm({
7547
7625
  device_model: import_zod2.z.string().optional(),
7548
7626
  timezone: import_zod2.z.string().optional(),
7549
7627
  locale: import_zod2.z.string().optional(),
7628
+ country: import_zod2.z.string().optional(),
7550
7629
  carrier: import_zod2.z.string().optional(),
7551
7630
  screen_width: import_zod2.z.number().optional(),
7552
7631
  screen_height: import_zod2.z.number().optional(),
@@ -7558,6 +7637,12 @@ var init_eventEnvelopeV2 = __esm({
7558
7637
  context: v2ContextSchema,
7559
7638
  events: import_zod2.z.array(v2EventSchema).min(1)
7560
7639
  });
7640
+ IDS_PROMO_ALIASES = [
7641
+ { key: "idfv", aliases: ["idfv"] },
7642
+ { key: "gaid", aliases: ["gaid"] },
7643
+ { key: "fb_anon_id", aliases: ["fbAnonId", "fb_anon_id"] },
7644
+ { key: "idfa", aliases: ["idfa"] }
7645
+ ];
7561
7646
  CONTEXT_KEY_ALIASES = {
7562
7647
  distinct_id: ["distinct_id", "distinctId"],
7563
7648
  session_id: ["session_id", "sessionId"],
@@ -7574,6 +7659,7 @@ var init_eventEnvelopeV2 = __esm({
7574
7659
  device_model: ["device_model", "deviceModel", "model"],
7575
7660
  timezone: ["timezone", "timeZone"],
7576
7661
  locale: ["locale"],
7662
+ country: ["country", "regionCode"],
7577
7663
  carrier: ["carrier_ctx"],
7578
7664
  screen_width: ["screen_width"],
7579
7665
  screen_height: ["screen_height"],
@@ -7593,11 +7679,11 @@ var init_events = __esm({
7593
7679
  });
7594
7680
 
7595
7681
  // src/core/EventBatcher.ts
7596
- var import_react_native24, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7682
+ var import_react_native25, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7597
7683
  var init_EventBatcher = __esm({
7598
7684
  "src/core/EventBatcher.ts"() {
7599
7685
  "use strict";
7600
- import_react_native24 = require("react-native");
7686
+ import_react_native25 = require("react-native");
7601
7687
  init_eventEnvelopeV2();
7602
7688
  init_events();
7603
7689
  BATCH_MAX_SIZE = 25;
@@ -7646,7 +7732,7 @@ var init_EventBatcher = __esm({
7646
7732
  const event = {
7647
7733
  eventName,
7648
7734
  distinctId,
7649
- properties: { ...properties, platform: import_react_native24.Platform.OS },
7735
+ properties: { ...properties, platform: import_react_native25.Platform.OS },
7650
7736
  timestamp: timestamp ?? Date.now(),
7651
7737
  environment: environment.toLowerCase()
7652
7738
  };
@@ -7813,8 +7899,8 @@ var init_HttpClient = __esm({
7813
7899
  if (this.shouldRetry(response.status, retryConfig)) {
7814
7900
  if (state.attempt < retryConfig.maxRetries) {
7815
7901
  state.attempt++;
7816
- const delay = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
7817
- await this.sleep(delay);
7902
+ const delay2 = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
7903
+ await this.sleep(delay2);
7818
7904
  continue;
7819
7905
  }
7820
7906
  }
@@ -7823,8 +7909,8 @@ var init_HttpClient = __esm({
7823
7909
  state.lastError = error instanceof Error ? error : new ClientError(CLIENT_ERROR_CODES.UNKNOWN, String(error));
7824
7910
  if (this.isNetworkError(state.lastError) && state.attempt < retryConfig.maxRetries) {
7825
7911
  state.attempt++;
7826
- const delay = this.calculateDelay(state.attempt, retryConfig);
7827
- await this.sleep(delay);
7912
+ const delay2 = this.calculateDelay(state.attempt, retryConfig);
7913
+ await this.sleep(delay2);
7828
7914
  continue;
7829
7915
  }
7830
7916
  throw state.lastError;
@@ -8001,11 +8087,11 @@ function normalizeGender(raw) {
8001
8087
  if (lower === "f" || lower === "female") return "f";
8002
8088
  return void 0;
8003
8089
  }
8004
- var import_react_native25, DATE_OF_BIRTH_RE, ApiClient;
8090
+ var import_react_native26, DATE_OF_BIRTH_RE, ApiClient;
8005
8091
  var init_ApiClient = __esm({
8006
8092
  "src/core/ApiClient.ts"() {
8007
8093
  "use strict";
8008
- import_react_native25 = require("react-native");
8094
+ import_react_native26 = require("react-native");
8009
8095
  init_apiPurchaseMethods();
8010
8096
  init_ApiCache();
8011
8097
  init_ApiClientFlags();
@@ -8108,7 +8194,7 @@ var init_ApiClient = __esm({
8108
8194
  buildSdkHeaders() {
8109
8195
  return {
8110
8196
  "x-sdk-version": SDK_VERSION,
8111
- "x-sdk-platform": import_react_native25.Platform.OS,
8197
+ "x-sdk-platform": import_react_native26.Platform.OS,
8112
8198
  "x-sdk-environment": this.environment
8113
8199
  };
8114
8200
  }
@@ -8138,7 +8224,7 @@ var init_ApiClient = __esm({
8138
8224
  message,
8139
8225
  context,
8140
8226
  sdkVersion: SDK_VERSION,
8141
- platform: import_react_native25.Platform.OS
8227
+ platform: import_react_native26.Platform.OS
8142
8228
  }, true);
8143
8229
  } catch (err) {
8144
8230
  if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
@@ -8156,7 +8242,7 @@ var init_ApiClient = __esm({
8156
8242
  async identify(distinctId, properties, email, deviceId, pii) {
8157
8243
  const rawTraits = {
8158
8244
  email: email || void 0,
8159
- platform: import_react_native25.Platform.OS,
8245
+ platform: import_react_native26.Platform.OS,
8160
8246
  name: properties?.name,
8161
8247
  country: properties?.country,
8162
8248
  locale: properties?.locale,
@@ -8352,7 +8438,7 @@ function setupNotifications(apiClient, config, environment, eventPipeline) {
8352
8438
  notificationsManager.setupHandlers({ eventBatcher: eventPipeline });
8353
8439
  notificationsManager.initialize({
8354
8440
  appKey: config.appKey,
8355
- apiBaseUrl: config.apiUrl ?? DEFAULT_API_URL,
8441
+ apiBaseUrl: DEFAULT_API_URL,
8356
8442
  debug: config.debug,
8357
8443
  environment: environment === "Sandbox" ? "sandbox" : "production"
8358
8444
  }).catch((err) => {
@@ -8431,36 +8517,6 @@ var init_PaywalloState = __esm({
8431
8517
  }
8432
8518
  });
8433
8519
 
8434
- // src/utils/detectEnvironment.ts
8435
- async function detectEnvironment() {
8436
- if (cached !== null) return cached;
8437
- if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
8438
- cached = "sandbox";
8439
- return cached;
8440
- }
8441
- if (!nativeModule) {
8442
- cached = "production";
8443
- return cached;
8444
- }
8445
- try {
8446
- const result = await nativeModule.detect();
8447
- cached = result === "sandbox" ? "sandbox" : "production";
8448
- return cached;
8449
- } catch {
8450
- cached = "production";
8451
- return cached;
8452
- }
8453
- }
8454
- var import_react_native26, nativeModule, cached;
8455
- var init_detectEnvironment = __esm({
8456
- "src/utils/detectEnvironment.ts"() {
8457
- "use strict";
8458
- import_react_native26 = require("react-native");
8459
- nativeModule = import_react_native26.NativeModules.PaywalloEnv ?? null;
8460
- cached = null;
8461
- }
8462
- });
8463
-
8464
8520
  // src/core/PaywalloInitializer.ts
8465
8521
  async function initWithRetry(state, config) {
8466
8522
  while (state.initAttempts <= MAX_INIT_RETRIES) {
@@ -8475,8 +8531,8 @@ async function initWithRetry(state, config) {
8475
8531
  } catch (error) {
8476
8532
  state.initAttempts++;
8477
8533
  if (state.initAttempts > MAX_INIT_RETRIES) throw error;
8478
- const delay = RETRY_DELAYS[state.initAttempts - 1] ?? 1e4;
8479
- await new Promise((resolve) => setTimeout(resolve, delay));
8534
+ const delay2 = RETRY_DELAYS[state.initAttempts - 1] ?? 1e4;
8535
+ await new Promise((resolve) => setTimeout(resolve, delay2));
8480
8536
  }
8481
8537
  }
8482
8538
  }
@@ -8506,7 +8562,7 @@ function reportInitFailure(state, config, error, reason) {
8506
8562
  try {
8507
8563
  const tempClient = new ApiClient(
8508
8564
  config.appKey,
8509
- config.apiUrl ?? DEFAULT_API_URL,
8565
+ DEFAULT_API_URL,
8510
8566
  config.debug
8511
8567
  );
8512
8568
  void tempClient.reportError("INIT_FAILED", reason, {
@@ -8556,7 +8612,7 @@ async function doInit(state, config) {
8556
8612
  ]);
8557
8613
  const apiClient = new ApiClient(
8558
8614
  config.appKey,
8559
- config.apiUrl ?? DEFAULT_API_URL,
8615
+ DEFAULT_API_URL,
8560
8616
  config.debug,
8561
8617
  environment,
8562
8618
  offlineQueueEnabled,
@@ -8630,8 +8686,8 @@ async function doInit(state, config) {
8630
8686
  let timezone;
8631
8687
  try {
8632
8688
  const opts = Intl.DateTimeFormat().resolvedOptions();
8633
- locale = opts.locale || void 0;
8634
- timezone = opts.timeZone || void 0;
8689
+ locale = device?.languageTag || opts.locale || void 0;
8690
+ timezone = device?.timeZone || opts.timeZone || void 0;
8635
8691
  } catch {
8636
8692
  }
8637
8693
  let screenWidth;
@@ -8655,7 +8711,7 @@ async function doInit(state, config) {
8655
8711
  device_model: device?.modelId || device?.model || void 0,
8656
8712
  timezone,
8657
8713
  locale,
8658
- carrier: device?.carrier || void 0,
8714
+ ...device?.regionCode && { country: device.regionCode },
8659
8715
  screen_width: screenWidth,
8660
8716
  screen_height: screenHeight,
8661
8717
  screen_density: screenDensity,
@@ -8723,6 +8779,7 @@ var init_PaywalloInitializer = __esm({
8723
8779
  init_plan();
8724
8780
  init_session();
8725
8781
  init_SubscriptionManager();
8782
+ init_detectEnvironment();
8726
8783
  init_NativeDeviceInfo();
8727
8784
  init_ApiClient();
8728
8785
  init_constants();
@@ -8732,7 +8789,6 @@ var init_PaywalloInitializer = __esm({
8732
8789
  init_PaywalloState();
8733
8790
  init_queue();
8734
8791
  init_NativeStorage();
8735
- init_detectEnvironment();
8736
8792
  }
8737
8793
  });
8738
8794
 
@@ -8853,6 +8909,7 @@ var init_PaywalloClient = __esm({
8853
8909
  init_plan();
8854
8910
  init_session();
8855
8911
  init_ClientError();
8912
+ init_onboarding();
8856
8913
  init_network();
8857
8914
  init_PaywalloAnalytics();
8858
8915
  init_PaywalloFlags();
@@ -8922,8 +8979,9 @@ var init_PaywalloClient = __esm({
8922
8979
  async track(eventName, options) {
8923
8980
  return trackEvent(this.getApiClientOrThrow(), this.state, eventName, options);
8924
8981
  }
8925
- async onboardingStep(index, name) {
8926
- return trackOnboardingStep(this.getApiClientOrThrow(), this.state, index, name);
8982
+ async onboardingStep(order, name) {
8983
+ this.ensureInitialized();
8984
+ return onboardingManager.step(name, order);
8927
8985
  }
8928
8986
  async coreAction(actionName) {
8929
8987
  this.ensureInitialized();
@@ -9010,6 +9068,9 @@ var init_PaywalloClient = __esm({
9010
9068
  getConfig() {
9011
9069
  return this.state.config;
9012
9070
  }
9071
+ getId() {
9072
+ return identityManager.getDistinctId();
9073
+ }
9013
9074
  getDistinctId() {
9014
9075
  return identityManager.getDistinctId();
9015
9076
  }
@@ -9310,6 +9371,7 @@ __export(index_exports, {
9310
9371
  createPurchaseError: () => createPurchaseError,
9311
9372
  default: () => index_default,
9312
9373
  detectDeviceLanguage: () => detectDeviceLanguage,
9374
+ getActiveRouteName: () => getActiveRouteName,
9313
9375
  getCurrentLanguage: () => getCurrentLanguage,
9314
9376
  getDefaultLanguage: () => getDefaultLanguage,
9315
9377
  getIAPService: () => getIAPService,
@@ -9338,6 +9400,7 @@ __export(index_exports, {
9338
9400
  useOnboarding: () => useOnboarding,
9339
9401
  usePaywallContext: () => usePaywallContext,
9340
9402
  usePaywallo: () => usePaywallo,
9403
+ usePaywalloScreenTracking: () => usePaywalloScreenTracking,
9341
9404
  usePlanPurchase: () => usePlanPurchase,
9342
9405
  usePlans: () => usePlans,
9343
9406
  useProducts: () => useProducts,
@@ -9472,7 +9535,7 @@ var import_react15 = require("react");
9472
9535
  init_onboarding();
9473
9536
  function useOnboarding() {
9474
9537
  const step = (0, import_react15.useCallback)(
9475
- (stepName) => onboardingManager.step(stepName),
9538
+ (stepName, order) => onboardingManager.step(stepName, order),
9476
9539
  []
9477
9540
  );
9478
9541
  const complete = (0, import_react15.useCallback)(
@@ -9816,8 +9879,41 @@ function useSubscription() {
9816
9879
  };
9817
9880
  }
9818
9881
 
9882
+ // src/hooks/usePaywalloScreenTracking.ts
9883
+ var import_react22 = require("react");
9884
+ init_PaywalloClient();
9885
+ function isNavState(value) {
9886
+ return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
9887
+ }
9888
+ function getActiveRouteName(state) {
9889
+ try {
9890
+ if (!isNavState(state)) return null;
9891
+ const route = state.routes[state.index];
9892
+ if (!route) return null;
9893
+ if (isNavState(route.state)) {
9894
+ return getActiveRouteName(route.state);
9895
+ }
9896
+ return typeof route.name === "string" ? route.name : null;
9897
+ } catch {
9898
+ return null;
9899
+ }
9900
+ }
9901
+ function usePaywalloScreenTracking() {
9902
+ const lastScreenRef = (0, import_react22.useRef)(null);
9903
+ return (0, import_react22.useCallback)((state) => {
9904
+ if (!PaywalloClient.isReady()) return;
9905
+ const screenName = getActiveRouteName(state);
9906
+ if (!screenName || screenName === lastScreenRef.current) return;
9907
+ lastScreenRef.current = screenName;
9908
+ void PaywalloClient.track("screen_view", {
9909
+ properties: { screen_name: screenName }
9910
+ }).catch(() => {
9911
+ });
9912
+ }, []);
9913
+ }
9914
+
9819
9915
  // src/PaywalloProvider.tsx
9820
- var import_react27 = require("react");
9916
+ var import_react28 = require("react");
9821
9917
  var import_react_native29 = require("react-native");
9822
9918
  init_PaywalloClient();
9823
9919
  init_NativeStorage();
@@ -9832,12 +9928,17 @@ init_paywall();
9832
9928
 
9833
9929
  // src/domains/paywall/SuperwallAttributeBridge.ts
9834
9930
  init_identity();
9931
+ var CONFIG_POLL_INTERVAL_MS = 250;
9932
+ var CONFIG_POLL_MAX_ATTEMPTS = 40;
9835
9933
  var lastSignature = null;
9836
9934
  function log(debug, msg, data) {
9837
9935
  if (!debug) return;
9838
9936
  if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
9839
9937
  else console.log(`[Paywallo:SuperwallAttr] ${msg}`);
9840
9938
  }
9939
+ function delay(ms) {
9940
+ return new Promise((resolve) => setTimeout(resolve, ms));
9941
+ }
9841
9942
  var PAID_MEDIUM = /cpc|ppc|cpm|cpa|paid|display/i;
9842
9943
  function deriveIsPaid(a) {
9843
9944
  if (a.fbclid || a.gclid || a.ttclid || a.tiktokCampaignId) return true;
@@ -9876,24 +9977,38 @@ function buildSuperwallAttributes(a) {
9876
9977
  out.pw_attributed_at = a.capturedAt;
9877
9978
  return out;
9878
9979
  }
9879
- function pickShared(mod) {
9880
- const shared = mod?.default?.shared ?? mod?.shared;
9881
- return shared && typeof shared.setUserAttributes === "function" ? shared : null;
9980
+ function pickNative(mod) {
9981
+ const native = mod?.SuperwallExpoModule ?? mod?.default?.SuperwallExpoModule;
9982
+ return native && typeof native.setUserAttributes === "function" && typeof native.getConfigurationStatus === "function" ? native : null;
9882
9983
  }
9883
- async function resolveSuperwallShared(debug) {
9884
- try {
9885
- const shared = pickShared(await import("expo-superwall/compat"));
9886
- if (shared) return shared;
9887
- } catch {
9888
- }
9984
+ async function resolveSuperwallNativeModule(debug) {
9889
9985
  try {
9890
- const shared = pickShared(await import("expo-superwall"));
9891
- if (shared) return shared;
9986
+ const native = pickNative(await import("expo-superwall"));
9987
+ if (native) return native;
9892
9988
  } catch {
9893
9989
  }
9894
- log(debug, "expo-superwall not available \u2014 attribute push skipped");
9990
+ log(debug, "expo-superwall native module not available \u2014 attribute push skipped");
9895
9991
  return null;
9896
9992
  }
9993
+ async function waitForConfigured(mod, debug) {
9994
+ for (let attempt = 0; attempt < CONFIG_POLL_MAX_ATTEMPTS; attempt++) {
9995
+ let status;
9996
+ try {
9997
+ status = String(await mod.getConfigurationStatus());
9998
+ } catch (err) {
9999
+ log(debug, "getConfigurationStatus error", { err: String(err) });
10000
+ return false;
10001
+ }
10002
+ if (status === "CONFIGURED") return true;
10003
+ if (status === "FAILED") {
10004
+ log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
10005
+ return false;
10006
+ }
10007
+ await delay(CONFIG_POLL_INTERVAL_MS);
10008
+ }
10009
+ log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
10010
+ return false;
10011
+ }
9897
10012
  async function pushAttributionToSuperwall(debug = false) {
9898
10013
  const attrs = buildSuperwallAttributes(attributionTracker.get());
9899
10014
  const signature = JSON.stringify(attrs);
@@ -9901,10 +10016,11 @@ async function pushAttributionToSuperwall(debug = false) {
9901
10016
  log(debug, "attributes unchanged \u2014 skipping push");
9902
10017
  return;
9903
10018
  }
9904
- const shared = await resolveSuperwallShared(debug);
9905
- if (!shared) return;
10019
+ const native = await resolveSuperwallNativeModule(debug);
10020
+ if (!native) return;
10021
+ if (!await waitForConfigured(native, debug)) return;
9906
10022
  try {
9907
- await shared.setUserAttributes(attrs);
10023
+ await native.setUserAttributes(attrs);
9908
10024
  lastSignature = signature;
9909
10025
  log(debug, "attributes pushed to Superwall", attrs);
9910
10026
  } catch (err) {
@@ -10020,50 +10136,9 @@ function buildCallbacks() {
10020
10136
  const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
10021
10137
  log2("onPurchase", { productId, identifier });
10022
10138
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
10023
- },
10024
- // Global Superwall event listener — used specifically for `transactionComplete`
10025
- // which is the only event that exposes the full product (currency/price) +
10026
- // transaction (storeTransactionId) shape needed to create a server-side
10027
- // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
10028
- // handled above with their typed callbacks).
10029
- onSuperwallEvent: (info) => {
10030
- const ev = info?.event;
10031
- if (!ev || ev.event !== "transactionComplete") return;
10032
- const ts = Date.now();
10033
- const product = ev.product;
10034
- const transaction = ev.transaction;
10035
- if (!product) {
10036
- log2("transactionComplete missing product \u2014 skipping transaction track");
10037
- return;
10038
- }
10039
- const productId = product.productIdentifier ?? product.id ?? "unknown";
10040
- const currency = product.currencyCode ?? "USD";
10041
- const price = product.price ?? 0;
10042
- const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
10043
- const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
10044
- const paywallIdentifier = ev.paywallInfo?.identifier;
10045
- log2("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
10046
- void (async () => {
10047
- try {
10048
- await PaywalloClient.track("transaction", {
10049
- priority: "critical",
10050
- properties: {
10051
- type: isTrial ? "trial_started" : "completed",
10052
- // Server's IngestTranslator looks for `transaction_id`/`product_id`
10053
- // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
10054
- // then resolves type=trial/new/renewal based on amount being 0 vs >0.
10055
- transaction_id: transactionId,
10056
- product_id: productId,
10057
- amount: price,
10058
- currency,
10059
- ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
10060
- }
10061
- });
10062
- } catch (err) {
10063
- log2("transactionComplete track error", { err: String(err) });
10064
- }
10065
- })();
10066
10139
  }
10140
+ // onSuperwallEvent removed — transaction sale events are sourced exclusively
10141
+ // from server-side webhooks (Apple/Google/Superwall/RevenueCat).
10067
10142
  };
10068
10143
  }
10069
10144
  function startSuperwallAutoBridge(debug = false) {
@@ -10107,7 +10182,7 @@ init_session();
10107
10182
  init_ClientError();
10108
10183
 
10109
10184
  // src/hooks/useAppAutoEvents.ts
10110
- var import_react22 = require("react");
10185
+ var import_react23 = require("react");
10111
10186
 
10112
10187
  // src/utils/platformDetection.ts
10113
10188
  async function detectPlatform3() {
@@ -10133,8 +10208,8 @@ var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
10133
10208
  var DEBOUNCE_MS = 1e3;
10134
10209
  function useAppAutoEvents(config) {
10135
10210
  const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
10136
- const didRunRef = (0, import_react22.useRef)(false);
10137
- (0, import_react22.useEffect)(() => {
10211
+ const didRunRef = (0, import_react23.useRef)(false);
10212
+ (0, import_react23.useEffect)(() => {
10138
10213
  if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
10139
10214
  if (!isInitialized || didRunRef.current) return;
10140
10215
  const timer = setTimeout(() => {
@@ -10144,13 +10219,13 @@ function useAppAutoEvents(config) {
10144
10219
  const sessionId = getSessionId();
10145
10220
  const appVersion = getAppVersion2();
10146
10221
  const deviceType = await detectPlatform3();
10147
- const osVersion = await detectOsVersion();
10148
- if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
10222
+ const osVersion2 = await detectOsVersion();
10223
+ if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion: osVersion2, appVersion, sessionId, hasSessionId: !!sessionId });
10149
10224
  try {
10150
10225
  if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
10151
10226
  const firstSeen = await storageGet(FIRST_SEEN_KEY);
10152
10227
  if (!firstSeen) {
10153
- await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
10228
+ await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion2, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
10154
10229
  await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
10155
10230
  if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
10156
10231
  if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
@@ -10162,7 +10237,7 @@ function useAppAutoEvents(config) {
10162
10237
  }
10163
10238
  try {
10164
10239
  if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
10165
- await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
10240
+ await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion2, ...sessionId ? { session_id: sessionId } : {} }, "normal");
10166
10241
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
10167
10242
  } catch (err) {
10168
10243
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start failed", err);
@@ -10174,7 +10249,7 @@ function useAppAutoEvents(config) {
10174
10249
  }
10175
10250
 
10176
10251
  // src/hooks/useCampaignPreload.ts
10177
- var import_react23 = require("react");
10252
+ var import_react24 = require("react");
10178
10253
  init_PaywalloClient();
10179
10254
  init_CampaignError();
10180
10255
 
@@ -10245,17 +10320,17 @@ function getCacheEntry(cache, placement) {
10245
10320
 
10246
10321
  // src/hooks/useCampaignPreload.ts
10247
10322
  function useCampaignPreload() {
10248
- const [preloadedWebView, setPreloadedWebView] = (0, import_react23.useState)(null);
10249
- const preloadedCampaignsRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
10250
- const isPreloadValid = (0, import_react23.useCallback)(
10323
+ const [preloadedWebView, setPreloadedWebView] = (0, import_react24.useState)(null);
10324
+ const preloadedCampaignsRef = (0, import_react24.useRef)(/* @__PURE__ */ new Map());
10325
+ const isPreloadValid = (0, import_react24.useCallback)(
10251
10326
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
10252
10327
  []
10253
10328
  );
10254
- const consumePreloadedCampaign = (0, import_react23.useCallback)(
10329
+ const consumePreloadedCampaign = (0, import_react24.useCallback)(
10255
10330
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
10256
10331
  []
10257
10332
  );
10258
- const preloadCampaign = (0, import_react23.useCallback)(
10333
+ const preloadCampaign = (0, import_react24.useCallback)(
10259
10334
  async (placement, context) => {
10260
10335
  try {
10261
10336
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -10324,11 +10399,11 @@ function useCampaignPreload() {
10324
10399
  }
10325
10400
 
10326
10401
  // src/hooks/useProductLoader.ts
10327
- var import_react24 = require("react");
10402
+ var import_react25 = require("react");
10328
10403
  function useProductLoader() {
10329
- const [products, setProducts] = (0, import_react24.useState)(/* @__PURE__ */ new Map());
10330
- const [isLoadingProducts, setIsLoadingProducts] = (0, import_react24.useState)(false);
10331
- const refreshProducts = (0, import_react24.useCallback)(async (productIds) => {
10404
+ const [products, setProducts] = (0, import_react25.useState)(/* @__PURE__ */ new Map());
10405
+ const [isLoadingProducts, setIsLoadingProducts] = (0, import_react25.useState)(false);
10406
+ const refreshProducts = (0, import_react25.useCallback)(async (productIds) => {
10332
10407
  setIsLoadingProducts(true);
10333
10408
  try {
10334
10409
  const iapService = getIAPService();
@@ -10346,11 +10421,11 @@ function useProductLoader() {
10346
10421
  }
10347
10422
 
10348
10423
  // src/hooks/usePurchaseOrchestrator.ts
10349
- var import_react25 = require("react");
10424
+ var import_react26 = require("react");
10350
10425
  init_paywall();
10351
10426
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
10352
10427
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
10353
- const handlePreloadedPurchase = (0, import_react25.useCallback)(
10428
+ const handlePreloadedPurchase = (0, import_react26.useCallback)(
10354
10429
  async (productId) => {
10355
10430
  try {
10356
10431
  if (preloadedWebView) {
@@ -10388,7 +10463,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
10388
10463
  },
10389
10464
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
10390
10465
  );
10391
- const handlePreloadedRestore = (0, import_react25.useCallback)(async () => {
10466
+ const handlePreloadedRestore = (0, import_react26.useCallback)(async () => {
10392
10467
  try {
10393
10468
  const transactions = await getIAPService().restore();
10394
10469
  if (transactions.length === 0) return;
@@ -10411,7 +10486,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
10411
10486
  }
10412
10487
 
10413
10488
  // src/hooks/useSubscriptionSync.ts
10414
- var import_react26 = require("react");
10489
+ var import_react27 = require("react");
10415
10490
  init_PaywalloClient();
10416
10491
  init_SubscriptionCache();
10417
10492
 
@@ -10521,18 +10596,18 @@ async function checkPersistedCache(distinctId, onHit) {
10521
10596
 
10522
10597
  // src/hooks/useSubscriptionSync.ts
10523
10598
  function useSubscriptionSync() {
10524
- const [subscription, setSubscription] = (0, import_react26.useState)(null);
10525
- const cacheRef = (0, import_react26.useRef)(null);
10526
- const setCacheAndState = (0, import_react26.useCallback)((sub) => {
10599
+ const [subscription, setSubscription] = (0, import_react27.useState)(null);
10600
+ const cacheRef = (0, import_react27.useRef)(null);
10601
+ const setCacheAndState = (0, import_react27.useCallback)((sub) => {
10527
10602
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
10528
10603
  setSubscription(sub);
10529
10604
  }, []);
10530
- const invalidateCache = (0, import_react26.useCallback)(() => {
10605
+ const invalidateCache = (0, import_react27.useCallback)(() => {
10531
10606
  cacheRef.current = null;
10532
10607
  const distinctId = PaywalloClient.getDistinctId();
10533
10608
  if (distinctId) void subscriptionCache.invalidate(distinctId);
10534
10609
  }, []);
10535
- const fetchAndCache = (0, import_react26.useCallback)(async () => {
10610
+ const fetchAndCache = (0, import_react27.useCallback)(async () => {
10536
10611
  const apiClient = PaywalloClient.getApiClient();
10537
10612
  const distinctId = PaywalloClient.getDistinctId();
10538
10613
  if (!apiClient || !distinctId) return null;
@@ -10542,7 +10617,7 @@ function useSubscriptionSync() {
10542
10617
  void subscriptionCache.set(distinctId, toDomainResponse(status));
10543
10618
  return sub;
10544
10619
  }, [setCacheAndState]);
10545
- const hasActiveSubscription = (0, import_react26.useCallback)(async () => {
10620
+ const hasActiveSubscription = (0, import_react27.useCallback)(async () => {
10546
10621
  const apiClient = PaywalloClient.getApiClient();
10547
10622
  const distinctId = PaywalloClient.getDistinctId();
10548
10623
  if (!apiClient || !distinctId) return false;
@@ -10562,7 +10637,7 @@ function useSubscriptionSync() {
10562
10637
  return resolveOfflineFromCache(distinctId);
10563
10638
  }
10564
10639
  }, [setCacheAndState]);
10565
- const getSubscription = (0, import_react26.useCallback)(async () => {
10640
+ const getSubscription = (0, import_react27.useCallback)(async () => {
10566
10641
  const apiClient = PaywalloClient.getApiClient();
10567
10642
  const distinctId = PaywalloClient.getDistinctId();
10568
10643
  if (!apiClient || !distinctId) return null;
@@ -10587,20 +10662,20 @@ function useSubscriptionSync() {
10587
10662
  init_localization();
10588
10663
  var import_jsx_runtime5 = require("react/jsx-runtime");
10589
10664
  function PaywalloProvider({ children, config }) {
10590
- const [isInitialized, setIsInitialized] = (0, import_react27.useState)(false);
10591
- const [initError, setInitError] = (0, import_react27.useState)(null);
10592
- const [distinctId, setDistinctId] = (0, import_react27.useState)(null);
10593
- const [activePaywall, setActivePaywall] = (0, import_react27.useState)(null);
10665
+ const [isInitialized, setIsInitialized] = (0, import_react28.useState)(false);
10666
+ const [initError, setInitError] = (0, import_react28.useState)(null);
10667
+ const [distinctId, setDistinctId] = (0, import_react28.useState)(null);
10668
+ const [activePaywall, setActivePaywall] = (0, import_react28.useState)(null);
10594
10669
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
10595
10670
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
10596
10671
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
10597
- const clearPreloadedWebView = (0, import_react27.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
10598
- const autoPreloadTriggeredRef = (0, import_react27.useRef)(false);
10599
- const preloadedWebViewRef = (0, import_react27.useRef)(preloadedWebView);
10600
- const pollTimerRef = (0, import_react27.useRef)(null);
10601
- const pollCancelledRef = (0, import_react27.useRef)(false);
10602
- const secureStorageRef = (0, import_react27.useRef)(new SecureStorage(config.debug));
10603
- (0, import_react27.useEffect)(() => {
10672
+ const clearPreloadedWebView = (0, import_react28.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
10673
+ const autoPreloadTriggeredRef = (0, import_react28.useRef)(false);
10674
+ const preloadedWebViewRef = (0, import_react28.useRef)(preloadedWebView);
10675
+ const pollTimerRef = (0, import_react28.useRef)(null);
10676
+ const pollCancelledRef = (0, import_react28.useRef)(false);
10677
+ const secureStorageRef = (0, import_react28.useRef)(new SecureStorage(config.debug));
10678
+ (0, import_react28.useEffect)(() => {
10604
10679
  preloadedWebViewRef.current = preloadedWebView;
10605
10680
  }, [preloadedWebView]);
10606
10681
  useAppAutoEvents({
@@ -10626,20 +10701,20 @@ function PaywalloProvider({ children, config }) {
10626
10701
  },
10627
10702
  debug: config.debug
10628
10703
  });
10629
- const triggerRepreload = (0, import_react27.useCallback)((placement) => {
10704
+ const triggerRepreload = (0, import_react28.useCallback)((placement) => {
10630
10705
  void PaywalloClient.preloadCampaign(placement).catch(() => {
10631
10706
  });
10632
10707
  }, []);
10633
10708
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
10634
- const SCREEN_HEIGHT = (0, import_react27.useMemo)(() => import_react_native29.Dimensions.get("window").height, []);
10635
- const slideAnim = (0, import_react27.useRef)(new import_react_native29.Animated.Value(SCREEN_HEIGHT)).current;
10636
- const handlePreloadedWebViewReady = (0, import_react27.useCallback)(() => {
10709
+ const SCREEN_HEIGHT = (0, import_react28.useMemo)(() => import_react_native29.Dimensions.get("window").height, []);
10710
+ const slideAnim = (0, import_react28.useRef)(new import_react_native29.Animated.Value(SCREEN_HEIGHT)).current;
10711
+ const handlePreloadedWebViewReady = (0, import_react28.useCallback)(() => {
10637
10712
  if (PaywalloClient.getConfig()?.debug) {
10638
10713
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
10639
10714
  }
10640
10715
  baseHandlePreloadedWebViewReady();
10641
10716
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
10642
- const handlePreloadedClose = (0, import_react27.useCallback)(() => {
10717
+ const handlePreloadedClose = (0, import_react28.useCallback)(() => {
10643
10718
  const placement = preloadedWebView?.placement;
10644
10719
  import_react_native29.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native29.Easing.in(import_react_native29.Easing.cubic), useNativeDriver: true }).start(() => {
10645
10720
  baseHandlePreloadedClose();
@@ -10654,8 +10729,8 @@ function PaywalloProvider({ children, config }) {
10654
10729
  presentedAtRef,
10655
10730
  invalidateCache
10656
10731
  );
10657
- const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react27.useState)(false);
10658
- const handlePreloadedPurchase = (0, import_react27.useCallback)(async (productId) => {
10732
+ const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react28.useState)(false);
10733
+ const handlePreloadedPurchase = (0, import_react28.useCallback)(async (productId) => {
10659
10734
  setIsPreloadPurchasing(true);
10660
10735
  try {
10661
10736
  await rawPreloadedPurchase(productId);
@@ -10663,15 +10738,25 @@ function PaywalloProvider({ children, config }) {
10663
10738
  setIsPreloadPurchasing(false);
10664
10739
  }
10665
10740
  }, [rawPreloadedPurchase]);
10666
- const configRef = (0, import_react27.useRef)(config);
10667
- (0, import_react27.useEffect)(() => {
10741
+ const configRef = (0, import_react28.useRef)(config);
10742
+ (0, import_react28.useEffect)(() => {
10668
10743
  let mounted = true;
10669
- initLocalization({ detectDevice: true });
10670
- PaywalloClient.init(configRef.current).then(async () => {
10744
+ const bootstrap = async () => {
10745
+ if (!PaywalloClient.isReady()) {
10746
+ initLocalization({ detectDevice: true });
10747
+ try {
10748
+ await PaywalloClient.init(configRef.current);
10749
+ } catch (err) {
10750
+ if (!mounted) return;
10751
+ setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
10752
+ return;
10753
+ }
10754
+ if (!mounted) return;
10755
+ startSuperwallAutoBridge(configRef.current.debug);
10756
+ void pushAttributionToSuperwall(configRef.current.debug);
10757
+ }
10671
10758
  if (!mounted) return;
10672
10759
  setDistinctId(PaywalloClient.getDistinctId());
10673
- startSuperwallAutoBridge(configRef.current.debug);
10674
- void pushAttributionToSuperwall(configRef.current.debug);
10675
10760
  setIsInitialized(true);
10676
10761
  if (!autoPreloadTriggeredRef.current) {
10677
10762
  autoPreloadTriggeredRef.current = true;
@@ -10684,19 +10769,17 @@ function PaywalloProvider({ children, config }) {
10684
10769
  });
10685
10770
  }
10686
10771
  }
10687
- }).catch((err) => {
10688
- if (!mounted) return;
10689
- setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
10690
- });
10772
+ };
10773
+ void bootstrap();
10691
10774
  return () => {
10692
10775
  mounted = false;
10693
10776
  };
10694
10777
  }, [preloadCampaign]);
10695
- const getPaywallConfig = (0, import_react27.useCallback)(async (p) => {
10778
+ const getPaywallConfig = (0, import_react28.useCallback)(async (p) => {
10696
10779
  const api = PaywalloClient.getApiClient();
10697
10780
  return api ? api.getPaywall(p) : null;
10698
10781
  }, []);
10699
- const presentPaywall = (0, import_react27.useCallback)((placement) => {
10782
+ const presentPaywall = (0, import_react28.useCallback)((placement) => {
10700
10783
  const preloaded = paywallPreloadService.getPreloaded(placement);
10701
10784
  return new Promise((resolve) => setActivePaywall({
10702
10785
  placement,
@@ -10714,7 +10797,7 @@ function PaywalloProvider({ children, config }) {
10714
10797
  }
10715
10798
  }));
10716
10799
  }, []);
10717
- const trackCampaignImpression = (0, import_react27.useCallback)((placement, variantKey, campaignId) => {
10800
+ const trackCampaignImpression = (0, import_react28.useCallback)((placement, variantKey, campaignId) => {
10718
10801
  const api = PaywalloClient.getApiClient();
10719
10802
  if (!api) return;
10720
10803
  const sessionId = PaywalloClient.getSessionId();
@@ -10728,7 +10811,7 @@ function PaywalloProvider({ children, config }) {
10728
10811
  }).catch(() => {
10729
10812
  });
10730
10813
  }, []);
10731
- const presentCampaign = (0, import_react27.useCallback)(
10814
+ const presentCampaign = (0, import_react28.useCallback)(
10732
10815
  async (placement, context) => {
10733
10816
  try {
10734
10817
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -10802,7 +10885,7 @@ function PaywalloProvider({ children, config }) {
10802
10885
  },
10803
10886
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
10804
10887
  );
10805
- const restorePurchases = (0, import_react27.useCallback)(async () => {
10888
+ const restorePurchases = (0, import_react28.useCallback)(async () => {
10806
10889
  invalidateCache();
10807
10890
  try {
10808
10891
  const txs = await getIAPService().restore();
@@ -10811,13 +10894,13 @@ function PaywalloProvider({ children, config }) {
10811
10894
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
10812
10895
  }
10813
10896
  }, [invalidateCache]);
10814
- const handlePaywallResult = (0, import_react27.useCallback)((result) => {
10897
+ const handlePaywallResult = (0, import_react28.useCallback)((result) => {
10815
10898
  if (activePaywall) {
10816
10899
  activePaywall.resolver(result);
10817
10900
  setActivePaywall(null);
10818
10901
  }
10819
10902
  }, [activePaywall]);
10820
- const bridgePaywallPresenter = (0, import_react27.useCallback)((placement) => {
10903
+ const bridgePaywallPresenter = (0, import_react28.useCallback)((placement) => {
10821
10904
  if (PaywalloClient.getConfig()?.debug) {
10822
10905
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
10823
10906
  }
@@ -10846,7 +10929,7 @@ function PaywalloProvider({ children, config }) {
10846
10929
  }
10847
10930
  return presentCampaign(placement);
10848
10931
  }, [presentPreloadedWebView, presentCampaign]);
10849
- (0, import_react27.useEffect)(() => {
10932
+ (0, import_react28.useEffect)(() => {
10850
10933
  if (!isInitialized) return;
10851
10934
  const onEmergency = async (id) => {
10852
10935
  try {
@@ -10873,9 +10956,9 @@ function PaywalloProvider({ children, config }) {
10873
10956
  };
10874
10957
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
10875
10958
  const isReady = isInitialized && !isLoadingProducts;
10876
- const noOp = (0, import_react27.useCallback)(() => {
10959
+ const noOp = (0, import_react28.useCallback)(() => {
10877
10960
  }, []);
10878
- const contextValue = (0, import_react27.useMemo)(() => ({
10961
+ const contextValue = (0, import_react28.useMemo)(() => ({
10879
10962
  config: PaywalloClient.getConfig(),
10880
10963
  isInitialized,
10881
10964
  isReady,
@@ -10909,7 +10992,7 @@ function PaywalloProvider({ children, config }) {
10909
10992
  getPaywallConfig,
10910
10993
  refreshProducts
10911
10994
  ]);
10912
- (0, import_react27.useLayoutEffect)(() => {
10995
+ (0, import_react28.useLayoutEffect)(() => {
10913
10996
  if (showingPreloadedWebView) {
10914
10997
  slideAnim.setValue(SCREEN_HEIGHT);
10915
10998
  import_react_native29.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native29.Easing.out(import_react_native29.Easing.cubic), useNativeDriver: true }).start();
@@ -11128,6 +11211,7 @@ var index_default = Paywallo;
11128
11211
  attributionTracker,
11129
11212
  createPurchaseError,
11130
11213
  detectDeviceLanguage,
11214
+ getActiveRouteName,
11131
11215
  getCurrentLanguage,
11132
11216
  getDefaultLanguage,
11133
11217
  getIAPService,
@@ -11156,6 +11240,7 @@ var index_default = Paywallo;
11156
11240
  useOnboarding,
11157
11241
  usePaywallContext,
11158
11242
  usePaywallo,
11243
+ usePaywalloScreenTracking,
11159
11244
  usePlanPurchase,
11160
11245
  usePlans,
11161
11246
  useProducts,