@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.mjs CHANGED
@@ -246,77 +246,102 @@ var init_StorageMigration = __esm({
246
246
  }
247
247
  });
248
248
 
249
+ // src/errors/ClientError.ts
250
+ var ClientError, CLIENT_ERROR_CODES;
251
+ var init_ClientError = __esm({
252
+ "src/errors/ClientError.ts"() {
253
+ "use strict";
254
+ init_PaywalloError();
255
+ ClientError = class extends PaywalloError {
256
+ constructor(code, message) {
257
+ super("client", code, message);
258
+ this.name = "ClientError";
259
+ }
260
+ };
261
+ CLIENT_ERROR_CODES = {
262
+ NOT_INITIALIZED: "CLIENT_NOT_INITIALIZED",
263
+ MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
264
+ INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
265
+ PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
266
+ INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
267
+ UNKNOWN: "CLIENT_UNKNOWN"
268
+ };
269
+ }
270
+ });
271
+
249
272
  // src/utils/NativeDeviceInfo.ts
250
- import { NativeModules as NativeModules2 } from "react-native";
273
+ import { Platform } from "react-native";
274
+ import * as Device from "expo-device";
275
+ import * as Application from "expo-application";
276
+ import * as Localization from "expo-localization";
277
+ function assertExpoModules() {
278
+ if (!Device || Device.osName === void 0) {
279
+ throw new ClientError(
280
+ "MISSING_EXPO_DEPS",
281
+ "Paywallo SDK 3.x requires expo-device, expo-localization and expo-application. Run: npx expo install expo-device expo-localization expo-application"
282
+ );
283
+ }
284
+ }
251
285
  function setNativeDeviceInfoDebug(debug) {
252
286
  _debug2 = debug;
253
287
  }
254
- function isDeviceInfoResult(value) {
255
- if (typeof value !== "object" || value === null) return false;
256
- const v = value;
257
- return typeof v["deviceId"] === "string" && typeof v["model"] === "string" && typeof v["systemVersion"] === "string" && typeof v["appVersion"] === "string" && typeof v["bundleId"] === "string";
258
- }
259
- function getNativeModule() {
260
- const mod = NativeModules2.PaywalloDevice;
261
- if (!mod) {
262
- if (!warnedOnce) {
263
- warnedOnce = true;
264
- if (_debug2) console.log("[Paywallo] NativeModules.PaywalloDevice not found \u2014 falling back to defaults");
265
- }
266
- return null;
267
- }
268
- return mod;
288
+ function buildSyncFields(idfv) {
289
+ const locale = Localization.getLocales()[0] ?? null;
290
+ const calendar = Localization.getCalendars()[0] ?? null;
291
+ const androidId = Platform.OS === "android" ? Application.getAndroidId() ?? "unknown" : "unknown";
292
+ return {
293
+ deviceId: Platform.OS === "ios" ? idfv ?? "unknown" : androidId,
294
+ model: Device.modelName ?? "unknown",
295
+ modelId: Device.modelId ?? Device.modelName ?? "unknown",
296
+ osName: Device.osName ?? Platform.OS,
297
+ systemVersion: Device.osVersion ?? "unknown",
298
+ appVersion: Application.nativeApplicationVersion ?? "unknown",
299
+ buildNumber: Application.nativeBuildVersion ?? "0",
300
+ bundleId: Application.applicationId ?? "unknown",
301
+ brand: Device.brand ?? "unknown",
302
+ idfv,
303
+ regionCode: locale?.regionCode ?? null,
304
+ languageTag: locale?.languageTag ?? null,
305
+ languageCode: locale?.languageCode ?? null,
306
+ timeZone: calendar?.timeZone ?? null
307
+ };
269
308
  }
270
309
  async function getDeviceInfo() {
271
310
  if (cachedDeviceInfo !== null) return cachedDeviceInfo;
272
- const mod = getNativeModule();
273
- if (!mod) return FALLBACK;
274
- const raw = await mod.getDeviceInfo();
275
- if (!isDeviceInfoResult(raw)) return FALLBACK;
276
- cachedDeviceInfo = raw;
277
- return raw;
311
+ assertExpoModules();
312
+ const idfv = Platform.OS === "ios" ? await Application.getIosIdForVendorAsync() : null;
313
+ if (_debug2) console.log("[Paywallo] device info collected, idfv present:", idfv != null);
314
+ cachedDeviceInfo = buildSyncFields(idfv);
315
+ return cachedDeviceInfo;
278
316
  }
279
317
  async function getInstallReferrer() {
280
- const mod = getNativeModule();
281
- if (!mod) return null;
282
318
  try {
283
- return await mod.getInstallReferrer();
319
+ return await Application.getInstallReferrerAsync();
284
320
  } catch {
285
321
  return null;
286
322
  }
287
323
  }
288
324
  function isAvailable2() {
289
- return NativeModules2.PaywalloDevice != null;
325
+ return typeof Device !== "undefined" && Device.osName !== void 0;
290
326
  }
291
327
  function clearCache() {
292
328
  cachedDeviceInfo = null;
293
329
  }
294
330
  function getCachedDeviceInfo() {
295
- return cachedDeviceInfo;
331
+ if (cachedDeviceInfo !== null) return cachedDeviceInfo;
332
+ try {
333
+ assertExpoModules();
334
+ return buildSyncFields(null);
335
+ } catch {
336
+ return null;
337
+ }
296
338
  }
297
- var FALLBACK, _debug2, warnedOnce, cachedDeviceInfo, nativeDeviceInfo;
339
+ var _debug2, cachedDeviceInfo, nativeDeviceInfo;
298
340
  var init_NativeDeviceInfo = __esm({
299
341
  "src/utils/NativeDeviceInfo.ts"() {
300
342
  "use strict";
301
- FALLBACK = {
302
- deviceId: "unknown",
303
- model: "unknown",
304
- modelId: "unknown",
305
- systemVersion: "0.0.0",
306
- appVersion: "0.0.0",
307
- buildNumber: "0",
308
- bundleId: "unknown",
309
- brand: "unknown",
310
- systemName: "unknown",
311
- totalDisk: 0,
312
- freeDisk: 0,
313
- totalRam: 0,
314
- carrier: "unknown",
315
- installerPackage: null,
316
- idfv: null
317
- };
343
+ init_ClientError();
318
344
  _debug2 = false;
319
- warnedOnce = false;
320
345
  cachedDeviceInfo = null;
321
346
  nativeDeviceInfo = {
322
347
  getDeviceInfo,
@@ -331,6 +356,7 @@ var init_NativeDeviceInfo = __esm({
331
356
  // src/utils/uuid.ts
332
357
  var uuid_exports = {};
333
358
  __export(uuid_exports, {
359
+ deterministicUUID: () => deterministicUUID,
334
360
  generateUUID: () => generateUUID,
335
361
  isValidUUIDv4: () => isValidUUIDv4
336
362
  });
@@ -342,6 +368,28 @@ function generateUUID() {
342
368
  return v.toString(16);
343
369
  });
344
370
  }
371
+ function deterministicUUID(seed) {
372
+ const fnv1a32 = (s, basis) => {
373
+ let h = basis >>> 0;
374
+ for (let i = 0; i < s.length; i++) {
375
+ h = Math.imul(h ^ s.charCodeAt(i), 16777619) >>> 0;
376
+ }
377
+ return h;
378
+ };
379
+ const B = 2166136261;
380
+ const h0 = fnv1a32(seed, B);
381
+ const h1 = fnv1a32(seed, B ^ 252645135);
382
+ const h2 = fnv1a32(seed, B ^ 4042322160);
383
+ const h3 = fnv1a32(seed, B ^ 2863311530);
384
+ const x = (n, len) => (n >>> 0).toString(16).padStart(len, "0").slice(-len);
385
+ const s1 = x(h0, 8);
386
+ const s2 = x(h1 >>> 16, 4);
387
+ const s3 = "4" + x(h1 & 4095, 3);
388
+ const vb = (8 | h2 >>> 30 & 3).toString(16);
389
+ const s4 = vb + x(h2 >>> 18 & 4095, 3);
390
+ const s5 = x(h2 & 65535, 4) + x(h3, 8);
391
+ return `${s1}-${s2}-${s3}-${s4}-${s5}`;
392
+ }
345
393
  function isValidUUIDv4(value) {
346
394
  if (!value) return false;
347
395
  return UUID_V4_REGEX.test(value);
@@ -609,6 +657,10 @@ var init_IdentityManager = __esm({
609
657
  if (!this.initialized) return "";
610
658
  return this.distinctId ?? this.anonId ?? "";
611
659
  }
660
+ /** Public stable identifier — returns the same value as `getDistinctId()`. */
661
+ getId() {
662
+ return this.getDistinctId();
663
+ }
612
664
  getDeviceId() {
613
665
  return this.deviceId;
614
666
  }
@@ -880,7 +932,7 @@ var init_campaign = __esm({
880
932
  });
881
933
 
882
934
  // src/domains/identity/AdvertisingIdManager.ts
883
- import { Platform } from "react-native";
935
+ import { Platform as Platform2 } from "react-native";
884
936
  var ZERO_IDFA, AdvertisingIdManager, advertisingIdManager;
885
937
  var init_AdvertisingIdManager = __esm({
886
938
  "src/domains/identity/AdvertisingIdManager.ts"() {
@@ -904,7 +956,7 @@ var init_AdvertisingIdManager = __esm({
904
956
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
905
957
  try {
906
958
  const tracking = await import("expo-tracking-transparency");
907
- if (Platform.OS === "ios") {
959
+ if (Platform2.OS === "ios") {
908
960
  if (!tracking.isAvailable()) {
909
961
  const idfv2 = await this.collectIdfv();
910
962
  const result2 = { ...fallback, idfv: idfv2 };
@@ -925,7 +977,7 @@ var init_AdvertisingIdManager = __esm({
925
977
  if (idfv !== null) this.cached = result;
926
978
  return result;
927
979
  }
928
- if (Platform.OS === "android") {
980
+ if (Platform2.OS === "android") {
929
981
  const rawGaid = tracking.getAdvertisingId();
930
982
  const gaid = this.sanitizeId(rawGaid);
931
983
  this.cached = { idfv: null, idfa: null, gaid, attStatus: "unavailable" };
@@ -1012,10 +1064,8 @@ async function collectDeviceInfo() {
1012
1064
  const result = await nativeDeviceInfo.getDeviceInfo();
1013
1065
  return {
1014
1066
  appVersion: result.appVersion,
1015
- deviceModel: result.model,
1016
- osVersion: result.systemVersion,
1017
- darwinVersion: result.darwinVersion,
1018
- webkitVersion: result.webkitVersion
1067
+ deviceModel: result.modelId,
1068
+ osVersion: result.systemVersion
1019
1069
  };
1020
1070
  }
1021
1071
  var init_deviceInfo = __esm({
@@ -1410,6 +1460,12 @@ async function markInstallTracked(storage, installedAt) {
1410
1460
  } catch {
1411
1461
  }
1412
1462
  }
1463
+ async function resolveInstallEventId(deviceKey, appKey) {
1464
+ if (deviceKey && deviceKey.trim().length > 0) {
1465
+ return deterministicUUID(`${appKey}:${deviceKey}`);
1466
+ }
1467
+ return getOrCreateInstallEventId();
1468
+ }
1413
1469
  async function getOrCreateInstallEventId() {
1414
1470
  const existing = await nativeStorage.get(INSTALL_KEYS.INSTALL_EVENT_ID);
1415
1471
  if (existing) return existing;
@@ -1430,6 +1486,7 @@ var init_InstallIdempotency = __esm({
1430
1486
  "src/domains/identity/InstallIdempotency.ts"() {
1431
1487
  "use strict";
1432
1488
  init_NativeStorage();
1489
+ init_uuid();
1433
1490
  INSTALL_KEYS = {
1434
1491
  /** Primary flag — set AFTER a successful $app_installed dispatch. */
1435
1492
  INSTALL_TRACKED: "@paywallo:install_tracked",
@@ -1454,18 +1511,180 @@ var init_InstallIdempotency = __esm({
1454
1511
  }
1455
1512
  });
1456
1513
 
1514
+ // src/domains/identity/MetaBridge.ts
1515
+ import { NativeModules as NativeModules2 } from "react-native";
1516
+ var TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
1517
+ var init_MetaBridge = __esm({
1518
+ "src/domains/identity/MetaBridge.ts"() {
1519
+ "use strict";
1520
+ TIMEOUT_MS = 2e3;
1521
+ NativeBridge = NativeModules2.PaywalloFBBridge ?? null;
1522
+ MetaBridge = class {
1523
+ constructor() {
1524
+ this.cachedAnonId = null;
1525
+ this.cachedDeferredLink = null;
1526
+ this.deferredLinkFetched = false;
1527
+ this.debug = false;
1528
+ }
1529
+ setDebug(debug) {
1530
+ this.debug = debug;
1531
+ }
1532
+ /** Sync read of the cached anonymous ID. Returns null until `getAnonymousID()` resolves once. */
1533
+ getCachedAnonymousID() {
1534
+ return this.cachedAnonId;
1535
+ }
1536
+ async getAnonymousID() {
1537
+ if (this.cachedAnonId) return this.cachedAnonId;
1538
+ if (!NativeBridge) return null;
1539
+ try {
1540
+ let timerId;
1541
+ const timeout = new Promise((resolve) => {
1542
+ timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1543
+ });
1544
+ const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
1545
+ clearTimeout(timerId);
1546
+ if (result) this.cachedAnonId = result;
1547
+ return result;
1548
+ } catch {
1549
+ return null;
1550
+ }
1551
+ }
1552
+ async fetchDeferredAppLink() {
1553
+ if (this.deferredLinkFetched) return this.cachedDeferredLink;
1554
+ if (!NativeBridge) return null;
1555
+ if (typeof NativeBridge.fetchDeferredAppLink !== "function") return null;
1556
+ try {
1557
+ let timerId;
1558
+ const timeout = new Promise((resolve) => {
1559
+ timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1560
+ });
1561
+ const result = await Promise.race([
1562
+ NativeBridge.fetchDeferredAppLink().catch(() => null),
1563
+ timeout
1564
+ ]);
1565
+ clearTimeout(timerId);
1566
+ this.deferredLinkFetched = true;
1567
+ this.cachedDeferredLink = result;
1568
+ return result;
1569
+ } catch {
1570
+ this.deferredLinkFetched = true;
1571
+ this.cachedDeferredLink = null;
1572
+ return null;
1573
+ }
1574
+ }
1575
+ async logEvent(name, params = {}) {
1576
+ if (!NativeBridge) return;
1577
+ try {
1578
+ await NativeBridge.logEvent(name, params);
1579
+ } catch (error) {
1580
+ console.error("[Paywallo:MetaBridge] logEvent failed", { name, error });
1581
+ }
1582
+ }
1583
+ async logPurchase(amount, currency, params = {}) {
1584
+ if (!NativeBridge) return;
1585
+ try {
1586
+ await NativeBridge.logPurchase(amount, currency, params);
1587
+ } catch (error) {
1588
+ console.error("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
1589
+ }
1590
+ }
1591
+ setUserID(userId) {
1592
+ if (!NativeBridge) return;
1593
+ try {
1594
+ NativeBridge.setUserID(userId);
1595
+ } catch (error) {
1596
+ console.error("[Paywallo:MetaBridge] setUserID failed", { error });
1597
+ }
1598
+ }
1599
+ flush() {
1600
+ if (!NativeBridge) return;
1601
+ try {
1602
+ NativeBridge.flush();
1603
+ } catch (error) {
1604
+ console.error("[Paywallo:MetaBridge] flush failed", { error });
1605
+ }
1606
+ }
1607
+ };
1608
+ metaBridge = new MetaBridge();
1609
+ }
1610
+ });
1611
+
1612
+ // src/domains/identity/deferredAppLink.ts
1613
+ function parseQuery(s) {
1614
+ const result = {};
1615
+ for (const pair of s.split("&")) {
1616
+ const eqIndex = pair.indexOf("=");
1617
+ if (eqIndex === -1) continue;
1618
+ try {
1619
+ const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
1620
+ const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
1621
+ if (key) result[key] = value;
1622
+ } catch {
1623
+ }
1624
+ }
1625
+ return result;
1626
+ }
1627
+ function parseDeferredAppLink(url) {
1628
+ if (!url || url.trim() === "") return null;
1629
+ const raw = url.slice(0, 2048);
1630
+ const cleanRaw = raw.split("#")[0];
1631
+ const qIndex = cleanRaw.indexOf("?");
1632
+ if (qIndex === -1) return null;
1633
+ const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
1634
+ let fbclid = nz(topParams["fbclid"]);
1635
+ let utmSource = nz(topParams["utm_source"]);
1636
+ let utmMedium = nz(topParams["utm_medium"]);
1637
+ let utmCampaign = nz(topParams["utm_campaign"]);
1638
+ let ttclid = nz(topParams["ttclid"]);
1639
+ let trackingId = nz(topParams["tracking_id"]);
1640
+ let targetUrl = null;
1641
+ if (topParams["target_url"]) {
1642
+ try {
1643
+ targetUrl = decodeURIComponent(topParams["target_url"]);
1644
+ } catch {
1645
+ targetUrl = topParams["target_url"];
1646
+ }
1647
+ }
1648
+ if (!fbclid && targetUrl) {
1649
+ const cleanTargetUrl = targetUrl.split("#")[0];
1650
+ const innerQ = cleanTargetUrl.indexOf("?");
1651
+ if (innerQ !== -1) {
1652
+ const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
1653
+ fbclid = fbclid ?? nz(inner["fbclid"]);
1654
+ utmSource = utmSource ?? nz(inner["utm_source"]);
1655
+ utmMedium = utmMedium ?? nz(inner["utm_medium"]);
1656
+ utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
1657
+ ttclid = ttclid ?? nz(inner["ttclid"]);
1658
+ trackingId = trackingId ?? nz(inner["tracking_id"]);
1659
+ }
1660
+ }
1661
+ if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
1662
+ return null;
1663
+ }
1664
+ return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
1665
+ }
1666
+ var nz;
1667
+ var init_deferredAppLink = __esm({
1668
+ "src/domains/identity/deferredAppLink.ts"() {
1669
+ "use strict";
1670
+ nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
1671
+ }
1672
+ });
1673
+
1457
1674
  // src/domains/identity/InstallReferrerManager.ts
1458
- import { Platform as Platform2 } from "react-native";
1675
+ import { Platform as Platform3 } from "react-native";
1459
1676
  var REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
1460
1677
  var init_InstallReferrerManager = __esm({
1461
1678
  "src/domains/identity/InstallReferrerManager.ts"() {
1462
1679
  "use strict";
1463
1680
  init_NativeDeviceInfo();
1681
+ init_MetaBridge();
1682
+ init_deferredAppLink();
1464
1683
  REFERRER_TIMEOUT_MS = 5e3;
1465
1684
  InstallReferrerManager = class {
1466
1685
  async getReferrer() {
1467
- if (Platform2.OS !== "android") {
1468
- return this.emptyResult();
1686
+ if (Platform3.OS !== "android") {
1687
+ return this.fetchDeferredReferrer();
1469
1688
  }
1470
1689
  let timerId;
1471
1690
  const timeoutPromise = new Promise((resolve) => {
@@ -1476,6 +1695,29 @@ var init_InstallReferrerManager = __esm({
1476
1695
  clearTimeout(timerId);
1477
1696
  return result;
1478
1697
  }
1698
+ async fetchDeferredReferrer() {
1699
+ try {
1700
+ const url = await metaBridge.fetchDeferredAppLink();
1701
+ const parsed = parseDeferredAppLink(url);
1702
+ if (!parsed) return this.emptyResult();
1703
+ return {
1704
+ rawReferrer: parsed.targetUrl ?? parsed.raw,
1705
+ trackingId: parsed.trackingId,
1706
+ fbclid: parsed.fbclid,
1707
+ utmSource: parsed.utmSource,
1708
+ utmMedium: parsed.utmMedium,
1709
+ utmCampaign: parsed.utmCampaign,
1710
+ ttclid: parsed.ttclid,
1711
+ tiktokCampaignId: null,
1712
+ tiktokAdgroupId: null,
1713
+ tiktokAdId: null,
1714
+ installReferrerRaw: parsed.raw,
1715
+ installReferrerSource: "meta_deferred"
1716
+ };
1717
+ } catch {
1718
+ return this.emptyResult();
1719
+ }
1720
+ }
1479
1721
  async fetchReferrer() {
1480
1722
  try {
1481
1723
  const referrer = await nativeDeviceInfo.getInstallReferrer();
@@ -1536,81 +1778,8 @@ var init_InstallReferrerManager = __esm({
1536
1778
  }
1537
1779
  });
1538
1780
 
1539
- // src/domains/identity/MetaBridge.ts
1540
- import { NativeModules as NativeModules3 } from "react-native";
1541
- var TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
1542
- var init_MetaBridge = __esm({
1543
- "src/domains/identity/MetaBridge.ts"() {
1544
- "use strict";
1545
- TIMEOUT_MS = 2e3;
1546
- NativeBridge = NativeModules3.PaywalloFBBridge ?? null;
1547
- MetaBridge = class {
1548
- constructor() {
1549
- this.cachedAnonId = null;
1550
- this.debug = false;
1551
- }
1552
- setDebug(debug) {
1553
- this.debug = debug;
1554
- }
1555
- /** Sync read of the cached anonymous ID. Returns null until `getAnonymousID()` resolves once. */
1556
- getCachedAnonymousID() {
1557
- return this.cachedAnonId;
1558
- }
1559
- async getAnonymousID() {
1560
- if (this.cachedAnonId) return this.cachedAnonId;
1561
- if (!NativeBridge) return null;
1562
- try {
1563
- let timerId;
1564
- const timeout = new Promise((resolve) => {
1565
- timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
1566
- });
1567
- const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
1568
- clearTimeout(timerId);
1569
- if (result) this.cachedAnonId = result;
1570
- return result;
1571
- } catch {
1572
- return null;
1573
- }
1574
- }
1575
- async logEvent(name, params = {}) {
1576
- if (!NativeBridge) return;
1577
- try {
1578
- await NativeBridge.logEvent(name, params);
1579
- } catch (error) {
1580
- console.error("[Paywallo:MetaBridge] logEvent failed", { name, error });
1581
- }
1582
- }
1583
- async logPurchase(amount, currency, params = {}) {
1584
- if (!NativeBridge) return;
1585
- try {
1586
- await NativeBridge.logPurchase(amount, currency, params);
1587
- } catch (error) {
1588
- console.error("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
1589
- }
1590
- }
1591
- setUserID(userId) {
1592
- if (!NativeBridge) return;
1593
- try {
1594
- NativeBridge.setUserID(userId);
1595
- } catch (error) {
1596
- console.error("[Paywallo:MetaBridge] setUserID failed", { error });
1597
- }
1598
- }
1599
- flush() {
1600
- if (!NativeBridge) return;
1601
- try {
1602
- NativeBridge.flush();
1603
- } catch (error) {
1604
- console.error("[Paywallo:MetaBridge] flush failed", { error });
1605
- }
1606
- }
1607
- };
1608
- metaBridge = new MetaBridge();
1609
- }
1610
- });
1611
-
1612
1781
  // src/domains/identity/InstallTracker.ts
1613
- import { Dimensions, PixelRatio, Platform as Platform3 } from "react-native";
1782
+ import { Dimensions, PixelRatio, Platform as Platform4 } from "react-native";
1614
1783
  var InstallTracker;
1615
1784
  var init_InstallTracker = __esm({
1616
1785
  "src/domains/identity/InstallTracker.ts"() {
@@ -1678,44 +1847,17 @@ var init_InstallTracker = __esm({
1678
1847
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
1679
1848
  const info = await nativeDeviceInfo.getDeviceInfo();
1680
1849
  const deviceModel = info.modelId || info.model || "unknown";
1681
- const osVersion = info.systemVersion || "unknown";
1850
+ const osVersion2 = info.systemVersion || "unknown";
1682
1851
  const appVersion = info.appVersion || "unknown";
1683
1852
  const buildNumber = info.buildNumber || "0";
1684
- let carrier = "unknown";
1685
- try {
1686
- carrier = info.carrier ?? "unknown";
1687
- } catch {
1688
- }
1689
- let totalDisk = 0;
1690
- try {
1691
- totalDisk = Number(info.totalDisk) || 0;
1692
- } catch {
1693
- }
1694
- let freeDisk = 0;
1695
- try {
1696
- freeDisk = Number(info.freeDisk) || 0;
1697
- } catch {
1698
- }
1699
- let totalRam = 0;
1700
- try {
1701
- totalRam = Number(info.totalRam) || 0;
1702
- } catch {
1703
- }
1704
- let brand = "unknown";
1705
- try {
1706
- brand = info.brand ?? "unknown";
1707
- } catch {
1708
- }
1709
- let installerPackage = null;
1853
+ let brand2 = "unknown";
1710
1854
  try {
1711
- if (Platform3.OS === "android") {
1712
- installerPackage = info.installerPackage ?? null;
1713
- }
1855
+ brand2 = info.brand ?? "unknown";
1714
1856
  } catch {
1715
1857
  }
1716
1858
  deviceData = {
1717
1859
  deviceModel,
1718
- osVersion,
1860
+ osVersion: osVersion2,
1719
1861
  appVersion,
1720
1862
  buildNumber,
1721
1863
  ...screenWidth > 0 && { screenWidth },
@@ -1723,21 +1865,20 @@ var init_InstallTracker = __esm({
1723
1865
  ...screenDensity > 0 && { screenDensity },
1724
1866
  timezone,
1725
1867
  locale,
1726
- ...carrier !== "unknown" && { carrier },
1727
- ...totalDisk > 0 && { totalDisk },
1728
- ...freeDisk > 0 && { freeDisk },
1729
- ...totalRam > 0 && { totalRam },
1730
- ...brand !== "unknown" && { brand },
1731
- ...installerPackage && { installerPackage }
1868
+ ...brand2 !== "unknown" && { brand: brand2 },
1869
+ ...info.regionCode && { country: info.regionCode }
1732
1870
  };
1733
1871
  } catch {
1734
1872
  }
1735
- const [fbAnonId, referrer, adIds, installEventId] = await Promise.all([
1873
+ const [fbAnonId, referrer, adIds] = await Promise.all([
1736
1874
  metaBridge.getAnonymousID(),
1737
1875
  installReferrerManager.getReferrer(),
1738
- this.advertisingIdManager.collect(requestATT),
1739
- getOrCreateInstallEventId()
1876
+ this.advertisingIdManager.collect(requestATT)
1740
1877
  ]);
1878
+ const installEventId = await resolveInstallEventId(
1879
+ adIds.idfv ?? adIds.gaid ?? null,
1880
+ apiClient.getAppKey()
1881
+ );
1741
1882
  if (referrer.fbclid || referrer.utmSource || referrer.utmMedium || referrer.utmCampaign || referrer.ttclid || referrer.tiktokCampaignId || referrer.tiktokAdgroupId || referrer.tiktokAdId) {
1742
1883
  attributionTracker.capture({
1743
1884
  ...referrer.fbclid && { fbclid: referrer.fbclid },
@@ -1768,7 +1909,7 @@ var init_InstallTracker = __esm({
1768
1909
  }
1769
1910
  await apiClient.trackEvent("$app_installed", distinctId, {
1770
1911
  installedAt,
1771
- platform: Platform3.OS,
1912
+ platform: Platform4.OS,
1772
1913
  installEventId,
1773
1914
  ...sessionId && { sessionId },
1774
1915
  ...deviceData,
@@ -1808,7 +1949,7 @@ var init_InstallTracker = __esm({
1808
1949
  method: "POST",
1809
1950
  headers: { "Content-Type": "application/json", "X-App-Key": appKey },
1810
1951
  body: JSON.stringify({
1811
- platform: Platform3.OS,
1952
+ platform: Platform4.OS,
1812
1953
  ...adIds.idfv && { idfv: adIds.idfv },
1813
1954
  ...adIds.gaid && { gaid: adIds.gaid },
1814
1955
  ...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
@@ -1818,6 +1959,7 @@ var init_InstallTracker = __esm({
1818
1959
  screenHeight: deviceData.screenHeight,
1819
1960
  timezone: deviceData.timezone,
1820
1961
  language: deviceData.locale,
1962
+ ...deviceData.country && { country: deviceData.country },
1821
1963
  fbAnonId: anonId,
1822
1964
  installTimestamp: new Date(installedAt).toISOString()
1823
1965
  }),
@@ -1938,12 +2080,12 @@ var init_identity = __esm({
1938
2080
  });
1939
2081
 
1940
2082
  // src/domains/notifications/bridges/NativePushBridge.ts
1941
- import { NativeEventEmitter, NativeModules as NativeModules4, Platform as Platform4 } from "react-native";
2083
+ import { NativeEventEmitter, NativeModules as NativeModules3, Platform as Platform5 } from "react-native";
1942
2084
  function getPaywalloNotifications() {
1943
- return NativeModules4.PaywalloNotifications ?? null;
2085
+ return NativeModules3.PaywalloNotifications ?? null;
1944
2086
  }
1945
2087
  function isNativePushAvailable() {
1946
- return !!NativeModules4.PaywalloNotifications;
2088
+ return !!NativeModules3.PaywalloNotifications;
1947
2089
  }
1948
2090
  function mapStatusToNumber(status) {
1949
2091
  switch (status) {
@@ -1965,7 +2107,7 @@ function mapStatusToNumber(status) {
1965
2107
  }
1966
2108
  function getEmitter() {
1967
2109
  if (_emitter) return _emitter;
1968
- const nativeMod = NativeModules4.PaywalloNotifications;
2110
+ const nativeMod = NativeModules3.PaywalloNotifications;
1969
2111
  if (!nativeMod) return null;
1970
2112
  _emitter = new NativeEventEmitter(nativeMod);
1971
2113
  return _emitter;
@@ -1986,7 +2128,7 @@ var init_NativePushBridge = __esm({
1986
2128
  }
1987
2129
  }
1988
2130
  async getAPNSToken() {
1989
- if (Platform4.OS !== "ios") return null;
2131
+ if (Platform5.OS !== "ios") return null;
1990
2132
  return this.getToken();
1991
2133
  }
1992
2134
  async requestPermission(options) {
@@ -2073,7 +2215,7 @@ var DEFAULT_API_URL, DEFAULT_WEB_URL;
2073
2215
  var init_constants = __esm({
2074
2216
  "src/core/constants.ts"() {
2075
2217
  "use strict";
2076
- DEFAULT_API_URL = "https://panel.lucasqueiroga.shop";
2218
+ DEFAULT_API_URL = "https://incommensurably-unprotrudent-brooklynn.ngrok-free.dev";
2077
2219
  DEFAULT_WEB_URL = "https://paywallo.com.br";
2078
2220
  }
2079
2221
  });
@@ -2220,7 +2362,7 @@ var init_ForegroundHandler = __esm({
2220
2362
  });
2221
2363
 
2222
2364
  // src/domains/notifications/NotificationEventTracker.ts
2223
- import { Platform as Platform5 } from "react-native";
2365
+ import { Platform as Platform6 } from "react-native";
2224
2366
  function getTimezone() {
2225
2367
  try {
2226
2368
  return Intl.DateTimeFormat().resolvedOptions().timeZone ?? "UTC";
@@ -2229,7 +2371,7 @@ function getTimezone() {
2229
2371
  }
2230
2372
  }
2231
2373
  function detectPlatform() {
2232
- return Platform5.OS === "ios" ? "ios" : "android";
2374
+ return Platform6.OS === "ios" ? "ios" : "android";
2233
2375
  }
2234
2376
  var SEEN_MESSAGES_KEY, LEGACY_SEEN_MESSAGES_KEY, SEEN_TTL_MS, MAX_SEEN_SIZE, NOTIFICATION_TYPE_TO_FAMILY, NotificationEventTracker;
2235
2377
  var init_NotificationEventTracker = __esm({
@@ -2405,8 +2547,8 @@ var init_NotificationHandlerSetup = __esm({
2405
2547
  // src/core/version.ts
2406
2548
  function resolveVersion() {
2407
2549
  try {
2408
- if ("2.4.0") {
2409
- return "2.4.0";
2550
+ if ("2.5.0") {
2551
+ return "2.5.0";
2410
2552
  }
2411
2553
  } catch {
2412
2554
  }
@@ -2421,7 +2563,7 @@ var init_version = __esm({
2421
2563
  });
2422
2564
 
2423
2565
  // src/domains/notifications/utils.ts
2424
- import { Platform as Platform6 } from "react-native";
2566
+ import { Platform as Platform7 } from "react-native";
2425
2567
  function sleep(ms, signal) {
2426
2568
  return new Promise((resolve, reject) => {
2427
2569
  if (signal?.aborted) {
@@ -2449,8 +2591,8 @@ async function getLocale() {
2449
2591
  return clampLocale(raw);
2450
2592
  } catch {
2451
2593
  try {
2452
- const { getLocales } = __require("expo-localization");
2453
- const code = getLocales()[0]?.languageCode ?? "en";
2594
+ const { getLocales: getLocales2 } = __require("expo-localization");
2595
+ const code = getLocales2()[0]?.languageCode ?? "en";
2454
2596
  return clampLocale(code);
2455
2597
  } catch {
2456
2598
  return "en";
@@ -2477,7 +2619,7 @@ function mapPermissionStatus(status) {
2477
2619
  }
2478
2620
  }
2479
2621
  function detectPlatform2() {
2480
- return Platform6.OS === "ios" ? "ios" : "android";
2622
+ return Platform7.OS === "ios" ? "ios" : "android";
2481
2623
  }
2482
2624
  async function getAppVersion() {
2483
2625
  try {
@@ -2654,7 +2796,7 @@ var init_NotificationTokenOps = __esm({
2654
2796
  });
2655
2797
 
2656
2798
  // src/domains/notifications/PermissionManager.ts
2657
- import { Platform as Platform7 } from "react-native";
2799
+ import { Platform as Platform8 } from "react-native";
2658
2800
  function mapPermissionStatus2(status) {
2659
2801
  switch (status) {
2660
2802
  case AUTH_STATUS2.AUTHORIZED:
@@ -2668,7 +2810,7 @@ function mapPermissionStatus2(status) {
2668
2810
  }
2669
2811
  }
2670
2812
  function getPlatform() {
2671
- return Platform7;
2813
+ return Platform8;
2672
2814
  }
2673
2815
  function needsAndroidRuntimePermission(platform) {
2674
2816
  if (platform.OS !== "android") return false;
@@ -3269,7 +3411,7 @@ var init_paywallHeartbeat = __esm({
3269
3411
  });
3270
3412
 
3271
3413
  // src/utils/localization.ts
3272
- import { NativeModules as NativeModules5, Platform as Platform8 } from "react-native";
3414
+ import { NativeModules as NativeModules4, Platform as Platform9 } from "react-native";
3273
3415
  function setCurrentLanguage(language) {
3274
3416
  currentLanguage = language;
3275
3417
  }
@@ -3289,11 +3431,11 @@ function getLocalizedString(text) {
3289
3431
  }
3290
3432
  function detectDeviceLanguage() {
3291
3433
  try {
3292
- if (Platform8.OS === "ios") {
3293
- const settings = NativeModules5.SettingsManager?.settings;
3434
+ if (Platform9.OS === "ios") {
3435
+ const settings = NativeModules4.SettingsManager?.settings;
3294
3436
  return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
3295
3437
  }
3296
- return NativeModules5.I18nManager?.localeIdentifier ?? "en-US";
3438
+ return NativeModules4.I18nManager?.localeIdentifier ?? "en-US";
3297
3439
  } catch {
3298
3440
  return "en-US";
3299
3441
  }
@@ -3464,8 +3606,8 @@ import {
3464
3606
  findNodeHandle,
3465
3607
  Linking as Linking2,
3466
3608
  NativeEventEmitter as NativeEventEmitter2,
3467
- NativeModules as NativeModules6,
3468
- Platform as Platform9,
3609
+ NativeModules as NativeModules5,
3610
+ Platform as Platform10,
3469
3611
  requireNativeComponent,
3470
3612
  StyleSheet as StyleSheet2,
3471
3613
  UIManager,
@@ -3485,7 +3627,7 @@ function getNativeWebView() {
3485
3627
  }
3486
3628
  function getWebViewEmitter() {
3487
3629
  if (webViewEmitter) return webViewEmitter;
3488
- const mod = NativeModules6.PaywalloWebViewModule;
3630
+ const mod = NativeModules5.PaywalloWebViewModule;
3489
3631
  if (!mod) return null;
3490
3632
  webViewEmitter = new NativeEventEmitter2(mod);
3491
3633
  return webViewEmitter;
@@ -3578,7 +3720,7 @@ function PaywallWebView({
3578
3720
  }
3579
3721
  }, []);
3580
3722
  const injectScript = useCallback((script) => {
3581
- if (Platform9.OS === "android") {
3723
+ if (Platform10.OS === "android") {
3582
3724
  const handle = findNodeHandle(webViewRef.current);
3583
3725
  if (handle == null) return;
3584
3726
  UIManager.dispatchViewManagerCommand(handle, "injectJavaScript", [script]);
@@ -3722,7 +3864,7 @@ function PaywallWebView({
3722
3864
  injectScript(buildPurchaseStateScript(isPurchasing));
3723
3865
  }, [isPurchasing, injectScript]);
3724
3866
  useEffect2(() => {
3725
- if (Platform9.OS !== "android") return;
3867
+ if (Platform10.OS !== "android") return;
3726
3868
  const onBackPress = () => {
3727
3869
  if (!paywallDataSent.current) return false;
3728
3870
  injectScript(buildBackButtonScript());
@@ -3763,269 +3905,25 @@ var init_PaywallWebView = __esm({
3763
3905
  }
3764
3906
  });
3765
3907
 
3766
- // src/domains/iap/PurchaseError.ts
3767
- function createPurchaseError(code, message) {
3768
- return new PurchaseError(
3769
- PURCHASE_ERROR_CODES[code],
3770
- message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
3771
- code === "USER_CANCELLED"
3772
- );
3773
- }
3774
- var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
3775
- var init_PurchaseError = __esm({
3776
- "src/domains/iap/PurchaseError.ts"() {
3777
- "use strict";
3778
- init_PaywalloError();
3779
- PurchaseError = class extends PaywalloError {
3780
- constructor(code, message, userCancelled = false, httpStatus) {
3781
- super("purchase", code, message);
3782
- this.name = "PurchaseError";
3783
- this.userCancelled = userCancelled;
3784
- this.httpStatus = httpStatus;
3785
- }
3786
- };
3787
- PURCHASE_ERROR_CODES = {
3788
- NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
3789
- PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
3790
- PURCHASE_FAILED: "PURCHASE_FAILED",
3791
- RESTORE_FAILED: "RESTORE_FAILED",
3792
- VALIDATION_FAILED: "VALIDATION_FAILED",
3793
- USER_CANCELLED: "USER_CANCELLED",
3794
- NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
3795
- STORE_ERROR: "PURCHASE_STORE_ERROR",
3796
- PENDING_PURCHASE: "PURCHASE_PENDING",
3797
- DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
3798
- STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
3799
- };
3800
- DEFAULT_MESSAGES = {
3801
- NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
3802
- PRODUCT_NOT_FOUND: "Product not found in store.",
3803
- PURCHASE_FAILED: "Purchase failed. Please try again.",
3804
- RESTORE_FAILED: "Failed to restore purchases. Please try again.",
3805
- VALIDATION_FAILED: "Failed to validate purchase with server.",
3806
- USER_CANCELLED: "Purchase was cancelled.",
3807
- NETWORK_ERROR: "Network error. Please check your connection.",
3808
- STORE_ERROR: "Store error. Please try again later.",
3809
- PENDING_PURCHASE: "Purchase is pending approval.",
3810
- DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
3811
- STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
3812
- };
3813
- }
3814
- });
3815
-
3816
- // src/domains/iap/NativeStoreKit.ts
3817
- var NativeStoreKit_exports = {};
3818
- __export(NativeStoreKit_exports, {
3819
- NativeStoreKit: () => NativeStoreKit,
3820
- nativeStoreKit: () => nativeStoreKit
3821
- });
3822
- import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules7, Platform as Platform10 } from "react-native";
3823
- function isAvailable3() {
3824
- return PaywalloStoreKitNative != null;
3825
- }
3826
- function mapNativeProduct(native) {
3827
- const product = {
3828
- productId: native.productId,
3829
- title: native.title,
3830
- description: native.description,
3831
- price: native.price,
3832
- priceValue: native.priceValue,
3833
- currency: native.currency ?? "USD",
3834
- localizedPrice: native.localizedPrice,
3835
- type: mapProductType(native.type),
3836
- subscriptionPeriod: native.subscriptionPeriod,
3837
- introductoryPrice: native.introductoryPrice,
3838
- introductoryPriceValue: native.introductoryPriceValue,
3839
- freeTrialPeriod: native.freeTrialPeriod
3840
- };
3841
- if (native.subscriptionPeriod) {
3842
- const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
3843
- product.pricePerMonth = prices.perMonth;
3844
- product.pricePerWeek = prices.perWeek;
3845
- product.pricePerDay = prices.perDay;
3846
- }
3847
- return product;
3848
- }
3849
- function mapProductType(type) {
3850
- switch (type) {
3851
- case "subscription":
3852
- case "autoRenewable":
3853
- return "subscription";
3854
- case "consumable":
3855
- return "consumable";
3856
- default:
3857
- return "non_consumable";
3858
- }
3859
- }
3860
- function calculatePerPeriodPrices(priceValue, period) {
3861
- const totalDays = periodToDays(period);
3862
- if (totalDays <= 0) return {};
3863
- const perDay = priceValue / totalDays;
3864
- const perWeek = perDay * 7;
3865
- const perMonth = perDay * 30;
3866
- return {
3867
- perDay: perDay.toFixed(2),
3868
- perWeek: perWeek.toFixed(2),
3869
- perMonth: perMonth.toFixed(2)
3870
- };
3871
- }
3872
- function periodToDays(period) {
3873
- const match = period.match(/^P(\d+)([DWMY])$/);
3874
- if (!match) return 0;
3875
- const value = parseInt(match[1], 10);
3876
- switch (match[2]) {
3877
- case "D":
3878
- return value;
3879
- case "W":
3880
- return value * 7;
3881
- case "M":
3882
- return value * 30;
3883
- case "Y":
3884
- return value * 365;
3885
- default:
3886
- return 0;
3887
- }
3888
- }
3889
- function mapNativePurchase(native) {
3890
- return {
3891
- productId: native.productId,
3892
- transactionId: native.transactionId,
3893
- transactionDate: native.transactionDate,
3894
- receipt: native.receipt,
3895
- platform: Platform10.OS
3896
- };
3897
- }
3898
- function normalizeTransactionUpdate(raw) {
3899
- if (!raw || typeof raw !== "object") return null;
3900
- const r = raw;
3901
- const type = r.type;
3902
- if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
3903
- const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
3904
- const productId = typeof r.productId === "string" ? r.productId : null;
3905
- if (!transactionId) return null;
3906
- const update = {
3907
- type,
3908
- transactionId,
3909
- productId: productId ?? ""
3910
- };
3911
- if (typeof r.amount === "number") update.amount = r.amount;
3912
- if (typeof r.currency === "string") update.currency = r.currency;
3913
- return update;
3914
- }
3915
- var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3916
- var init_NativeStoreKit = __esm({
3917
- "src/domains/iap/NativeStoreKit.ts"() {
3918
- "use strict";
3919
- init_PaywalloClient();
3920
- init_uuid();
3921
- init_IdentityManager();
3922
- init_PurchaseError();
3923
- TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
3924
- PaywalloStoreKitNative = NativeModules7.PaywalloStoreKit ?? null;
3925
- nativeStoreKit = {
3926
- isAvailable: isAvailable3,
3927
- async getProducts(productIds) {
3928
- if (!PaywalloStoreKitNative) {
3929
- return [];
3930
- }
3931
- const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
3932
- return nativeProducts.map(mapNativeProduct);
3933
- },
3934
- async purchase(productId) {
3935
- if (!PaywalloStoreKitNative) {
3936
- throw new PurchaseError(
3937
- PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
3938
- "StoreKit native module not available"
3939
- );
3940
- }
3941
- const distinctId = identityManager.getDistinctId();
3942
- const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
3943
- const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
3944
- if (distinctId && appAccountToken === null) {
3945
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
3946
- }
3947
- try {
3948
- const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
3949
- if (!result || result.pending) {
3950
- return null;
3951
- }
3952
- return mapNativePurchase(result);
3953
- } catch (err) {
3954
- const message = err instanceof Error ? err.message : String(err);
3955
- if (message.toLowerCase().includes("cancel")) {
3956
- throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
3957
- }
3958
- throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
3959
- }
3960
- },
3961
- async finishTransaction(transactionId) {
3962
- if (!PaywalloStoreKitNative) return;
3963
- try {
3964
- await PaywalloStoreKitNative.finishTransaction(transactionId);
3965
- } catch (err) {
3966
- console.error("[Paywallo] finishTransaction native error", err);
3967
- }
3968
- },
3969
- async getActiveTransactions() {
3970
- if (!PaywalloStoreKitNative) return [];
3971
- try {
3972
- const results = await PaywalloStoreKitNative.getActiveTransactions();
3973
- return results.map(mapNativePurchase);
3974
- } catch (err) {
3975
- console.error("[Paywallo] getActiveTransactions failed", err);
3976
- return [];
3977
- }
3978
- },
3979
- /**
3980
- * Subscribes to native transaction update events (renewals, refunds,
3981
- * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
3982
- * `PurchasesUpdatedListener` + refund diff on Android.
3983
- *
3984
- * Returns a subscription with a `remove()` method. The caller is
3985
- * responsible for cleanup (typically on `Paywallo.reset()`).
3986
- *
3987
- * Returns `null` when the native module isn't available (non-mobile env,
3988
- * native module not linked) — callers should treat null as a no-op.
3989
- */
3990
- subscribeToTransactionUpdates(listener) {
3991
- if (!PaywalloStoreKitNative) return null;
3992
- try {
3993
- const EmitterCtor = NativeEventEmitter3;
3994
- const emitter = new EmitterCtor(PaywalloStoreKitNative);
3995
- const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
3996
- const update = normalizeTransactionUpdate(payload);
3997
- if (update) listener(update);
3998
- });
3999
- return { remove: () => sub.remove() };
4000
- } catch (err) {
4001
- if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
4002
- return null;
4003
- }
4004
- }
4005
- };
4006
- NativeStoreKit = nativeStoreKit;
4007
- }
4008
- });
4009
-
4010
3908
  // src/domains/iap/IAPTransactionEmitter.ts
4011
3909
  var IAPTransactionEmitter;
4012
3910
  var init_IAPTransactionEmitter = __esm({
4013
3911
  "src/domains/iap/IAPTransactionEmitter.ts"() {
4014
3912
  "use strict";
4015
3913
  init_PaywalloClient();
4016
- init_NativeStoreKit();
4017
3914
  IAPTransactionEmitter = class {
4018
3915
  constructor(getProductFromCache, getApiClientFn, getDebug) {
4019
3916
  this.getProductFromCache = getProductFromCache;
4020
3917
  this.getApiClientFn = getApiClientFn;
4021
3918
  this.getDebug = getDebug;
4022
- this.transactionUpdateSubscription = null;
4023
3919
  }
4024
3920
  /**
4025
- * Emits `transaction {type: "completed"}` after a server-validated purchase.
4026
- * Fire-and-forgeta tracking failure must never poison the purchase flow.
3921
+ * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
3922
+ * antes do StoreKit/Billing abrir conta mesmo que a sheet nativa seja cancelada.
3923
+ * Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
3924
+ * nunca pode travar a compra.
4027
3925
  */
4028
- async emitTransactionCompleted(purchase, productId, options) {
3926
+ async emitCheckoutStarted(productId) {
4029
3927
  try {
4030
3928
  const apiClient = this.getApiClientFn();
4031
3929
  if (!apiClient) return;
@@ -4033,40 +3931,9 @@ var init_IAPTransactionEmitter = __esm({
4033
3931
  if (!distinctId) return;
4034
3932
  const product = this.getProductFromCache(productId);
4035
3933
  const properties = {
4036
- type: "completed",
4037
- tx_id: purchase.transactionId,
4038
- product_id: purchase.productId
4039
- };
4040
- if (product?.priceValue !== void 0 && product.priceValue > 0) {
4041
- properties.amount = product.priceValue;
4042
- }
4043
- if (product?.currency) {
4044
- properties.currency = product.currency;
4045
- }
4046
- if (options?.paywallId) properties.paywall_id = options.paywallId;
4047
- if (options?.variantId) properties.variant_id = options.variantId;
4048
- await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4049
- } catch (err) {
4050
- if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
4051
- }
4052
- }
4053
- /**
4054
- * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
4055
- * antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
4056
- * Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
4057
- * nunca pode travar a compra.
4058
- */
4059
- async emitCheckoutStarted(productId) {
4060
- try {
4061
- const apiClient = this.getApiClientFn();
4062
- if (!apiClient) return;
4063
- const distinctId = PaywalloClient.getDistinctId();
4064
- if (!distinctId) return;
4065
- const product = this.getProductFromCache(productId);
4066
- const properties = {
4067
- product_id: productId,
4068
- amount: product?.priceValue ?? 0,
4069
- currency: product?.currency ?? "USD"
3934
+ product_id: productId,
3935
+ amount: product?.priceValue ?? 0,
3936
+ currency: product?.currency ?? "USD"
4070
3937
  };
4071
3938
  await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
4072
3939
  priority: "critical"
@@ -4077,90 +3944,17 @@ var init_IAPTransactionEmitter = __esm({
4077
3944
  }
4078
3945
  }
4079
3946
  /**
4080
- * Forwards a native transaction update to the EventBatcher.
4081
- * Public so tests can exercise the wiring without a fake NativeEventEmitter.
4082
- */
4083
- async emitTransactionUpdate(update) {
4084
- try {
4085
- const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
4086
- if (!apiClient) {
4087
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
4088
- return;
4089
- }
4090
- const distinctId = PaywalloClient.getDistinctId();
4091
- if (!distinctId) {
4092
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
4093
- return;
4094
- }
4095
- const properties = {
4096
- type: update.type,
4097
- tx_id: update.transactionId,
4098
- product_id: update.productId
4099
- };
4100
- if (update.type !== "canceled") {
4101
- const cached2 = this.getProductFromCache(update.productId);
4102
- const amount = update.amount ?? cached2?.priceValue;
4103
- const currency = update.currency ?? cached2?.currency;
4104
- if (typeof amount === "number" && amount > 0) properties.amount = amount;
4105
- if (currency) properties.currency = currency;
4106
- }
4107
- await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
4108
- if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
4109
- } catch (err) {
4110
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
4111
- }
4112
- }
4113
- /**
4114
- * Starts listening to native Transaction.updates. Idempotent.
3947
+ * No-op. Native transaction update listener removed — sale events are sourced
3948
+ * exclusively from server-side webhooks (Apple/Google/Superwall/RevenueCat).
4115
3949
  */
4116
3950
  startListener() {
4117
- if (this.transactionUpdateSubscription) return;
4118
- this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
4119
- (update) => {
4120
- void this.emitTransactionUpdate(update);
4121
- }
4122
- );
4123
- if (this.getDebug() && this.transactionUpdateSubscription) {
4124
- console.log("[Paywallo IAP] transaction update listener started");
4125
- }
4126
3951
  }
4127
3952
  /**
4128
- * Removes the native listener. Safe to call multiple times.
3953
+ * No-op. See startListener.
4129
3954
  */
4130
3955
  stopListener() {
4131
- if (!this.transactionUpdateSubscription) return;
4132
- try {
4133
- this.transactionUpdateSubscription.remove();
4134
- } catch (err) {
4135
- if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
4136
- }
4137
- this.transactionUpdateSubscription = null;
4138
- if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
4139
- }
4140
- };
4141
- }
4142
- });
4143
-
4144
- // src/errors/ClientError.ts
4145
- var ClientError, CLIENT_ERROR_CODES;
4146
- var init_ClientError = __esm({
4147
- "src/errors/ClientError.ts"() {
4148
- "use strict";
4149
- init_PaywalloError();
4150
- ClientError = class extends PaywalloError {
4151
- constructor(code, message) {
4152
- super("client", code, message);
4153
- this.name = "ClientError";
4154
3956
  }
4155
3957
  };
4156
- CLIENT_ERROR_CODES = {
4157
- NOT_INITIALIZED: "CLIENT_NOT_INITIALIZED",
4158
- MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
4159
- INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
4160
- PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
4161
- INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
4162
- UNKNOWN: "CLIENT_UNKNOWN"
4163
- };
4164
3958
  }
4165
3959
  });
4166
3960
 
@@ -4531,11 +4325,11 @@ var init_OfflineQueue = __esm({
4531
4325
  });
4532
4326
  return true;
4533
4327
  }
4534
- const delay = Math.min(
4328
+ const delay2 = Math.min(
4535
4329
  this.config.baseRetryDelayMs * Math.pow(2, item.attempts - 1),
4536
4330
  this.config.maxRetryDelayMs
4537
4331
  );
4538
- item.nextRetryAt = Date.now() + delay;
4332
+ item.nextRetryAt = Date.now() + delay2;
4539
4333
  await this.storage.saveToStorage(this.queue);
4540
4334
  return false;
4541
4335
  }
@@ -5047,6 +4841,56 @@ var init_queue = __esm({
5047
4841
  }
5048
4842
  });
5049
4843
 
4844
+ // src/domains/iap/PurchaseError.ts
4845
+ function createPurchaseError(code, message) {
4846
+ return new PurchaseError(
4847
+ PURCHASE_ERROR_CODES[code],
4848
+ message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
4849
+ code === "USER_CANCELLED"
4850
+ );
4851
+ }
4852
+ var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
4853
+ var init_PurchaseError = __esm({
4854
+ "src/domains/iap/PurchaseError.ts"() {
4855
+ "use strict";
4856
+ init_PaywalloError();
4857
+ PurchaseError = class extends PaywalloError {
4858
+ constructor(code, message, userCancelled = false, httpStatus) {
4859
+ super("purchase", code, message);
4860
+ this.name = "PurchaseError";
4861
+ this.userCancelled = userCancelled;
4862
+ this.httpStatus = httpStatus;
4863
+ }
4864
+ };
4865
+ PURCHASE_ERROR_CODES = {
4866
+ NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
4867
+ PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
4868
+ PURCHASE_FAILED: "PURCHASE_FAILED",
4869
+ RESTORE_FAILED: "RESTORE_FAILED",
4870
+ VALIDATION_FAILED: "VALIDATION_FAILED",
4871
+ USER_CANCELLED: "USER_CANCELLED",
4872
+ NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
4873
+ STORE_ERROR: "PURCHASE_STORE_ERROR",
4874
+ PENDING_PURCHASE: "PURCHASE_PENDING",
4875
+ DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
4876
+ STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
4877
+ };
4878
+ DEFAULT_MESSAGES = {
4879
+ NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
4880
+ PRODUCT_NOT_FOUND: "Product not found in store.",
4881
+ PURCHASE_FAILED: "Purchase failed. Please try again.",
4882
+ RESTORE_FAILED: "Failed to restore purchases. Please try again.",
4883
+ VALIDATION_FAILED: "Failed to validate purchase with server.",
4884
+ USER_CANCELLED: "Purchase was cancelled.",
4885
+ NETWORK_ERROR: "Network error. Please check your connection.",
4886
+ STORE_ERROR: "Store error. Please try again later.",
4887
+ PENDING_PURCHASE: "Purchase is pending approval.",
4888
+ DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
4889
+ STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
4890
+ };
4891
+ }
4892
+ });
4893
+
5050
4894
  // src/domains/iap/IAPValidator.ts
5051
4895
  import { Platform as Platform11 } from "react-native";
5052
4896
  var IAPValidator;
@@ -5145,53 +4989,247 @@ var init_IAPValidator = __esm({
5145
4989
  }
5146
4990
  });
5147
4991
 
5148
- // src/domains/iap/IAPService.ts
5149
- import { Platform as Platform12 } from "react-native";
5150
- function getIAPService() {
5151
- if (!_instance) {
5152
- _instance = new IAPService();
5153
- _instance.startTransactionListener();
4992
+ // src/domains/iap/NativeStoreKit.ts
4993
+ var NativeStoreKit_exports = {};
4994
+ __export(NativeStoreKit_exports, {
4995
+ NativeStoreKit: () => NativeStoreKit,
4996
+ nativeStoreKit: () => nativeStoreKit
4997
+ });
4998
+ import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform12 } from "react-native";
4999
+ function isAvailable3() {
5000
+ return PaywalloStoreKitNative != null;
5001
+ }
5002
+ function mapNativeProduct(native) {
5003
+ const product = {
5004
+ productId: native.productId,
5005
+ title: native.title,
5006
+ description: native.description,
5007
+ price: native.price,
5008
+ priceValue: native.priceValue,
5009
+ currency: native.currency ?? "USD",
5010
+ localizedPrice: native.localizedPrice,
5011
+ type: mapProductType(native.type),
5012
+ subscriptionPeriod: native.subscriptionPeriod,
5013
+ introductoryPrice: native.introductoryPrice,
5014
+ introductoryPriceValue: native.introductoryPriceValue,
5015
+ freeTrialPeriod: native.freeTrialPeriod
5016
+ };
5017
+ if (native.subscriptionPeriod) {
5018
+ const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
5019
+ product.pricePerMonth = prices.perMonth;
5020
+ product.pricePerWeek = prices.perWeek;
5021
+ product.pricePerDay = prices.perDay;
5154
5022
  }
5155
- return _instance;
5023
+ return product;
5156
5024
  }
5157
- var IAPService, _instance;
5158
- var init_IAPService = __esm({
5159
- "src/domains/iap/IAPService.ts"() {
5025
+ function mapProductType(type) {
5026
+ switch (type) {
5027
+ case "subscription":
5028
+ case "autoRenewable":
5029
+ return "subscription";
5030
+ case "consumable":
5031
+ return "consumable";
5032
+ default:
5033
+ return "non_consumable";
5034
+ }
5035
+ }
5036
+ function calculatePerPeriodPrices(priceValue, period) {
5037
+ const totalDays = periodToDays(period);
5038
+ if (totalDays <= 0) return {};
5039
+ const perDay = priceValue / totalDays;
5040
+ const perWeek = perDay * 7;
5041
+ const perMonth = perDay * 30;
5042
+ return {
5043
+ perDay: perDay.toFixed(2),
5044
+ perWeek: perWeek.toFixed(2),
5045
+ perMonth: perMonth.toFixed(2)
5046
+ };
5047
+ }
5048
+ function periodToDays(period) {
5049
+ const match = period.match(/^P(\d+)([DWMY])$/);
5050
+ if (!match) return 0;
5051
+ const value = parseInt(match[1], 10);
5052
+ switch (match[2]) {
5053
+ case "D":
5054
+ return value;
5055
+ case "W":
5056
+ return value * 7;
5057
+ case "M":
5058
+ return value * 30;
5059
+ case "Y":
5060
+ return value * 365;
5061
+ default:
5062
+ return 0;
5063
+ }
5064
+ }
5065
+ function mapNativePurchase(native) {
5066
+ return {
5067
+ productId: native.productId,
5068
+ transactionId: native.transactionId,
5069
+ transactionDate: native.transactionDate,
5070
+ receipt: native.receipt,
5071
+ platform: Platform12.OS
5072
+ };
5073
+ }
5074
+ function normalizeTransactionUpdate(raw) {
5075
+ if (!raw || typeof raw !== "object") return null;
5076
+ const r = raw;
5077
+ const type = r.type;
5078
+ if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
5079
+ const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
5080
+ const productId = typeof r.productId === "string" ? r.productId : null;
5081
+ if (!transactionId) return null;
5082
+ const update = {
5083
+ type,
5084
+ transactionId,
5085
+ productId: productId ?? ""
5086
+ };
5087
+ if (typeof r.amount === "number") update.amount = r.amount;
5088
+ if (typeof r.currency === "string") update.currency = r.currency;
5089
+ return update;
5090
+ }
5091
+ var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
5092
+ var init_NativeStoreKit = __esm({
5093
+ "src/domains/iap/NativeStoreKit.ts"() {
5160
5094
  "use strict";
5161
5095
  init_PaywalloClient();
5162
- init_IAPTransactionEmitter();
5163
- init_IAPValidator();
5164
- init_NativeStoreKit();
5096
+ init_uuid();
5097
+ init_IdentityManager();
5165
5098
  init_PurchaseError();
5166
- IAPService = class {
5167
- constructor(apiClient) {
5168
- this.productsCache = /* @__PURE__ */ new Map();
5169
- this.apiClient = null;
5170
- this.purchaseInFlight = null;
5171
- /**
5172
- * Idempotency guard against double-finish. Populated with transaction IDs
5173
- * that have already been forwarded to `NativeStoreKit.finishTransaction`.
5174
- */
5175
- this.finishedTransactions = /* @__PURE__ */ new Set();
5176
- this.apiClient = apiClient ?? null;
5177
- this.validator = new IAPValidator(
5178
- (id) => this.productsCache.get(id),
5179
- () => this.getApiClient(),
5180
- () => this.debug,
5181
- () => this.getDeviceCountry()
5182
- );
5183
- this.emitter = new IAPTransactionEmitter(
5184
- (id) => this.productsCache.get(id),
5185
- () => this.apiClient ?? PaywalloClient.getApiClient(),
5186
- () => this.debug
5187
- );
5188
- }
5189
- get debug() {
5190
- return PaywalloClient.getConfig()?.debug ?? false;
5191
- }
5192
- getDeviceCountry() {
5193
- try {
5194
- const locale = Intl.DateTimeFormat().resolvedOptions().locale;
5099
+ TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
5100
+ PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
5101
+ nativeStoreKit = {
5102
+ isAvailable: isAvailable3,
5103
+ async getProducts(productIds) {
5104
+ if (!PaywalloStoreKitNative) {
5105
+ return [];
5106
+ }
5107
+ const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
5108
+ return nativeProducts.map(mapNativeProduct);
5109
+ },
5110
+ async purchase(productId) {
5111
+ if (!PaywalloStoreKitNative) {
5112
+ throw new PurchaseError(
5113
+ PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
5114
+ "StoreKit native module not available"
5115
+ );
5116
+ }
5117
+ const distinctId = identityManager.getDistinctId();
5118
+ const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
5119
+ const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
5120
+ if (distinctId && appAccountToken === null) {
5121
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
5122
+ }
5123
+ try {
5124
+ const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
5125
+ if (!result || result.pending) {
5126
+ return null;
5127
+ }
5128
+ return mapNativePurchase(result);
5129
+ } catch (err) {
5130
+ const message = err instanceof Error ? err.message : String(err);
5131
+ if (message.toLowerCase().includes("cancel")) {
5132
+ throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
5133
+ }
5134
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
5135
+ }
5136
+ },
5137
+ async finishTransaction(transactionId) {
5138
+ if (!PaywalloStoreKitNative) return;
5139
+ try {
5140
+ await PaywalloStoreKitNative.finishTransaction(transactionId);
5141
+ } catch (err) {
5142
+ console.error("[Paywallo] finishTransaction native error", err);
5143
+ }
5144
+ },
5145
+ async getActiveTransactions() {
5146
+ if (!PaywalloStoreKitNative) return [];
5147
+ try {
5148
+ const results = await PaywalloStoreKitNative.getActiveTransactions();
5149
+ return results.map(mapNativePurchase);
5150
+ } catch (err) {
5151
+ console.error("[Paywallo] getActiveTransactions failed", err);
5152
+ return [];
5153
+ }
5154
+ },
5155
+ /**
5156
+ * Subscribes to native transaction update events (renewals, refunds,
5157
+ * cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
5158
+ * `PurchasesUpdatedListener` + refund diff on Android.
5159
+ *
5160
+ * Returns a subscription with a `remove()` method. The caller is
5161
+ * responsible for cleanup (typically on `Paywallo.reset()`).
5162
+ *
5163
+ * Returns `null` when the native module isn't available (non-mobile env,
5164
+ * native module not linked) — callers should treat null as a no-op.
5165
+ */
5166
+ subscribeToTransactionUpdates(listener) {
5167
+ if (!PaywalloStoreKitNative) return null;
5168
+ try {
5169
+ const EmitterCtor = NativeEventEmitter3;
5170
+ const emitter = new EmitterCtor(PaywalloStoreKitNative);
5171
+ const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
5172
+ const update = normalizeTransactionUpdate(payload);
5173
+ if (update) listener(update);
5174
+ });
5175
+ return { remove: () => sub.remove() };
5176
+ } catch (err) {
5177
+ if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
5178
+ return null;
5179
+ }
5180
+ }
5181
+ };
5182
+ NativeStoreKit = nativeStoreKit;
5183
+ }
5184
+ });
5185
+
5186
+ // src/domains/iap/IAPService.ts
5187
+ import { Platform as Platform13 } from "react-native";
5188
+ function getIAPService() {
5189
+ if (!_instance) {
5190
+ _instance = new IAPService();
5191
+ _instance.startTransactionListener();
5192
+ }
5193
+ return _instance;
5194
+ }
5195
+ var IAPService, _instance;
5196
+ var init_IAPService = __esm({
5197
+ "src/domains/iap/IAPService.ts"() {
5198
+ "use strict";
5199
+ init_PaywalloClient();
5200
+ init_IAPTransactionEmitter();
5201
+ init_IAPValidator();
5202
+ init_NativeStoreKit();
5203
+ init_PurchaseError();
5204
+ IAPService = class {
5205
+ constructor(apiClient) {
5206
+ this.productsCache = /* @__PURE__ */ new Map();
5207
+ this.apiClient = null;
5208
+ this.purchaseInFlight = null;
5209
+ /**
5210
+ * Idempotency guard against double-finish. Populated with transaction IDs
5211
+ * that have already been forwarded to `NativeStoreKit.finishTransaction`.
5212
+ */
5213
+ this.finishedTransactions = /* @__PURE__ */ new Set();
5214
+ this.apiClient = apiClient ?? null;
5215
+ this.validator = new IAPValidator(
5216
+ (id) => this.productsCache.get(id),
5217
+ () => this.getApiClient(),
5218
+ () => this.debug,
5219
+ () => this.getDeviceCountry()
5220
+ );
5221
+ this.emitter = new IAPTransactionEmitter(
5222
+ (id) => this.productsCache.get(id),
5223
+ () => this.apiClient ?? PaywalloClient.getApiClient(),
5224
+ () => this.debug
5225
+ );
5226
+ }
5227
+ get debug() {
5228
+ return PaywalloClient.getConfig()?.debug ?? false;
5229
+ }
5230
+ getDeviceCountry() {
5231
+ try {
5232
+ const locale = Intl.DateTimeFormat().resolvedOptions().locale;
5195
5233
  const parts = locale.split("-");
5196
5234
  return parts[parts.length - 1]?.toUpperCase() || "US";
5197
5235
  } catch {
@@ -5245,8 +5283,8 @@ var init_IAPService = __esm({
5245
5283
  stopTransactionListener() {
5246
5284
  this.emitter.stopListener();
5247
5285
  }
5248
- async emitTransactionUpdate(update) {
5249
- return this.emitter.emitTransactionUpdate(update);
5286
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
5287
+ async emitTransactionUpdate(_update) {
5250
5288
  }
5251
5289
  async purchase(productId, options) {
5252
5290
  if (!nativeStoreKit.isAvailable()) {
@@ -5275,10 +5313,6 @@ var init_IAPService = __esm({
5275
5313
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
5276
5314
  await this.finishTransaction(purchase.transactionId);
5277
5315
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
5278
- await this.emitter.emitTransactionCompleted(purchase, productId, {
5279
- paywallId: options?.paywallId,
5280
- variantId: options?.variantId
5281
- });
5282
5316
  }).catch(async (err) => {
5283
5317
  const kind = this.validator.classifyValidationError(err);
5284
5318
  if (kind === "server4xx") {
@@ -5307,7 +5341,7 @@ var init_IAPService = __esm({
5307
5341
  const transactions = await nativeStoreKit.getActiveTransactions();
5308
5342
  const apiClient = this.getApiClient();
5309
5343
  const distinctId = PaywalloClient.getDistinctId();
5310
- const platform = Platform12.OS;
5344
+ const platform = Platform13.OS;
5311
5345
  const results = await Promise.allSettled(
5312
5346
  transactions.map((tx) => {
5313
5347
  const product = this.productsCache.get(tx.productId);
@@ -6379,319 +6413,29 @@ var init_session = __esm({
6379
6413
  }
6380
6414
  });
6381
6415
 
6382
- // src/domains/analytics/AnalyticsError.ts
6383
- var AnalyticsError, ANALYTICS_ERROR_CODES;
6384
- var init_AnalyticsError = __esm({
6385
- "src/domains/analytics/AnalyticsError.ts"() {
6416
+ // src/domains/onboarding/OnboardingError.ts
6417
+ var OnboardingError, ONBOARDING_ERROR_CODES;
6418
+ var init_OnboardingError = __esm({
6419
+ "src/domains/onboarding/OnboardingError.ts"() {
6386
6420
  "use strict";
6387
6421
  init_PaywalloError();
6388
- AnalyticsError = class extends PaywalloError {
6422
+ OnboardingError = class extends PaywalloError {
6389
6423
  constructor(code, message) {
6390
- super("analytics", code, message);
6391
- this.name = "AnalyticsError";
6424
+ super("onboarding", code, message);
6425
+ this.name = "OnboardingError";
6392
6426
  }
6393
6427
  };
6394
- ANALYTICS_ERROR_CODES = {
6395
- NOT_INITIALIZED: "ANALYTICS_NOT_INITIALIZED",
6396
- INVALID_STEP_INDEX: "ANALYTICS_INVALID_STEP_INDEX",
6397
- INVALID_ACTION_NAME: "ANALYTICS_INVALID_ACTION_NAME",
6398
- VALIDATION_ERROR: "ANALYTICS_VALIDATION_ERROR"
6428
+ ONBOARDING_ERROR_CODES = {
6429
+ NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
6430
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
6399
6431
  };
6400
6432
  }
6401
6433
  });
6402
6434
 
6403
- // src/domains/analytics/index.ts
6404
- var init_analytics = __esm({
6405
- "src/domains/analytics/index.ts"() {
6406
- "use strict";
6407
- init_AnalyticsError();
6408
- }
6409
- });
6410
-
6411
- // src/core/events/schemas.ts
6412
- import { z } from "zod";
6413
- var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6414
- var init_schemas = __esm({
6415
- "src/core/events/schemas.ts"() {
6416
- "use strict";
6417
- lifecycleEventSchema = z.object({
6418
- type: z.enum([
6419
- "install",
6420
- "cold_start",
6421
- "foreground",
6422
- "background",
6423
- "session_start",
6424
- "session_end"
6425
- ]),
6426
- session_id: z.string().optional(),
6427
- duration_s: z.number().nonnegative().optional(),
6428
- first_ever: z.boolean().optional(),
6429
- // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6430
- app_version: z.string().optional(),
6431
- device_type: z.enum(["ios", "android"]).optional(),
6432
- os_version: z.string().optional(),
6433
- // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6434
- started_at: z.string().optional(),
6435
- ended_at: z.string().optional()
6436
- }).strict();
6437
- identifyEventSchema = z.object({
6438
- distinct_id: z.string().min(1),
6439
- email: z.email().optional(),
6440
- traits: z.record(z.string(), z.unknown()).optional(),
6441
- attribution: z.record(z.string(), z.unknown()).optional(),
6442
- device: z.record(z.string(), z.unknown()).optional()
6443
- }).strict();
6444
- paywallEventSchema = z.object({
6445
- type: z.enum(["viewed", "closed", "purchased"]),
6446
- paywall_id: z.string().min(1),
6447
- placement: z.string().optional(),
6448
- variant_id: z.string().optional(),
6449
- variant_key: z.string().optional(),
6450
- campaign_id: z.string().optional(),
6451
- // `closed`-specific
6452
- closed_at: z.string().optional(),
6453
- duration_s: z.number().nonnegative().optional(),
6454
- close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6455
- scroll_depth: z.number().min(0).max(1).optional(),
6456
- // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6457
- product_id: z.string().optional()
6458
- }).strict();
6459
- transactionEventSchema = z.object({
6460
- type: z.enum([
6461
- "completed",
6462
- "failed",
6463
- "refunded",
6464
- "renewed",
6465
- "canceled",
6466
- "expired"
6467
- ]),
6468
- tx_id: z.string().min(1),
6469
- amount: z.number().optional(),
6470
- currency: z.string().length(3).optional(),
6471
- product_id: z.string().optional(),
6472
- subscription_id: z.string().optional(),
6473
- paywall_id: z.string().optional(),
6474
- variant_id: z.string().optional(),
6475
- reason: z.string().optional()
6476
- }).strict();
6477
- onboardingEventSchema = z.object({
6478
- type: z.enum(["step", "complete", "drop"]),
6479
- step_index: z.number().int().nonnegative().optional(),
6480
- step_name: z.string().optional(),
6481
- variant_key: z.string().optional(),
6482
- time_on_prev_s: z.number().nonnegative().optional()
6483
- }).strict();
6484
- notificationEventSchema = z.object({
6485
- type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6486
- notification_id: z.string().optional(),
6487
- campaign_id: z.string().optional(),
6488
- variant_key: z.string().nullable().optional(),
6489
- message_id: z.string().optional(),
6490
- device_id: z.string().optional(),
6491
- push_platform: z.enum(["ios", "android"]).optional(),
6492
- timezone: z.string().optional(),
6493
- failure_reason: z.string().optional(),
6494
- app_user_id: z.string().optional()
6495
- }).strict();
6496
- customEventSchema = z.object({
6497
- name: z.string().min(1).regex(
6498
- /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
6499
- "custom event name must be snake_case or $reserved"
6500
- ),
6501
- props: z.record(z.string(), z.unknown()).optional()
6502
- }).strict();
6503
- EVENT_SCHEMAS = {
6504
- lifecycle: lifecycleEventSchema,
6505
- identify: identifyEventSchema,
6506
- paywall: paywallEventSchema,
6507
- transaction: transactionEventSchema,
6508
- onboarding: onboardingEventSchema,
6509
- notification: notificationEventSchema,
6510
- custom: customEventSchema
6511
- };
6512
- }
6513
- });
6514
-
6515
- // src/core/events/eventFamilies.ts
6516
- function isDeprecatedEvent(eventName) {
6517
- return DEPRECATED_EVENT_NAMES.includes(eventName);
6518
- }
6519
- function isCanonicalFamily(name) {
6520
- return CANONICAL_FAMILIES.includes(name);
6521
- }
6522
- function detectFamily(eventName) {
6523
- if (isCanonicalFamily(eventName) && eventName !== "custom") {
6524
- return eventName;
6525
- }
6526
- return "custom";
6527
- }
6528
- function validateEvent(eventName, properties) {
6529
- const family = detectFamily(eventName);
6530
- const schema = EVENT_SCHEMAS[family];
6531
- const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
6532
- const parsed = schema.safeParse(input);
6533
- if (parsed.success) {
6534
- return { ok: true, family, data: parsed.data };
6535
- }
6536
- return { ok: false, family, error: parsed.error };
6537
- }
6538
- 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"() {
6435
+ // src/domains/onboarding/OnboardingManager.ts
6436
+ var OnboardingManager, onboardingManager;
6437
+ var init_OnboardingManager = __esm({
6438
+ "src/domains/onboarding/OnboardingManager.ts"() {
6695
6439
  "use strict";
6696
6440
  init_OnboardingError();
6697
6441
  OnboardingManager = class {
@@ -6713,15 +6457,20 @@ var init_OnboardingManager = __esm({
6713
6457
  * Saves the step name internally for implicit drop detection on the backend.
6714
6458
  *
6715
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).
6716
6461
  */
6717
- async step(stepName) {
6462
+ async step(stepName, order) {
6718
6463
  this.assertConfig();
6719
6464
  this.validateStepName(stepName, "step");
6720
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
+ }
6721
6470
  const payload = {
6722
6471
  family: "onboarding",
6723
6472
  type: "step",
6724
- ...{ step_name: stepName }
6473
+ ...stepPayload
6725
6474
  };
6726
6475
  return this.emit("onboarding", payload, "step");
6727
6476
  }
@@ -6792,12 +6541,290 @@ var init_OnboardingManager = __esm({
6792
6541
  }
6793
6542
  });
6794
6543
 
6795
- // src/domains/onboarding/index.ts
6796
- var init_onboarding = __esm({
6797
- "src/domains/onboarding/index.ts"() {
6544
+ // src/domains/onboarding/index.ts
6545
+ var init_onboarding = __esm({
6546
+ "src/domains/onboarding/index.ts"() {
6547
+ "use strict";
6548
+ init_OnboardingError();
6549
+ init_OnboardingManager();
6550
+ }
6551
+ });
6552
+
6553
+ // src/domains/analytics/AnalyticsError.ts
6554
+ var AnalyticsError, ANALYTICS_ERROR_CODES;
6555
+ var init_AnalyticsError = __esm({
6556
+ "src/domains/analytics/AnalyticsError.ts"() {
6557
+ "use strict";
6558
+ init_PaywalloError();
6559
+ AnalyticsError = class extends PaywalloError {
6560
+ constructor(code, message) {
6561
+ super("analytics", code, message);
6562
+ this.name = "AnalyticsError";
6563
+ }
6564
+ };
6565
+ ANALYTICS_ERROR_CODES = {
6566
+ NOT_INITIALIZED: "ANALYTICS_NOT_INITIALIZED",
6567
+ INVALID_STEP_INDEX: "ANALYTICS_INVALID_STEP_INDEX",
6568
+ INVALID_ACTION_NAME: "ANALYTICS_INVALID_ACTION_NAME",
6569
+ VALIDATION_ERROR: "ANALYTICS_VALIDATION_ERROR"
6570
+ };
6571
+ }
6572
+ });
6573
+
6574
+ // src/domains/analytics/index.ts
6575
+ var init_analytics = __esm({
6576
+ "src/domains/analytics/index.ts"() {
6577
+ "use strict";
6578
+ init_AnalyticsError();
6579
+ }
6580
+ });
6581
+
6582
+ // src/core/events/schemas.ts
6583
+ import { z } from "zod";
6584
+ var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6585
+ var init_schemas = __esm({
6586
+ "src/core/events/schemas.ts"() {
6587
+ "use strict";
6588
+ lifecycleEventSchema = z.object({
6589
+ type: z.enum([
6590
+ "install",
6591
+ "cold_start",
6592
+ "foreground",
6593
+ "background",
6594
+ "session_start",
6595
+ "session_end"
6596
+ ]),
6597
+ session_id: z.string().optional(),
6598
+ duration_s: z.number().nonnegative().optional(),
6599
+ first_ever: z.boolean().optional(),
6600
+ // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6601
+ app_version: z.string().optional(),
6602
+ device_type: z.enum(["ios", "android"]).optional(),
6603
+ os_version: z.string().optional(),
6604
+ // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6605
+ started_at: z.string().optional(),
6606
+ ended_at: z.string().optional()
6607
+ }).strict();
6608
+ identifyEventSchema = z.object({
6609
+ distinct_id: z.string().min(1),
6610
+ email: z.email().optional(),
6611
+ traits: z.record(z.string(), z.unknown()).optional(),
6612
+ attribution: z.record(z.string(), z.unknown()).optional(),
6613
+ device: z.record(z.string(), z.unknown()).optional()
6614
+ }).strict();
6615
+ paywallEventSchema = z.object({
6616
+ type: z.enum(["viewed", "closed", "purchased"]),
6617
+ paywall_id: z.string().min(1),
6618
+ placement: z.string().optional(),
6619
+ variant_id: z.string().optional(),
6620
+ variant_key: z.string().optional(),
6621
+ campaign_id: z.string().optional(),
6622
+ // `closed`-specific
6623
+ closed_at: z.string().optional(),
6624
+ duration_s: z.number().nonnegative().optional(),
6625
+ close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6626
+ scroll_depth: z.number().min(0).max(1).optional(),
6627
+ // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6628
+ product_id: z.string().optional()
6629
+ }).strict();
6630
+ transactionEventSchema = z.object({
6631
+ type: z.enum([
6632
+ "completed",
6633
+ "failed",
6634
+ "refunded",
6635
+ "renewed",
6636
+ "canceled",
6637
+ "expired"
6638
+ ]),
6639
+ tx_id: z.string().min(1),
6640
+ amount: z.number().optional(),
6641
+ currency: z.string().length(3).optional(),
6642
+ product_id: z.string().optional(),
6643
+ subscription_id: z.string().optional(),
6644
+ paywall_id: z.string().optional(),
6645
+ variant_id: z.string().optional(),
6646
+ reason: z.string().optional()
6647
+ }).strict();
6648
+ onboardingEventSchema = z.object({
6649
+ type: z.enum(["step", "complete", "drop"]),
6650
+ step_index: z.number().int().nonnegative().optional(),
6651
+ step_name: z.string().optional(),
6652
+ variant_key: z.string().optional(),
6653
+ time_on_prev_s: z.number().nonnegative().optional()
6654
+ }).strict();
6655
+ notificationEventSchema = z.object({
6656
+ type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6657
+ notification_id: z.string().optional(),
6658
+ campaign_id: z.string().optional(),
6659
+ variant_key: z.string().nullable().optional(),
6660
+ message_id: z.string().optional(),
6661
+ device_id: z.string().optional(),
6662
+ push_platform: z.enum(["ios", "android"]).optional(),
6663
+ timezone: z.string().optional(),
6664
+ failure_reason: z.string().optional(),
6665
+ app_user_id: z.string().optional()
6666
+ }).strict();
6667
+ customEventSchema = z.object({
6668
+ name: z.string().min(1).regex(
6669
+ /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
6670
+ "custom event name must be snake_case or $reserved"
6671
+ ),
6672
+ props: z.record(z.string(), z.unknown()).optional()
6673
+ }).strict();
6674
+ EVENT_SCHEMAS = {
6675
+ lifecycle: lifecycleEventSchema,
6676
+ identify: identifyEventSchema,
6677
+ paywall: paywallEventSchema,
6678
+ transaction: transactionEventSchema,
6679
+ onboarding: onboardingEventSchema,
6680
+ notification: notificationEventSchema,
6681
+ custom: customEventSchema
6682
+ };
6683
+ }
6684
+ });
6685
+
6686
+ // src/core/events/eventFamilies.ts
6687
+ function isDeprecatedEvent(eventName) {
6688
+ return DEPRECATED_EVENT_NAMES.includes(eventName);
6689
+ }
6690
+ function isCanonicalFamily(name) {
6691
+ return CANONICAL_FAMILIES.includes(name);
6692
+ }
6693
+ function detectFamily(eventName) {
6694
+ if (isCanonicalFamily(eventName) && eventName !== "custom") {
6695
+ return eventName;
6696
+ }
6697
+ return "custom";
6698
+ }
6699
+ function validateEvent(eventName, properties) {
6700
+ const family = detectFamily(eventName);
6701
+ const schema = EVENT_SCHEMAS[family];
6702
+ const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
6703
+ const parsed = schema.safeParse(input);
6704
+ if (parsed.success) {
6705
+ return { ok: true, family, data: parsed.data };
6706
+ }
6707
+ return { ok: false, family, error: parsed.error };
6708
+ }
6709
+ function formatValidationError(error) {
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
+ ];
6736
+ }
6737
+ });
6738
+
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
+ import { NativeModules as NativeModules7 } from "react-native";
7164
+ async function detectEnvironment() {
7165
+ if (cached !== null) return cached;
7166
+ if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
7167
+ cached = "sandbox";
7168
+ return cached;
7169
+ }
7170
+ if (!nativeModule) {
7171
+ cached = "production";
7172
+ return cached;
7173
+ }
7174
+ try {
7175
+ const result = await nativeModule.detect();
7176
+ cached = result === "sandbox" ? "sandbox" : "production";
7177
+ return cached;
7178
+ } catch {
7179
+ cached = "production";
7180
+ return cached;
7181
+ }
7182
+ }
7183
+ var nativeModule, cached;
7184
+ var init_detectEnvironment = __esm({
7185
+ "src/utils/detectEnvironment.ts"() {
7186
+ "use strict";
7187
+ nativeModule = NativeModules7.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(
@@ -7457,7 +7514,7 @@ var init_ApiClientQueue = __esm({
7457
7514
  });
7458
7515
 
7459
7516
  // src/core/eventEnvelopeV2.ts
7460
- import { Platform as Platform13 } from "react-native";
7517
+ import { Platform as Platform14 } from "react-native";
7461
7518
  import { z as z2 } from "zod";
7462
7519
  function buildV2Envelope(events, providerContext) {
7463
7520
  const context = { ...providerContext };
@@ -7473,21 +7530,42 @@ function buildV2Envelope(events, providerContext) {
7473
7530
  if (family === "custom") {
7474
7531
  payload.event_name = event.eventName;
7475
7532
  }
7476
- return {
7477
- id: generateUUID(),
7478
- ts: event.timestamp,
7479
- name: family,
7480
- payload
7481
- };
7533
+ const candidateId = payload.installEventId;
7534
+ let id;
7535
+ if (typeof candidateId === "string" && isValidUUIDv4(candidateId)) {
7536
+ id = candidateId;
7537
+ delete payload.installEventId;
7538
+ } else {
7539
+ id = generateUUID();
7540
+ }
7541
+ return { id, ts: event.timestamp, name: family, payload };
7482
7542
  });
7483
7543
  if (context.sdk_version === void 0) context.sdk_version = SDK_VERSION;
7484
- if (context.platform === void 0) context.platform = Platform13.OS;
7544
+ if (context.platform === void 0) context.platform = Platform14.OS;
7485
7545
  if (context.distinct_id === void 0 && events.length > 0) {
7486
7546
  context.distinct_id = events[0].distinctId;
7487
7547
  }
7488
7548
  if (!context.distinct_id || context.distinct_id.length === 0) {
7489
7549
  console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
7490
7550
  }
7551
+ const idsFromProps = {};
7552
+ const existingIds = context.ids;
7553
+ for (const event of events) {
7554
+ for (const { key, aliases } of IDS_PROMO_ALIASES) {
7555
+ if (existingIds?.[key] !== void 0) continue;
7556
+ if (idsFromProps[key] !== void 0) continue;
7557
+ for (const alias of aliases) {
7558
+ const val = event.properties[alias];
7559
+ if (typeof val === "string" && val.length > 0) {
7560
+ idsFromProps[key] = val;
7561
+ break;
7562
+ }
7563
+ }
7564
+ }
7565
+ }
7566
+ if (Object.keys(idsFromProps).length > 0) {
7567
+ context.ids = { ...idsFromProps, ...context.ids };
7568
+ }
7491
7569
  return { context, events: v2Events };
7492
7570
  }
7493
7571
  function resolveProviderContext(provider) {
@@ -7521,7 +7599,7 @@ function splitPayloadAndContext(properties) {
7521
7599
  }
7522
7600
  return { payload, promoted };
7523
7601
  }
7524
- var v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7602
+ var v2EventSchema, v2ContextSchema, v2EnvelopeSchema, IDS_PROMO_ALIASES, CONTEXT_KEY_ALIASES;
7525
7603
  var init_eventEnvelopeV2 = __esm({
7526
7604
  "src/core/eventEnvelopeV2.ts"() {
7527
7605
  "use strict";
@@ -7547,6 +7625,7 @@ var init_eventEnvelopeV2 = __esm({
7547
7625
  device_model: z2.string().optional(),
7548
7626
  timezone: z2.string().optional(),
7549
7627
  locale: z2.string().optional(),
7628
+ country: z2.string().optional(),
7550
7629
  carrier: z2.string().optional(),
7551
7630
  screen_width: z2.number().optional(),
7552
7631
  screen_height: z2.number().optional(),
@@ -7558,6 +7637,12 @@ var init_eventEnvelopeV2 = __esm({
7558
7637
  context: v2ContextSchema,
7559
7638
  events: z2.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,7 +7679,7 @@ var init_events = __esm({
7593
7679
  });
7594
7680
 
7595
7681
  // src/core/EventBatcher.ts
7596
- import { Platform as Platform14 } from "react-native";
7682
+ import { Platform as Platform15 } from "react-native";
7597
7683
  var BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7598
7684
  var init_EventBatcher = __esm({
7599
7685
  "src/core/EventBatcher.ts"() {
@@ -7646,7 +7732,7 @@ var init_EventBatcher = __esm({
7646
7732
  const event = {
7647
7733
  eventName,
7648
7734
  distinctId,
7649
- properties: { ...properties, platform: Platform14.OS },
7735
+ properties: { ...properties, platform: Platform15.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;
@@ -7972,7 +8058,7 @@ var init_http = __esm({
7972
8058
  });
7973
8059
 
7974
8060
  // src/core/ApiClient.ts
7975
- import { Platform as Platform15 } from "react-native";
8061
+ import { Platform as Platform16 } from "react-native";
7976
8062
  function normalizeDateOfBirth(raw) {
7977
8063
  if (!raw) return void 0;
7978
8064
  if (DATE_OF_BIRTH_RE.test(raw)) return raw;
@@ -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": Platform15.OS,
8197
+ "x-sdk-platform": Platform16.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: Platform15.OS
8227
+ platform: Platform16.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: Platform15.OS,
8245
+ platform: Platform16.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
- import { NativeModules as NativeModules8 } from "react-native";
8436
- async function detectEnvironment() {
8437
- if (cached !== null) return cached;
8438
- if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
8439
- cached = "sandbox";
8440
- return cached;
8441
- }
8442
- if (!nativeModule) {
8443
- cached = "production";
8444
- return cached;
8445
- }
8446
- try {
8447
- const result = await nativeModule.detect();
8448
- cached = result === "sandbox" ? "sandbox" : "production";
8449
- return cached;
8450
- } catch {
8451
- cached = "production";
8452
- return cached;
8453
- }
8454
- }
8455
- var nativeModule, cached;
8456
- var init_detectEnvironment = __esm({
8457
- "src/utils/detectEnvironment.ts"() {
8458
- "use strict";
8459
- nativeModule = NativeModules8.PaywalloEnv ?? null;
8460
- cached = null;
8461
- }
8462
- });
8463
-
8464
8520
  // src/core/PaywalloInitializer.ts
8465
8521
  import { Dimensions as Dimensions4, PixelRatio as PixelRatio2 } from "react-native";
8466
8522
  async function initWithRetry(state, config) {
@@ -8476,8 +8532,8 @@ async function initWithRetry(state, config) {
8476
8532
  } catch (error) {
8477
8533
  state.initAttempts++;
8478
8534
  if (state.initAttempts > MAX_INIT_RETRIES) throw error;
8479
- const delay = RETRY_DELAYS[state.initAttempts - 1] ?? 1e4;
8480
- await new Promise((resolve) => setTimeout(resolve, delay));
8535
+ const delay2 = RETRY_DELAYS[state.initAttempts - 1] ?? 1e4;
8536
+ await new Promise((resolve) => setTimeout(resolve, delay2));
8481
8537
  }
8482
8538
  }
8483
8539
  }
@@ -8507,7 +8563,7 @@ function reportInitFailure(state, config, error, reason) {
8507
8563
  try {
8508
8564
  const tempClient = new ApiClient(
8509
8565
  config.appKey,
8510
- config.apiUrl ?? DEFAULT_API_URL,
8566
+ DEFAULT_API_URL,
8511
8567
  config.debug
8512
8568
  );
8513
8569
  void tempClient.reportError("INIT_FAILED", reason, {
@@ -8557,7 +8613,7 @@ async function doInit(state, config) {
8557
8613
  ]);
8558
8614
  const apiClient = new ApiClient(
8559
8615
  config.appKey,
8560
- config.apiUrl ?? DEFAULT_API_URL,
8616
+ DEFAULT_API_URL,
8561
8617
  config.debug,
8562
8618
  environment,
8563
8619
  offlineQueueEnabled,
@@ -8631,8 +8687,8 @@ async function doInit(state, config) {
8631
8687
  let timezone;
8632
8688
  try {
8633
8689
  const opts = Intl.DateTimeFormat().resolvedOptions();
8634
- locale = opts.locale || void 0;
8635
- timezone = opts.timeZone || void 0;
8690
+ locale = device?.languageTag || opts.locale || void 0;
8691
+ timezone = device?.timeZone || opts.timeZone || void 0;
8636
8692
  } catch {
8637
8693
  }
8638
8694
  let screenWidth;
@@ -8656,7 +8712,7 @@ async function doInit(state, config) {
8656
8712
  device_model: device?.modelId || device?.model || void 0,
8657
8713
  timezone,
8658
8714
  locale,
8659
- carrier: device?.carrier || void 0,
8715
+ ...device?.regionCode && { country: device.regionCode },
8660
8716
  screen_width: screenWidth,
8661
8717
  screen_height: screenHeight,
8662
8718
  screen_density: screenDensity,
@@ -8722,6 +8778,7 @@ var init_PaywalloInitializer = __esm({
8722
8778
  init_plan();
8723
8779
  init_session();
8724
8780
  init_SubscriptionManager();
8781
+ init_detectEnvironment();
8725
8782
  init_NativeDeviceInfo();
8726
8783
  init_ApiClient();
8727
8784
  init_constants();
@@ -8731,7 +8788,6 @@ var init_PaywalloInitializer = __esm({
8731
8788
  init_PaywalloState();
8732
8789
  init_queue();
8733
8790
  init_NativeStorage();
8734
- init_detectEnvironment();
8735
8791
  }
8736
8792
  });
8737
8793
 
@@ -8852,6 +8908,7 @@ var init_PaywalloClient = __esm({
8852
8908
  init_plan();
8853
8909
  init_session();
8854
8910
  init_ClientError();
8911
+ init_onboarding();
8855
8912
  init_network();
8856
8913
  init_PaywalloAnalytics();
8857
8914
  init_PaywalloFlags();
@@ -8921,8 +8978,9 @@ var init_PaywalloClient = __esm({
8921
8978
  async track(eventName, options) {
8922
8979
  return trackEvent(this.getApiClientOrThrow(), this.state, eventName, options);
8923
8980
  }
8924
- async onboardingStep(index, name) {
8925
- return trackOnboardingStep(this.getApiClientOrThrow(), this.state, index, name);
8981
+ async onboardingStep(order, name) {
8982
+ this.ensureInitialized();
8983
+ return onboardingManager.step(name, order);
8926
8984
  }
8927
8985
  async coreAction(actionName) {
8928
8986
  this.ensureInitialized();
@@ -9009,6 +9067,9 @@ var init_PaywalloClient = __esm({
9009
9067
  getConfig() {
9010
9068
  return this.state.config;
9011
9069
  }
9070
+ getId() {
9071
+ return identityManager.getDistinctId();
9072
+ }
9012
9073
  getDistinctId() {
9013
9074
  return identityManager.getDistinctId();
9014
9075
  }
@@ -9146,9 +9207,9 @@ __export(OfferingService_exports, {
9146
9207
  OfferingService: () => OfferingService,
9147
9208
  offeringService: () => offeringService
9148
9209
  });
9149
- import { Platform as Platform16 } from "react-native";
9210
+ import { Platform as Platform17 } from "react-native";
9150
9211
  function resolveProductId(raw) {
9151
- const isIos = Platform16.OS === "ios";
9212
+ const isIos = Platform17.OS === "ios";
9152
9213
  const productId = isIos ? raw.apple_product_id : raw.google_product_id;
9153
9214
  return productId ?? "";
9154
9215
  }
@@ -9167,7 +9228,7 @@ function mapProduct2(raw) {
9167
9228
  };
9168
9229
  }
9169
9230
  function pickPlatformProduct(products) {
9170
- const isIos = Platform16.OS === "ios";
9231
+ const isIos = Platform17.OS === "ios";
9171
9232
  const match = products.find((p) => isIos ? p.apple_product_id : p.google_product_id);
9172
9233
  return match ?? products[0] ?? null;
9173
9234
  }
@@ -9394,7 +9455,7 @@ init_onboarding();
9394
9455
  import { useCallback as useCallback9 } from "react";
9395
9456
  function useOnboarding() {
9396
9457
  const step = useCallback9(
9397
- (stepName) => onboardingManager.step(stepName),
9458
+ (stepName, order) => onboardingManager.step(stepName, order),
9398
9459
  []
9399
9460
  );
9400
9461
  const complete = useCallback9(
@@ -9738,8 +9799,41 @@ function useSubscription() {
9738
9799
  };
9739
9800
  }
9740
9801
 
9802
+ // src/hooks/usePaywalloScreenTracking.ts
9803
+ init_PaywalloClient();
9804
+ import { useCallback as useCallback15, useRef as useRef8 } from "react";
9805
+ function isNavState(value) {
9806
+ return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
9807
+ }
9808
+ function getActiveRouteName(state) {
9809
+ try {
9810
+ if (!isNavState(state)) return null;
9811
+ const route = state.routes[state.index];
9812
+ if (!route) return null;
9813
+ if (isNavState(route.state)) {
9814
+ return getActiveRouteName(route.state);
9815
+ }
9816
+ return typeof route.name === "string" ? route.name : null;
9817
+ } catch {
9818
+ return null;
9819
+ }
9820
+ }
9821
+ function usePaywalloScreenTracking() {
9822
+ const lastScreenRef = useRef8(null);
9823
+ return useCallback15((state) => {
9824
+ if (!PaywalloClient.isReady()) return;
9825
+ const screenName = getActiveRouteName(state);
9826
+ if (!screenName || screenName === lastScreenRef.current) return;
9827
+ lastScreenRef.current = screenName;
9828
+ void PaywalloClient.track("screen_view", {
9829
+ properties: { screen_name: screenName }
9830
+ }).catch(() => {
9831
+ });
9832
+ }, []);
9833
+ }
9834
+
9741
9835
  // src/PaywalloProvider.tsx
9742
- import { useCallback as useCallback19, useEffect as useEffect14, useLayoutEffect, useMemo as useMemo3, useRef as useRef11, useState as useState17 } from "react";
9836
+ import { useCallback as useCallback20, useEffect as useEffect14, useLayoutEffect, useMemo as useMemo3, useRef as useRef12, useState as useState17 } from "react";
9743
9837
  import { Animated as Animated4, Dimensions as Dimensions5, Easing as Easing2, StyleSheet as StyleSheet5 } from "react-native";
9744
9838
  init_PaywalloClient();
9745
9839
  init_NativeStorage();
@@ -9754,12 +9848,17 @@ init_paywall();
9754
9848
 
9755
9849
  // src/domains/paywall/SuperwallAttributeBridge.ts
9756
9850
  init_identity();
9851
+ var CONFIG_POLL_INTERVAL_MS = 250;
9852
+ var CONFIG_POLL_MAX_ATTEMPTS = 40;
9757
9853
  var lastSignature = null;
9758
9854
  function log(debug, msg, data) {
9759
9855
  if (!debug) return;
9760
9856
  if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
9761
9857
  else console.log(`[Paywallo:SuperwallAttr] ${msg}`);
9762
9858
  }
9859
+ function delay(ms) {
9860
+ return new Promise((resolve) => setTimeout(resolve, ms));
9861
+ }
9763
9862
  var PAID_MEDIUM = /cpc|ppc|cpm|cpa|paid|display/i;
9764
9863
  function deriveIsPaid(a) {
9765
9864
  if (a.fbclid || a.gclid || a.ttclid || a.tiktokCampaignId) return true;
@@ -9798,24 +9897,38 @@ function buildSuperwallAttributes(a) {
9798
9897
  out.pw_attributed_at = a.capturedAt;
9799
9898
  return out;
9800
9899
  }
9801
- function pickShared(mod) {
9802
- const shared = mod?.default?.shared ?? mod?.shared;
9803
- return shared && typeof shared.setUserAttributes === "function" ? shared : null;
9900
+ function pickNative(mod) {
9901
+ const native = mod?.SuperwallExpoModule ?? mod?.default?.SuperwallExpoModule;
9902
+ return native && typeof native.setUserAttributes === "function" && typeof native.getConfigurationStatus === "function" ? native : null;
9804
9903
  }
9805
- async function resolveSuperwallShared(debug) {
9904
+ async function resolveSuperwallNativeModule(debug) {
9806
9905
  try {
9807
- const shared = pickShared(await import("expo-superwall/compat"));
9808
- if (shared) return shared;
9906
+ const native = pickNative(await import("expo-superwall"));
9907
+ if (native) return native;
9809
9908
  } catch {
9810
9909
  }
9811
- try {
9812
- const shared = pickShared(await import("expo-superwall"));
9813
- if (shared) return shared;
9814
- } catch {
9815
- }
9816
- log(debug, "expo-superwall not available \u2014 attribute push skipped");
9910
+ log(debug, "expo-superwall native module not available \u2014 attribute push skipped");
9817
9911
  return null;
9818
9912
  }
9913
+ async function waitForConfigured(mod, debug) {
9914
+ for (let attempt = 0; attempt < CONFIG_POLL_MAX_ATTEMPTS; attempt++) {
9915
+ let status;
9916
+ try {
9917
+ status = String(await mod.getConfigurationStatus());
9918
+ } catch (err) {
9919
+ log(debug, "getConfigurationStatus error", { err: String(err) });
9920
+ return false;
9921
+ }
9922
+ if (status === "CONFIGURED") return true;
9923
+ if (status === "FAILED") {
9924
+ log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
9925
+ return false;
9926
+ }
9927
+ await delay(CONFIG_POLL_INTERVAL_MS);
9928
+ }
9929
+ log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
9930
+ return false;
9931
+ }
9819
9932
  async function pushAttributionToSuperwall(debug = false) {
9820
9933
  const attrs = buildSuperwallAttributes(attributionTracker.get());
9821
9934
  const signature = JSON.stringify(attrs);
@@ -9823,10 +9936,11 @@ async function pushAttributionToSuperwall(debug = false) {
9823
9936
  log(debug, "attributes unchanged \u2014 skipping push");
9824
9937
  return;
9825
9938
  }
9826
- const shared = await resolveSuperwallShared(debug);
9827
- if (!shared) return;
9939
+ const native = await resolveSuperwallNativeModule(debug);
9940
+ if (!native) return;
9941
+ if (!await waitForConfigured(native, debug)) return;
9828
9942
  try {
9829
- await shared.setUserAttributes(attrs);
9943
+ await native.setUserAttributes(attrs);
9830
9944
  lastSignature = signature;
9831
9945
  log(debug, "attributes pushed to Superwall", attrs);
9832
9946
  } catch (err) {
@@ -9942,50 +10056,9 @@ function buildCallbacks() {
9942
10056
  const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
9943
10057
  log2("onPurchase", { productId, identifier });
9944
10058
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
9945
- },
9946
- // Global Superwall event listener — used specifically for `transactionComplete`
9947
- // which is the only event that exposes the full product (currency/price) +
9948
- // transaction (storeTransactionId) shape needed to create a server-side
9949
- // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
9950
- // handled above with their typed callbacks).
9951
- onSuperwallEvent: (info) => {
9952
- const ev = info?.event;
9953
- if (!ev || ev.event !== "transactionComplete") return;
9954
- const ts = Date.now();
9955
- const product = ev.product;
9956
- const transaction = ev.transaction;
9957
- if (!product) {
9958
- log2("transactionComplete missing product \u2014 skipping transaction track");
9959
- return;
9960
- }
9961
- const productId = product.productIdentifier ?? product.id ?? "unknown";
9962
- const currency = product.currencyCode ?? "USD";
9963
- const price = product.price ?? 0;
9964
- const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
9965
- const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
9966
- const paywallIdentifier = ev.paywallInfo?.identifier;
9967
- log2("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
9968
- void (async () => {
9969
- try {
9970
- await PaywalloClient.track("transaction", {
9971
- priority: "critical",
9972
- properties: {
9973
- type: isTrial ? "trial_started" : "completed",
9974
- // Server's IngestTranslator looks for `transaction_id`/`product_id`
9975
- // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
9976
- // then resolves type=trial/new/renewal based on amount being 0 vs >0.
9977
- transaction_id: transactionId,
9978
- product_id: productId,
9979
- amount: price,
9980
- currency,
9981
- ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
9982
- }
9983
- });
9984
- } catch (err) {
9985
- log2("transactionComplete track error", { err: String(err) });
9986
- }
9987
- })();
9988
10059
  }
10060
+ // onSuperwallEvent removed — transaction sale events are sourced exclusively
10061
+ // from server-side webhooks (Apple/Google/Superwall/RevenueCat).
9989
10062
  };
9990
10063
  }
9991
10064
  function startSuperwallAutoBridge(debug = false) {
@@ -10029,7 +10102,7 @@ init_session();
10029
10102
  init_ClientError();
10030
10103
 
10031
10104
  // src/hooks/useAppAutoEvents.ts
10032
- import { useEffect as useEffect13, useRef as useRef8 } from "react";
10105
+ import { useEffect as useEffect13, useRef as useRef9 } from "react";
10033
10106
 
10034
10107
  // src/utils/platformDetection.ts
10035
10108
  async function detectPlatform3() {
@@ -10055,7 +10128,7 @@ var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
10055
10128
  var DEBOUNCE_MS = 1e3;
10056
10129
  function useAppAutoEvents(config) {
10057
10130
  const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
10058
- const didRunRef = useRef8(false);
10131
+ const didRunRef = useRef9(false);
10059
10132
  useEffect13(() => {
10060
10133
  if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
10061
10134
  if (!isInitialized || didRunRef.current) return;
@@ -10066,13 +10139,13 @@ function useAppAutoEvents(config) {
10066
10139
  const sessionId = getSessionId();
10067
10140
  const appVersion = getAppVersion2();
10068
10141
  const deviceType = await detectPlatform3();
10069
- const osVersion = await detectOsVersion();
10070
- if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
10142
+ const osVersion2 = await detectOsVersion();
10143
+ if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion: osVersion2, appVersion, sessionId, hasSessionId: !!sessionId });
10071
10144
  try {
10072
10145
  if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
10073
10146
  const firstSeen = await storageGet(FIRST_SEEN_KEY);
10074
10147
  if (!firstSeen) {
10075
- await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
10148
+ await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion2, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
10076
10149
  await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
10077
10150
  if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
10078
10151
  if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
@@ -10084,7 +10157,7 @@ function useAppAutoEvents(config) {
10084
10157
  }
10085
10158
  try {
10086
10159
  if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
10087
- await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
10160
+ await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion2, ...sessionId ? { session_id: sessionId } : {} }, "normal");
10088
10161
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
10089
10162
  } catch (err) {
10090
10163
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start failed", err);
@@ -10098,7 +10171,7 @@ function useAppAutoEvents(config) {
10098
10171
  // src/hooks/useCampaignPreload.ts
10099
10172
  init_PaywalloClient();
10100
10173
  init_CampaignError();
10101
- import { useCallback as useCallback15, useRef as useRef9, useState as useState14 } from "react";
10174
+ import { useCallback as useCallback16, useRef as useRef10, useState as useState14 } from "react";
10102
10175
 
10103
10176
  // src/utils/loadCampaignProducts.ts
10104
10177
  init_IAPService();
@@ -10168,16 +10241,16 @@ function getCacheEntry(cache, placement) {
10168
10241
  // src/hooks/useCampaignPreload.ts
10169
10242
  function useCampaignPreload() {
10170
10243
  const [preloadedWebView, setPreloadedWebView] = useState14(null);
10171
- const preloadedCampaignsRef = useRef9(/* @__PURE__ */ new Map());
10172
- const isPreloadValid = useCallback15(
10244
+ const preloadedCampaignsRef = useRef10(/* @__PURE__ */ new Map());
10245
+ const isPreloadValid = useCallback16(
10173
10246
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
10174
10247
  []
10175
10248
  );
10176
- const consumePreloadedCampaign = useCallback15(
10249
+ const consumePreloadedCampaign = useCallback16(
10177
10250
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
10178
10251
  []
10179
10252
  );
10180
- const preloadCampaign = useCallback15(
10253
+ const preloadCampaign = useCallback16(
10181
10254
  async (placement, context) => {
10182
10255
  try {
10183
10256
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -10246,11 +10319,11 @@ function useCampaignPreload() {
10246
10319
  }
10247
10320
 
10248
10321
  // src/hooks/useProductLoader.ts
10249
- import { useCallback as useCallback16, useState as useState15 } from "react";
10322
+ import { useCallback as useCallback17, useState as useState15 } from "react";
10250
10323
  function useProductLoader() {
10251
10324
  const [products, setProducts] = useState15(/* @__PURE__ */ new Map());
10252
10325
  const [isLoadingProducts, setIsLoadingProducts] = useState15(false);
10253
- const refreshProducts = useCallback16(async (productIds) => {
10326
+ const refreshProducts = useCallback17(async (productIds) => {
10254
10327
  setIsLoadingProducts(true);
10255
10328
  try {
10256
10329
  const iapService = getIAPService();
@@ -10268,11 +10341,11 @@ function useProductLoader() {
10268
10341
  }
10269
10342
 
10270
10343
  // src/hooks/usePurchaseOrchestrator.ts
10271
- import { useCallback as useCallback17 } from "react";
10344
+ import { useCallback as useCallback18 } from "react";
10272
10345
  init_paywall();
10273
10346
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
10274
10347
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
10275
- const handlePreloadedPurchase = useCallback17(
10348
+ const handlePreloadedPurchase = useCallback18(
10276
10349
  async (productId) => {
10277
10350
  try {
10278
10351
  if (preloadedWebView) {
@@ -10310,7 +10383,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
10310
10383
  },
10311
10384
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
10312
10385
  );
10313
- const handlePreloadedRestore = useCallback17(async () => {
10386
+ const handlePreloadedRestore = useCallback18(async () => {
10314
10387
  try {
10315
10388
  const transactions = await getIAPService().restore();
10316
10389
  if (transactions.length === 0) return;
@@ -10335,7 +10408,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
10335
10408
  // src/hooks/useSubscriptionSync.ts
10336
10409
  init_PaywalloClient();
10337
10410
  init_SubscriptionCache();
10338
- import { useCallback as useCallback18, useRef as useRef10, useState as useState16 } from "react";
10411
+ import { useCallback as useCallback19, useRef as useRef11, useState as useState16 } from "react";
10339
10412
 
10340
10413
  // src/hooks/subscriptionSyncHelpers.ts
10341
10414
  init_SubscriptionCache();
@@ -10444,17 +10517,17 @@ async function checkPersistedCache(distinctId, onHit) {
10444
10517
  // src/hooks/useSubscriptionSync.ts
10445
10518
  function useSubscriptionSync() {
10446
10519
  const [subscription, setSubscription] = useState16(null);
10447
- const cacheRef = useRef10(null);
10448
- const setCacheAndState = useCallback18((sub) => {
10520
+ const cacheRef = useRef11(null);
10521
+ const setCacheAndState = useCallback19((sub) => {
10449
10522
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
10450
10523
  setSubscription(sub);
10451
10524
  }, []);
10452
- const invalidateCache = useCallback18(() => {
10525
+ const invalidateCache = useCallback19(() => {
10453
10526
  cacheRef.current = null;
10454
10527
  const distinctId = PaywalloClient.getDistinctId();
10455
10528
  if (distinctId) void subscriptionCache.invalidate(distinctId);
10456
10529
  }, []);
10457
- const fetchAndCache = useCallback18(async () => {
10530
+ const fetchAndCache = useCallback19(async () => {
10458
10531
  const apiClient = PaywalloClient.getApiClient();
10459
10532
  const distinctId = PaywalloClient.getDistinctId();
10460
10533
  if (!apiClient || !distinctId) return null;
@@ -10464,7 +10537,7 @@ function useSubscriptionSync() {
10464
10537
  void subscriptionCache.set(distinctId, toDomainResponse(status));
10465
10538
  return sub;
10466
10539
  }, [setCacheAndState]);
10467
- const hasActiveSubscription = useCallback18(async () => {
10540
+ const hasActiveSubscription = useCallback19(async () => {
10468
10541
  const apiClient = PaywalloClient.getApiClient();
10469
10542
  const distinctId = PaywalloClient.getDistinctId();
10470
10543
  if (!apiClient || !distinctId) return false;
@@ -10484,7 +10557,7 @@ function useSubscriptionSync() {
10484
10557
  return resolveOfflineFromCache(distinctId);
10485
10558
  }
10486
10559
  }, [setCacheAndState]);
10487
- const getSubscription = useCallback18(async () => {
10560
+ const getSubscription = useCallback19(async () => {
10488
10561
  const apiClient = PaywalloClient.getApiClient();
10489
10562
  const distinctId = PaywalloClient.getDistinctId();
10490
10563
  if (!apiClient || !distinctId) return null;
@@ -10516,12 +10589,12 @@ function PaywalloProvider({ children, config }) {
10516
10589
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
10517
10590
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
10518
10591
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
10519
- const clearPreloadedWebView = useCallback19(() => setPreloadedWebView(null), [setPreloadedWebView]);
10520
- const autoPreloadTriggeredRef = useRef11(false);
10521
- const preloadedWebViewRef = useRef11(preloadedWebView);
10522
- const pollTimerRef = useRef11(null);
10523
- const pollCancelledRef = useRef11(false);
10524
- const secureStorageRef = useRef11(new SecureStorage(config.debug));
10592
+ const clearPreloadedWebView = useCallback20(() => setPreloadedWebView(null), [setPreloadedWebView]);
10593
+ const autoPreloadTriggeredRef = useRef12(false);
10594
+ const preloadedWebViewRef = useRef12(preloadedWebView);
10595
+ const pollTimerRef = useRef12(null);
10596
+ const pollCancelledRef = useRef12(false);
10597
+ const secureStorageRef = useRef12(new SecureStorage(config.debug));
10525
10598
  useEffect14(() => {
10526
10599
  preloadedWebViewRef.current = preloadedWebView;
10527
10600
  }, [preloadedWebView]);
@@ -10548,20 +10621,20 @@ function PaywalloProvider({ children, config }) {
10548
10621
  },
10549
10622
  debug: config.debug
10550
10623
  });
10551
- const triggerRepreload = useCallback19((placement) => {
10624
+ const triggerRepreload = useCallback20((placement) => {
10552
10625
  void PaywalloClient.preloadCampaign(placement).catch(() => {
10553
10626
  });
10554
10627
  }, []);
10555
10628
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
10556
10629
  const SCREEN_HEIGHT = useMemo3(() => Dimensions5.get("window").height, []);
10557
- const slideAnim = useRef11(new Animated4.Value(SCREEN_HEIGHT)).current;
10558
- const handlePreloadedWebViewReady = useCallback19(() => {
10630
+ const slideAnim = useRef12(new Animated4.Value(SCREEN_HEIGHT)).current;
10631
+ const handlePreloadedWebViewReady = useCallback20(() => {
10559
10632
  if (PaywalloClient.getConfig()?.debug) {
10560
10633
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
10561
10634
  }
10562
10635
  baseHandlePreloadedWebViewReady();
10563
10636
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
10564
- const handlePreloadedClose = useCallback19(() => {
10637
+ const handlePreloadedClose = useCallback20(() => {
10565
10638
  const placement = preloadedWebView?.placement;
10566
10639
  Animated4.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
10567
10640
  baseHandlePreloadedClose();
@@ -10577,7 +10650,7 @@ function PaywalloProvider({ children, config }) {
10577
10650
  invalidateCache
10578
10651
  );
10579
10652
  const [isPreloadPurchasing, setIsPreloadPurchasing] = useState17(false);
10580
- const handlePreloadedPurchase = useCallback19(async (productId) => {
10653
+ const handlePreloadedPurchase = useCallback20(async (productId) => {
10581
10654
  setIsPreloadPurchasing(true);
10582
10655
  try {
10583
10656
  await rawPreloadedPurchase(productId);
@@ -10585,15 +10658,25 @@ function PaywalloProvider({ children, config }) {
10585
10658
  setIsPreloadPurchasing(false);
10586
10659
  }
10587
10660
  }, [rawPreloadedPurchase]);
10588
- const configRef = useRef11(config);
10661
+ const configRef = useRef12(config);
10589
10662
  useEffect14(() => {
10590
10663
  let mounted = true;
10591
- initLocalization({ detectDevice: true });
10592
- PaywalloClient.init(configRef.current).then(async () => {
10664
+ const bootstrap = async () => {
10665
+ if (!PaywalloClient.isReady()) {
10666
+ initLocalization({ detectDevice: true });
10667
+ try {
10668
+ await PaywalloClient.init(configRef.current);
10669
+ } catch (err) {
10670
+ if (!mounted) return;
10671
+ setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
10672
+ return;
10673
+ }
10674
+ if (!mounted) return;
10675
+ startSuperwallAutoBridge(configRef.current.debug);
10676
+ void pushAttributionToSuperwall(configRef.current.debug);
10677
+ }
10593
10678
  if (!mounted) return;
10594
10679
  setDistinctId(PaywalloClient.getDistinctId());
10595
- startSuperwallAutoBridge(configRef.current.debug);
10596
- void pushAttributionToSuperwall(configRef.current.debug);
10597
10680
  setIsInitialized(true);
10598
10681
  if (!autoPreloadTriggeredRef.current) {
10599
10682
  autoPreloadTriggeredRef.current = true;
@@ -10606,19 +10689,17 @@ function PaywalloProvider({ children, config }) {
10606
10689
  });
10607
10690
  }
10608
10691
  }
10609
- }).catch((err) => {
10610
- if (!mounted) return;
10611
- setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
10612
- });
10692
+ };
10693
+ void bootstrap();
10613
10694
  return () => {
10614
10695
  mounted = false;
10615
10696
  };
10616
10697
  }, [preloadCampaign]);
10617
- const getPaywallConfig = useCallback19(async (p) => {
10698
+ const getPaywallConfig = useCallback20(async (p) => {
10618
10699
  const api = PaywalloClient.getApiClient();
10619
10700
  return api ? api.getPaywall(p) : null;
10620
10701
  }, []);
10621
- const presentPaywall = useCallback19((placement) => {
10702
+ const presentPaywall = useCallback20((placement) => {
10622
10703
  const preloaded = paywallPreloadService.getPreloaded(placement);
10623
10704
  return new Promise((resolve) => setActivePaywall({
10624
10705
  placement,
@@ -10636,7 +10717,7 @@ function PaywalloProvider({ children, config }) {
10636
10717
  }
10637
10718
  }));
10638
10719
  }, []);
10639
- const trackCampaignImpression = useCallback19((placement, variantKey, campaignId) => {
10720
+ const trackCampaignImpression = useCallback20((placement, variantKey, campaignId) => {
10640
10721
  const api = PaywalloClient.getApiClient();
10641
10722
  if (!api) return;
10642
10723
  const sessionId = PaywalloClient.getSessionId();
@@ -10650,7 +10731,7 @@ function PaywalloProvider({ children, config }) {
10650
10731
  }).catch(() => {
10651
10732
  });
10652
10733
  }, []);
10653
- const presentCampaign = useCallback19(
10734
+ const presentCampaign = useCallback20(
10654
10735
  async (placement, context) => {
10655
10736
  try {
10656
10737
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -10724,7 +10805,7 @@ function PaywalloProvider({ children, config }) {
10724
10805
  },
10725
10806
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
10726
10807
  );
10727
- const restorePurchases = useCallback19(async () => {
10808
+ const restorePurchases = useCallback20(async () => {
10728
10809
  invalidateCache();
10729
10810
  try {
10730
10811
  const txs = await getIAPService().restore();
@@ -10733,13 +10814,13 @@ function PaywalloProvider({ children, config }) {
10733
10814
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
10734
10815
  }
10735
10816
  }, [invalidateCache]);
10736
- const handlePaywallResult = useCallback19((result) => {
10817
+ const handlePaywallResult = useCallback20((result) => {
10737
10818
  if (activePaywall) {
10738
10819
  activePaywall.resolver(result);
10739
10820
  setActivePaywall(null);
10740
10821
  }
10741
10822
  }, [activePaywall]);
10742
- const bridgePaywallPresenter = useCallback19((placement) => {
10823
+ const bridgePaywallPresenter = useCallback20((placement) => {
10743
10824
  if (PaywalloClient.getConfig()?.debug) {
10744
10825
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
10745
10826
  }
@@ -10795,7 +10876,7 @@ function PaywalloProvider({ children, config }) {
10795
10876
  };
10796
10877
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
10797
10878
  const isReady = isInitialized && !isLoadingProducts;
10798
- const noOp = useCallback19(() => {
10879
+ const noOp = useCallback20(() => {
10799
10880
  }, []);
10800
10881
  const contextValue = useMemo3(() => ({
10801
10882
  config: PaywalloClient.getConfig(),
@@ -11050,6 +11131,7 @@ export {
11050
11131
  createPurchaseError,
11051
11132
  index_default as default,
11052
11133
  detectDeviceLanguage,
11134
+ getActiveRouteName,
11053
11135
  getCurrentLanguage,
11054
11136
  getDefaultLanguage,
11055
11137
  getIAPService,
@@ -11078,6 +11160,7 @@ export {
11078
11160
  useOnboarding,
11079
11161
  usePaywallContext,
11080
11162
  usePaywallo,
11163
+ usePaywalloScreenTracking,
11081
11164
  usePlanPurchase,
11082
11165
  usePlans,
11083
11166
  useProducts,