@virex-tech/paywallo-sdk 2.5.1 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -348,13 +348,13 @@ function getCachedDeviceInfo() {
348
348
  return null;
349
349
  }
350
350
  }
351
- var import_react_native2, Device, Application, Localization, _debug2, cachedDeviceInfo, nativeDeviceInfo;
351
+ var import_react_native2, Application, Device, Localization, _debug2, cachedDeviceInfo, nativeDeviceInfo;
352
352
  var init_NativeDeviceInfo = __esm({
353
353
  "src/utils/NativeDeviceInfo.ts"() {
354
354
  "use strict";
355
355
  import_react_native2 = require("react-native");
356
- Device = __toESM(require("expo-device"));
357
356
  Application = __toESM(require("expo-application"));
357
+ Device = __toESM(require("expo-device"));
358
358
  Localization = __toESM(require("expo-localization"));
359
359
  init_ClientError();
360
360
  _debug2 = false;
@@ -968,7 +968,9 @@ var init_AdvertisingIdManager = __esm({
968
968
  return this.cached;
969
969
  }
970
970
  async collect(requestATT = false) {
971
- if (this.cached) return this.cached;
971
+ if (this.cached && !(requestATT && this.cached.attStatus === "undetermined")) {
972
+ return this.cached;
973
+ }
972
974
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
973
975
  try {
974
976
  const tracking = await import("expo-tracking-transparency");
@@ -1391,6 +1393,7 @@ var init_AttributionTracker = __esm({
1391
1393
  constructor(debug = false) {
1392
1394
  this.cache = null;
1393
1395
  this.captureInProgress = false;
1396
+ this.listeners = /* @__PURE__ */ new Set();
1394
1397
  this.storage = new SecureStorage(debug);
1395
1398
  }
1396
1399
  /**
@@ -1430,10 +1433,31 @@ var init_AttributionTracker = __esm({
1430
1433
  };
1431
1434
  await this.storage.set(ATTRIBUTION_STORAGE_KEY, JSON.stringify(capture));
1432
1435
  this.cache = capture;
1436
+ this.notifyListeners(capture);
1433
1437
  } finally {
1434
1438
  this.captureInProgress = false;
1435
1439
  }
1436
1440
  }
1441
+ /**
1442
+ * Subscribes to the first real attribution write (first-write-wins — no-op
1443
+ * captures never fire). Useful for late enrichment consumers (e.g. re-push
1444
+ * of Superwall attributes when a warm deep link / deferred match lands after
1445
+ * init). Returns an unsubscribe function.
1446
+ */
1447
+ onCapture(listener) {
1448
+ this.listeners.add(listener);
1449
+ return () => {
1450
+ this.listeners.delete(listener);
1451
+ };
1452
+ }
1453
+ notifyListeners(capture) {
1454
+ for (const listener of this.listeners) {
1455
+ try {
1456
+ listener(capture);
1457
+ } catch {
1458
+ }
1459
+ }
1460
+ }
1437
1461
  /**
1438
1462
  * Returns the persisted attribution capture, or `null` if none exists.
1439
1463
  * Synchronous — reads from in-memory cache (populated via `loadFromStorage()`).
@@ -1527,6 +1551,68 @@ var init_InstallIdempotency = __esm({
1527
1551
  }
1528
1552
  });
1529
1553
 
1554
+ // src/domains/identity/deferredAppLink.ts
1555
+ function parseQuery(s) {
1556
+ const result = {};
1557
+ for (const pair of s.split("&")) {
1558
+ const eqIndex = pair.indexOf("=");
1559
+ if (eqIndex === -1) continue;
1560
+ try {
1561
+ const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
1562
+ const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
1563
+ if (key) result[key] = value;
1564
+ } catch {
1565
+ }
1566
+ }
1567
+ return result;
1568
+ }
1569
+ function parseDeferredAppLink(url) {
1570
+ if (!url || url.trim() === "") return null;
1571
+ const raw = url.slice(0, 2048);
1572
+ const cleanRaw = raw.split("#")[0];
1573
+ const qIndex = cleanRaw.indexOf("?");
1574
+ if (qIndex === -1) return null;
1575
+ const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
1576
+ let fbclid = nz(topParams["fbclid"]);
1577
+ let utmSource = nz(topParams["utm_source"]);
1578
+ let utmMedium = nz(topParams["utm_medium"]);
1579
+ let utmCampaign = nz(topParams["utm_campaign"]);
1580
+ let ttclid = nz(topParams["ttclid"]);
1581
+ let trackingId = nz(topParams["tracking_id"]);
1582
+ let targetUrl = null;
1583
+ if (topParams["target_url"]) {
1584
+ try {
1585
+ targetUrl = decodeURIComponent(topParams["target_url"]);
1586
+ } catch {
1587
+ targetUrl = topParams["target_url"];
1588
+ }
1589
+ }
1590
+ if (!fbclid && targetUrl) {
1591
+ const cleanTargetUrl = targetUrl.split("#")[0];
1592
+ const innerQ = cleanTargetUrl.indexOf("?");
1593
+ if (innerQ !== -1) {
1594
+ const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
1595
+ fbclid = fbclid ?? nz(inner["fbclid"]);
1596
+ utmSource = utmSource ?? nz(inner["utm_source"]);
1597
+ utmMedium = utmMedium ?? nz(inner["utm_medium"]);
1598
+ utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
1599
+ ttclid = ttclid ?? nz(inner["ttclid"]);
1600
+ trackingId = trackingId ?? nz(inner["tracking_id"]);
1601
+ }
1602
+ }
1603
+ if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
1604
+ return null;
1605
+ }
1606
+ return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
1607
+ }
1608
+ var nz;
1609
+ var init_deferredAppLink = __esm({
1610
+ "src/domains/identity/deferredAppLink.ts"() {
1611
+ "use strict";
1612
+ nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
1613
+ }
1614
+ });
1615
+
1530
1616
  // src/domains/identity/MetaBridge.ts
1531
1617
  var import_react_native5, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
1532
1618
  var init_MetaBridge = __esm({
@@ -1625,68 +1711,6 @@ var init_MetaBridge = __esm({
1625
1711
  }
1626
1712
  });
1627
1713
 
1628
- // src/domains/identity/deferredAppLink.ts
1629
- function parseQuery(s) {
1630
- const result = {};
1631
- for (const pair of s.split("&")) {
1632
- const eqIndex = pair.indexOf("=");
1633
- if (eqIndex === -1) continue;
1634
- try {
1635
- const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
1636
- const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
1637
- if (key) result[key] = value;
1638
- } catch {
1639
- }
1640
- }
1641
- return result;
1642
- }
1643
- function parseDeferredAppLink(url) {
1644
- if (!url || url.trim() === "") return null;
1645
- const raw = url.slice(0, 2048);
1646
- const cleanRaw = raw.split("#")[0];
1647
- const qIndex = cleanRaw.indexOf("?");
1648
- if (qIndex === -1) return null;
1649
- const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
1650
- let fbclid = nz(topParams["fbclid"]);
1651
- let utmSource = nz(topParams["utm_source"]);
1652
- let utmMedium = nz(topParams["utm_medium"]);
1653
- let utmCampaign = nz(topParams["utm_campaign"]);
1654
- let ttclid = nz(topParams["ttclid"]);
1655
- let trackingId = nz(topParams["tracking_id"]);
1656
- let targetUrl = null;
1657
- if (topParams["target_url"]) {
1658
- try {
1659
- targetUrl = decodeURIComponent(topParams["target_url"]);
1660
- } catch {
1661
- targetUrl = topParams["target_url"];
1662
- }
1663
- }
1664
- if (!fbclid && targetUrl) {
1665
- const cleanTargetUrl = targetUrl.split("#")[0];
1666
- const innerQ = cleanTargetUrl.indexOf("?");
1667
- if (innerQ !== -1) {
1668
- const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
1669
- fbclid = fbclid ?? nz(inner["fbclid"]);
1670
- utmSource = utmSource ?? nz(inner["utm_source"]);
1671
- utmMedium = utmMedium ?? nz(inner["utm_medium"]);
1672
- utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
1673
- ttclid = ttclid ?? nz(inner["ttclid"]);
1674
- trackingId = trackingId ?? nz(inner["tracking_id"]);
1675
- }
1676
- }
1677
- if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
1678
- return null;
1679
- }
1680
- return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
1681
- }
1682
- var nz;
1683
- var init_deferredAppLink = __esm({
1684
- "src/domains/identity/deferredAppLink.ts"() {
1685
- "use strict";
1686
- nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
1687
- }
1688
- });
1689
-
1690
1714
  // src/domains/identity/InstallReferrerManager.ts
1691
1715
  var import_react_native6, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
1692
1716
  var init_InstallReferrerManager = __esm({
@@ -1694,8 +1718,8 @@ var init_InstallReferrerManager = __esm({
1694
1718
  "use strict";
1695
1719
  import_react_native6 = require("react-native");
1696
1720
  init_NativeDeviceInfo();
1697
- init_MetaBridge();
1698
1721
  init_deferredAppLink();
1722
+ init_MetaBridge();
1699
1723
  REFERRER_TIMEOUT_MS = 5e3;
1700
1724
  InstallReferrerManager = class {
1701
1725
  async getReferrer() {
@@ -1795,7 +1819,17 @@ var init_InstallReferrerManager = __esm({
1795
1819
  });
1796
1820
 
1797
1821
  // src/domains/identity/InstallTracker.ts
1798
- var import_react_native7, InstallTracker;
1822
+ function extractDeferredAttribution(raw) {
1823
+ if (raw === null || typeof raw !== "object") return null;
1824
+ const source = raw;
1825
+ const out = {};
1826
+ for (const field of DEFERRED_ATTRIBUTION_FIELDS) {
1827
+ const value = source[field];
1828
+ if (typeof value === "string" && value !== "") out[field] = value;
1829
+ }
1830
+ return Object.keys(out).length > 0 ? out : null;
1831
+ }
1832
+ var import_react_native7, InstallTracker, DEFERRED_ATTRIBUTION_FIELDS;
1799
1833
  var init_InstallTracker = __esm({
1800
1834
  "src/domains/identity/InstallTracker.ts"() {
1801
1835
  "use strict";
@@ -1961,7 +1995,7 @@ var init_InstallTracker = __esm({
1961
1995
  const baseUrl = apiClient.getBaseUrl();
1962
1996
  const appKey = apiClient.getAppKey();
1963
1997
  const referrer = await installReferrerManager.getReferrer();
1964
- await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
1998
+ const response = await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
1965
1999
  method: "POST",
1966
2000
  headers: { "Content-Type": "application/json", "X-App-Key": appKey },
1967
2001
  body: JSON.stringify({
@@ -1983,12 +2017,42 @@ var init_InstallTracker = __esm({
1983
2017
  });
1984
2018
  await storage.set(INSTALL_KEYS.DEFERRED_MATCH_DONE, "1").catch(() => {
1985
2019
  });
2020
+ await this.applyDeferredMatchResponse(response);
1986
2021
  } catch {
1987
2022
  } finally {
1988
2023
  clearTimeout(timer);
1989
2024
  }
1990
2025
  }
2026
+ async applyDeferredMatchResponse(response) {
2027
+ try {
2028
+ const body = await response.json();
2029
+ if (body === null || typeof body !== "object") return;
2030
+ const outer = body;
2031
+ const source = typeof outer.data === "object" && outer.data !== null ? outer.data : body;
2032
+ if (source.matched !== true) return;
2033
+ const attribution = extractDeferredAttribution(source.attribution);
2034
+ if (!attribution) return;
2035
+ await attributionTracker.capture({
2036
+ ...attribution,
2037
+ installReferrerSource: "deferred_match"
2038
+ });
2039
+ } catch {
2040
+ }
2041
+ }
1991
2042
  };
2043
+ DEFERRED_ATTRIBUTION_FIELDS = [
2044
+ "fbclid",
2045
+ "gclid",
2046
+ "ttclid",
2047
+ "utmSource",
2048
+ "utmMedium",
2049
+ "utmCampaign",
2050
+ "utmContent",
2051
+ "utmTerm",
2052
+ "tiktokCampaignId",
2053
+ "tiktokAdgroupId",
2054
+ "tiktokAdId"
2055
+ ];
1992
2056
  }
1993
2057
  });
1994
2058
 
@@ -2563,8 +2627,8 @@ var init_NotificationHandlerSetup = __esm({
2563
2627
  // src/core/version.ts
2564
2628
  function resolveVersion() {
2565
2629
  try {
2566
- if ("2.5.1") {
2567
- return "2.5.1";
2630
+ if ("2.5.2") {
2631
+ return "2.5.2";
2568
2632
  }
2569
2633
  } catch {
2570
2634
  }
@@ -3177,9 +3241,146 @@ var init_NotificationsManager = __esm({
3177
3241
  var init_notifications = __esm({
3178
3242
  "src/domains/notifications/index.ts"() {
3179
3243
  "use strict";
3180
- init_NativePushBridge();
3181
- init_NotificationsError();
3182
- init_NotificationsManager();
3244
+ init_NativePushBridge();
3245
+ init_NotificationsError();
3246
+ init_NotificationsManager();
3247
+ }
3248
+ });
3249
+
3250
+ // src/domains/onboarding/OnboardingError.ts
3251
+ var OnboardingError, ONBOARDING_ERROR_CODES;
3252
+ var init_OnboardingError = __esm({
3253
+ "src/domains/onboarding/OnboardingError.ts"() {
3254
+ "use strict";
3255
+ init_PaywalloError();
3256
+ OnboardingError = class extends PaywalloError {
3257
+ constructor(code, message) {
3258
+ super("onboarding", code, message);
3259
+ this.name = "OnboardingError";
3260
+ }
3261
+ };
3262
+ ONBOARDING_ERROR_CODES = {
3263
+ NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
3264
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
3265
+ };
3266
+ }
3267
+ });
3268
+
3269
+ // src/domains/onboarding/OnboardingManager.ts
3270
+ var OnboardingManager, onboardingManager;
3271
+ var init_OnboardingManager = __esm({
3272
+ "src/domains/onboarding/OnboardingManager.ts"() {
3273
+ "use strict";
3274
+ init_OnboardingError();
3275
+ OnboardingManager = class {
3276
+ constructor() {
3277
+ this.pipeline = null;
3278
+ this.debug = false;
3279
+ this.lastStep = null;
3280
+ this.finished = false;
3281
+ }
3282
+ /**
3283
+ * Injects runtime dependencies. Called by PaywalloClient during init.
3284
+ */
3285
+ injectDeps(config) {
3286
+ this.pipeline = config.pipeline;
3287
+ this.debug = config.debug ?? false;
3288
+ }
3289
+ /**
3290
+ * Tracks an onboarding step view.
3291
+ * Saves the step name internally for implicit drop detection on the backend.
3292
+ *
3293
+ * @param stepName - Unique name for this onboarding step.
3294
+ * @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
3295
+ */
3296
+ async step(stepName, order) {
3297
+ this.assertConfig();
3298
+ this.validateStepName(stepName, "step");
3299
+ this.lastStep = stepName;
3300
+ const stepPayload = { step_name: stepName };
3301
+ if (order !== void 0 && Number.isFinite(order) && order >= 0) {
3302
+ stepPayload.order = order;
3303
+ }
3304
+ const payload = {
3305
+ family: "onboarding",
3306
+ type: "step",
3307
+ ...stepPayload
3308
+ };
3309
+ return this.emit("onboarding", payload, "step");
3310
+ }
3311
+ /**
3312
+ * Tracks successful completion of the onboarding flow.
3313
+ * Marks the flow as finished.
3314
+ */
3315
+ async complete() {
3316
+ this.assertConfig();
3317
+ this.finished = true;
3318
+ return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
3319
+ }
3320
+ /**
3321
+ * Tracks an explicit drop (abandonment) at the given step.
3322
+ * Marks the flow as finished.
3323
+ *
3324
+ * @param stepName - The step name where the user dropped off.
3325
+ */
3326
+ async drop(stepName) {
3327
+ this.assertConfig();
3328
+ this.validateStepName(stepName, "drop");
3329
+ this.finished = true;
3330
+ const payload = {
3331
+ family: "onboarding",
3332
+ type: "drop",
3333
+ ...{ step_name: stepName }
3334
+ };
3335
+ return this.emit("onboarding", payload, "drop");
3336
+ }
3337
+ /** Returns the last step name seen, or null if no step was tracked yet. */
3338
+ getLastStep() {
3339
+ return this.lastStep;
3340
+ }
3341
+ /** Returns whether the flow has been marked as finished (complete or drop). */
3342
+ isFinished() {
3343
+ return this.finished;
3344
+ }
3345
+ async emit(eventName, properties, logLabel) {
3346
+ const pipeline = this.pipeline;
3347
+ const distinctId = pipeline.getDistinctId();
3348
+ if (!distinctId) {
3349
+ if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
3350
+ return;
3351
+ }
3352
+ await pipeline.trackEvent(eventName, distinctId, properties);
3353
+ if (this.debug) {
3354
+ console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
3355
+ }
3356
+ }
3357
+ assertConfig() {
3358
+ if (!this.pipeline) {
3359
+ throw new OnboardingError(
3360
+ ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
3361
+ "OnboardingManager not initialized. Call Paywallo.init() first."
3362
+ );
3363
+ }
3364
+ }
3365
+ validateStepName(stepName, method) {
3366
+ if (typeof stepName !== "string" || stepName.trim().length === 0) {
3367
+ throw new OnboardingError(
3368
+ ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
3369
+ `OnboardingManager.${method}(): stepName must be a non-empty string.`
3370
+ );
3371
+ }
3372
+ }
3373
+ };
3374
+ onboardingManager = new OnboardingManager();
3375
+ }
3376
+ });
3377
+
3378
+ // src/domains/onboarding/index.ts
3379
+ var init_onboarding = __esm({
3380
+ "src/domains/onboarding/index.ts"() {
3381
+ "use strict";
3382
+ init_OnboardingError();
3383
+ init_OnboardingManager();
3183
3384
  }
3184
3385
  });
3185
3386
 
@@ -6413,143 +6614,6 @@ var init_session = __esm({
6413
6614
  }
6414
6615
  });
6415
6616
 
6416
- // src/domains/onboarding/OnboardingError.ts
6417
- var OnboardingError, ONBOARDING_ERROR_CODES;
6418
- var init_OnboardingError = __esm({
6419
- "src/domains/onboarding/OnboardingError.ts"() {
6420
- "use strict";
6421
- init_PaywalloError();
6422
- OnboardingError = class extends PaywalloError {
6423
- constructor(code, message) {
6424
- super("onboarding", code, message);
6425
- this.name = "OnboardingError";
6426
- }
6427
- };
6428
- ONBOARDING_ERROR_CODES = {
6429
- NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
6430
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
6431
- };
6432
- }
6433
- });
6434
-
6435
- // src/domains/onboarding/OnboardingManager.ts
6436
- var OnboardingManager, onboardingManager;
6437
- var init_OnboardingManager = __esm({
6438
- "src/domains/onboarding/OnboardingManager.ts"() {
6439
- "use strict";
6440
- init_OnboardingError();
6441
- OnboardingManager = class {
6442
- constructor() {
6443
- this.pipeline = null;
6444
- this.debug = false;
6445
- this.lastStep = null;
6446
- this.finished = false;
6447
- }
6448
- /**
6449
- * Injects runtime dependencies. Called by PaywalloClient during init.
6450
- */
6451
- injectDeps(config) {
6452
- this.pipeline = config.pipeline;
6453
- this.debug = config.debug ?? false;
6454
- }
6455
- /**
6456
- * Tracks an onboarding step view.
6457
- * Saves the step name internally for implicit drop detection on the backend.
6458
- *
6459
- * @param stepName - Unique name for this onboarding step.
6460
- * @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
6461
- */
6462
- async step(stepName, order) {
6463
- this.assertConfig();
6464
- this.validateStepName(stepName, "step");
6465
- this.lastStep = stepName;
6466
- const stepPayload = { step_name: stepName };
6467
- if (order !== void 0 && Number.isFinite(order) && order >= 0) {
6468
- stepPayload.order = order;
6469
- }
6470
- const payload = {
6471
- family: "onboarding",
6472
- type: "step",
6473
- ...stepPayload
6474
- };
6475
- return this.emit("onboarding", payload, "step");
6476
- }
6477
- /**
6478
- * Tracks successful completion of the onboarding flow.
6479
- * Marks the flow as finished.
6480
- */
6481
- async complete() {
6482
- this.assertConfig();
6483
- this.finished = true;
6484
- return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
6485
- }
6486
- /**
6487
- * Tracks an explicit drop (abandonment) at the given step.
6488
- * Marks the flow as finished.
6489
- *
6490
- * @param stepName - The step name where the user dropped off.
6491
- */
6492
- async drop(stepName) {
6493
- this.assertConfig();
6494
- this.validateStepName(stepName, "drop");
6495
- this.finished = true;
6496
- const payload = {
6497
- family: "onboarding",
6498
- type: "drop",
6499
- ...{ step_name: stepName }
6500
- };
6501
- return this.emit("onboarding", payload, "drop");
6502
- }
6503
- /** Returns the last step name seen, or null if no step was tracked yet. */
6504
- getLastStep() {
6505
- return this.lastStep;
6506
- }
6507
- /** Returns whether the flow has been marked as finished (complete or drop). */
6508
- isFinished() {
6509
- return this.finished;
6510
- }
6511
- async emit(eventName, properties, logLabel) {
6512
- const pipeline = this.pipeline;
6513
- const distinctId = pipeline.getDistinctId();
6514
- if (!distinctId) {
6515
- if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
6516
- return;
6517
- }
6518
- await pipeline.trackEvent(eventName, distinctId, properties);
6519
- if (this.debug) {
6520
- console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
6521
- }
6522
- }
6523
- assertConfig() {
6524
- if (!this.pipeline) {
6525
- throw new OnboardingError(
6526
- ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
6527
- "OnboardingManager not initialized. Call Paywallo.init() first."
6528
- );
6529
- }
6530
- }
6531
- validateStepName(stepName, method) {
6532
- if (typeof stepName !== "string" || stepName.trim().length === 0) {
6533
- throw new OnboardingError(
6534
- ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
6535
- `OnboardingManager.${method}(): stepName must be a non-empty string.`
6536
- );
6537
- }
6538
- }
6539
- };
6540
- onboardingManager = new OnboardingManager();
6541
- }
6542
- });
6543
-
6544
- // src/domains/onboarding/index.ts
6545
- var init_onboarding = __esm({
6546
- "src/domains/onboarding/index.ts"() {
6547
- "use strict";
6548
- init_OnboardingError();
6549
- init_OnboardingManager();
6550
- }
6551
- });
6552
-
6553
6617
  // src/domains/analytics/AnalyticsError.ts
6554
6618
  var AnalyticsError, ANALYTICS_ERROR_CODES;
6555
6619
  var init_AnalyticsError = __esm({
@@ -6622,7 +6686,21 @@ var init_schemas = __esm({
6622
6686
  // `closed`-specific
6623
6687
  closed_at: import_zod.z.string().optional(),
6624
6688
  duration_s: import_zod.z.number().nonnegative().optional(),
6625
- close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6689
+ // Two emitter dialects coexist: the canonical emitters
6690
+ // (`emitPaywallClosed`/heartbeat) send "dismiss"/"cta"/"purchase"/"error"/
6691
+ // "timeout"; the SuperwallAutoBridge forwards the server-enum values
6692
+ // "purchased"/"dismissed"/"restored" straight through. The server accepts
6693
+ // both — the schema must too, or every bridge close fails debug validation.
6694
+ close_reason: import_zod.z.enum([
6695
+ "dismiss",
6696
+ "cta",
6697
+ "purchase",
6698
+ "error",
6699
+ "timeout",
6700
+ "purchased",
6701
+ "dismissed",
6702
+ "restored"
6703
+ ]).optional(),
6626
6704
  scroll_depth: import_zod.z.number().min(0).max(1).optional(),
6627
6705
  // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6628
6706
  product_id: import_zod.z.string().optional()
@@ -6630,21 +6708,33 @@ var init_schemas = __esm({
6630
6708
  transactionEventSchema = import_zod.z.object({
6631
6709
  type: import_zod.z.enum([
6632
6710
  "completed",
6711
+ "trial_started",
6633
6712
  "failed",
6634
6713
  "refunded",
6635
6714
  "renewed",
6636
6715
  "canceled",
6637
6716
  "expired"
6638
6717
  ]),
6639
- tx_id: import_zod.z.string().min(1),
6718
+ // Server contract key is `transaction_id` (what IngestTranslator reads and
6719
+ // the SuperwallAutoBridge emits); `tx_id` is the legacy key still emitted
6720
+ // by IAPTransactionEmitter — the server accepts both. At least one is
6721
+ // required (enforced by the refine below).
6722
+ transaction_id: import_zod.z.string().min(1).optional(),
6723
+ tx_id: import_zod.z.string().min(1).optional(),
6640
6724
  amount: import_zod.z.number().optional(),
6725
+ // Full (post-trial) price carried on trial_started for analytics, since
6726
+ // `amount` is 0 for a free trial.
6727
+ full_price: import_zod.z.number().nonnegative().optional(),
6641
6728
  currency: import_zod.z.string().length(3).optional(),
6642
6729
  product_id: import_zod.z.string().optional(),
6643
6730
  subscription_id: import_zod.z.string().optional(),
6644
6731
  paywall_id: import_zod.z.string().optional(),
6645
6732
  variant_id: import_zod.z.string().optional(),
6646
6733
  reason: import_zod.z.string().optional()
6647
- }).strict();
6734
+ }).strict().refine((v) => v.transaction_id !== void 0 || v.tx_id !== void 0, {
6735
+ message: "transaction requires transaction_id (or legacy tx_id)",
6736
+ path: ["transaction_id"]
6737
+ });
6648
6738
  onboardingEventSchema = import_zod.z.object({
6649
6739
  type: import_zod.z.enum(["step", "complete", "drop"]),
6650
6740
  step_index: import_zod.z.number().int().nonnegative().optional(),
@@ -7474,8 +7564,13 @@ var init_ApiClientFlags = __esm({
7474
7564
  });
7475
7565
 
7476
7566
  // src/core/ApiClientQueue.ts
7567
+ function isServerError(res) {
7568
+ if (typeof res !== "object" || res === null) return false;
7569
+ const r = res;
7570
+ return r.ok === false && typeof r.status === "number" && r.status >= 500;
7571
+ }
7477
7572
  async function postWithQueue(deps, url, payload, label, priority) {
7478
- if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
7573
+ const enqueue = async () => {
7479
7574
  await offlineQueue.enqueue(
7480
7575
  "POST",
7481
7576
  url,
@@ -7484,22 +7579,22 @@ async function postWithQueue(deps, url, payload, label, priority) {
7484
7579
  void 0,
7485
7580
  priority
7486
7581
  );
7582
+ };
7583
+ if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
7584
+ await enqueue();
7487
7585
  if (deps.isDebug()) console.log("[Paywallo:ApiClient] Queued for offline:", label, priority ?? "normal");
7488
7586
  return;
7489
7587
  }
7490
7588
  try {
7491
- await deps.post(url, payload);
7589
+ const res = await deps.post(url, payload);
7590
+ if (isServerError(res) && deps.isOfflineQueueEnabled()) {
7591
+ if (deps.isDebug()) console.log("[Paywallo:ApiClient] Server error, queued for retry:", label, priority ?? "normal");
7592
+ await enqueue();
7593
+ }
7492
7594
  } catch (error) {
7493
7595
  if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
7494
7596
  if (deps.isOfflineQueueEnabled()) {
7495
- await offlineQueue.enqueue(
7496
- "POST",
7497
- url,
7498
- payload,
7499
- { "X-App-Key": deps.getAppKey() },
7500
- void 0,
7501
- priority
7502
- );
7597
+ await enqueue();
7503
7598
  return;
7504
7599
  }
7505
7600
  throw error;
@@ -8905,11 +9000,11 @@ var init_PaywalloClient = __esm({
8905
9000
  init_campaign();
8906
9001
  init_identity();
8907
9002
  init_notifications();
9003
+ init_onboarding();
8908
9004
  init_paywall();
8909
9005
  init_plan();
8910
9006
  init_session();
8911
9007
  init_ClientError();
8912
- init_onboarding();
8913
9008
  init_network();
8914
9009
  init_PaywalloAnalytics();
8915
9010
  init_PaywalloFlags();
@@ -9616,8 +9711,41 @@ function usePlanPurchase() {
9616
9711
  };
9617
9712
  }
9618
9713
 
9619
- // src/hooks/usePlans.ts
9714
+ // src/hooks/usePaywalloScreenTracking.ts
9620
9715
  var import_react18 = require("react");
9716
+ init_PaywalloClient();
9717
+ function isNavState(value) {
9718
+ return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
9719
+ }
9720
+ function getActiveRouteName(state) {
9721
+ try {
9722
+ if (!isNavState(state)) return null;
9723
+ const route = state.routes[state.index];
9724
+ if (!route) return null;
9725
+ if (isNavState(route.state)) {
9726
+ return getActiveRouteName(route.state);
9727
+ }
9728
+ return typeof route.name === "string" ? route.name : null;
9729
+ } catch {
9730
+ return null;
9731
+ }
9732
+ }
9733
+ function usePaywalloScreenTracking() {
9734
+ const lastScreenRef = (0, import_react18.useRef)(null);
9735
+ return (0, import_react18.useCallback)((state) => {
9736
+ if (!PaywalloClient.isReady()) return;
9737
+ const screenName = getActiveRouteName(state);
9738
+ if (!screenName || screenName === lastScreenRef.current) return;
9739
+ lastScreenRef.current = screenName;
9740
+ void PaywalloClient.track("screen_view", {
9741
+ properties: { screen_name: screenName }
9742
+ }).catch(() => {
9743
+ });
9744
+ }, []);
9745
+ }
9746
+
9747
+ // src/hooks/usePlans.ts
9748
+ var import_react19 = require("react");
9621
9749
  async function getPlanService() {
9622
9750
  try {
9623
9751
  const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
@@ -9627,11 +9755,11 @@ async function getPlanService() {
9627
9755
  }
9628
9756
  }
9629
9757
  function usePlans() {
9630
- const [allPlans, setAllPlans] = (0, import_react18.useState)([]);
9631
- const [currentPlan, setCurrentPlan] = (0, import_react18.useState)(null);
9632
- const [isLoading, setIsLoading] = (0, import_react18.useState)(true);
9633
- const [error, setError] = (0, import_react18.useState)(null);
9634
- const fetchPlans = (0, import_react18.useCallback)(async (forceRefresh = false) => {
9758
+ const [allPlans, setAllPlans] = (0, import_react19.useState)([]);
9759
+ const [currentPlan, setCurrentPlan] = (0, import_react19.useState)(null);
9760
+ const [isLoading, setIsLoading] = (0, import_react19.useState)(true);
9761
+ const [error, setError] = (0, import_react19.useState)(null);
9762
+ const fetchPlans = (0, import_react19.useCallback)(async (forceRefresh = false) => {
9635
9763
  setIsLoading(true);
9636
9764
  setError(null);
9637
9765
  try {
@@ -9652,17 +9780,17 @@ function usePlans() {
9652
9780
  setIsLoading(false);
9653
9781
  }
9654
9782
  }, []);
9655
- const refresh = (0, import_react18.useCallback)(() => {
9783
+ const refresh = (0, import_react19.useCallback)(() => {
9656
9784
  return fetchPlans(true);
9657
9785
  }, [fetchPlans]);
9658
- (0, import_react18.useEffect)(() => {
9786
+ (0, import_react19.useEffect)(() => {
9659
9787
  void fetchPlans(false);
9660
9788
  }, [fetchPlans]);
9661
9789
  return { currentPlan, allPlans, isLoading, error, refresh };
9662
9790
  }
9663
9791
 
9664
9792
  // src/hooks/useProducts.ts
9665
- var import_react19 = require("react");
9793
+ var import_react20 = require("react");
9666
9794
 
9667
9795
  // src/utils/productMappers.ts
9668
9796
  function mapToIAPProduct(product) {
@@ -9701,11 +9829,11 @@ function mapToFormattedProduct(product) {
9701
9829
 
9702
9830
  // src/hooks/useProducts.ts
9703
9831
  function useProducts(initialProductIds) {
9704
- const [products, setProducts] = (0, import_react19.useState)([]);
9705
- const [formattedProducts, setFormattedProducts] = (0, import_react19.useState)([]);
9706
- const [isLoading, setIsLoading] = (0, import_react19.useState)(false);
9707
- const [error, setError] = (0, import_react19.useState)(null);
9708
- const loadProducts = (0, import_react19.useCallback)(async (productIds) => {
9832
+ const [products, setProducts] = (0, import_react20.useState)([]);
9833
+ const [formattedProducts, setFormattedProducts] = (0, import_react20.useState)([]);
9834
+ const [isLoading, setIsLoading] = (0, import_react20.useState)(false);
9835
+ const [error, setError] = (0, import_react20.useState)(null);
9836
+ const loadProducts = (0, import_react20.useCallback)(async (productIds) => {
9709
9837
  setIsLoading(true);
9710
9838
  setError(null);
9711
9839
  try {
@@ -9719,24 +9847,24 @@ function useProducts(initialProductIds) {
9719
9847
  setIsLoading(false);
9720
9848
  }
9721
9849
  }, []);
9722
- (0, import_react19.useEffect)(() => {
9850
+ (0, import_react20.useEffect)(() => {
9723
9851
  if (initialProductIds && initialProductIds.length > 0) {
9724
9852
  void loadProducts(initialProductIds);
9725
9853
  }
9726
9854
  }, []);
9727
- const getProduct = (0, import_react19.useCallback)(
9855
+ const getProduct = (0, import_react20.useCallback)(
9728
9856
  (productId) => {
9729
9857
  return products.find((p) => p.productId === productId);
9730
9858
  },
9731
9859
  [products]
9732
9860
  );
9733
- const getFormattedProduct = (0, import_react19.useCallback)(
9861
+ const getFormattedProduct = (0, import_react20.useCallback)(
9734
9862
  (productId) => {
9735
9863
  return formattedProducts.find((p) => p.productId === productId);
9736
9864
  },
9737
9865
  [formattedProducts]
9738
9866
  );
9739
- const getPriceInfo = (0, import_react19.useCallback)(
9867
+ const getPriceInfo = (0, import_react20.useCallback)(
9740
9868
  (productId) => {
9741
9869
  const p = formattedProducts.find((fp) => fp.productId === productId);
9742
9870
  if (!p) return void 0;
@@ -9765,14 +9893,14 @@ function useProducts(initialProductIds) {
9765
9893
  }
9766
9894
 
9767
9895
  // src/hooks/usePurchase.ts
9768
- var import_react20 = require("react");
9896
+ var import_react21 = require("react");
9769
9897
  function toIAPPurchase(p) {
9770
9898
  return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
9771
9899
  }
9772
9900
  function usePurchase() {
9773
- const [state, setState] = (0, import_react20.useState)("idle");
9774
- const [error, setError] = (0, import_react20.useState)(null);
9775
- const purchase = (0, import_react20.useCallback)(async (productId) => {
9901
+ const [state, setState] = (0, import_react21.useState)("idle");
9902
+ const [error, setError] = (0, import_react21.useState)(null);
9903
+ const purchase = (0, import_react21.useCallback)(async (productId) => {
9776
9904
  setState("purchasing");
9777
9905
  setError(null);
9778
9906
  try {
@@ -9800,7 +9928,7 @@ function usePurchase() {
9800
9928
  return { status: "failed", error: purchaseError };
9801
9929
  }
9802
9930
  }, []);
9803
- const restore = (0, import_react20.useCallback)(async () => {
9931
+ const restore = (0, import_react21.useCallback)(async () => {
9804
9932
  setState("restoring");
9805
9933
  setError(null);
9806
9934
  try {
@@ -9819,14 +9947,14 @@ function usePurchase() {
9819
9947
  }
9820
9948
 
9821
9949
  // src/hooks/useSubscription.ts
9822
- var import_react21 = require("react");
9950
+ var import_react22 = require("react");
9823
9951
  init_SubscriptionManager();
9824
9952
  var EMPTY_ENTITLEMENTS = [];
9825
9953
  function useSubscription() {
9826
- const [status, setStatus] = (0, import_react21.useState)(null);
9827
- const [isLoading, setIsLoading] = (0, import_react21.useState)(true);
9828
- const [error, setError] = (0, import_react21.useState)(null);
9829
- const refresh = (0, import_react21.useCallback)(async () => {
9954
+ const [status, setStatus] = (0, import_react22.useState)(null);
9955
+ const [isLoading, setIsLoading] = (0, import_react22.useState)(true);
9956
+ const [error, setError] = (0, import_react22.useState)(null);
9957
+ const refresh = (0, import_react22.useCallback)(async () => {
9830
9958
  setIsLoading(true);
9831
9959
  setError(null);
9832
9960
  try {
@@ -9839,7 +9967,7 @@ function useSubscription() {
9839
9967
  setIsLoading(false);
9840
9968
  }
9841
9969
  }, []);
9842
- const restore = (0, import_react21.useCallback)(async () => {
9970
+ const restore = (0, import_react22.useCallback)(async () => {
9843
9971
  setIsLoading(true);
9844
9972
  setError(null);
9845
9973
  try {
@@ -9852,7 +9980,7 @@ function useSubscription() {
9852
9980
  setIsLoading(false);
9853
9981
  }
9854
9982
  }, []);
9855
- (0, import_react21.useEffect)(() => {
9983
+ (0, import_react22.useEffect)(() => {
9856
9984
  void subscriptionManager.getSubscriptionStatus().then((s) => {
9857
9985
  setStatus(s);
9858
9986
  setIsLoading(false);
@@ -9879,39 +10007,6 @@ function useSubscription() {
9879
10007
  };
9880
10008
  }
9881
10009
 
9882
- // src/hooks/usePaywalloScreenTracking.ts
9883
- var import_react22 = require("react");
9884
- init_PaywalloClient();
9885
- function isNavState(value) {
9886
- return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
9887
- }
9888
- function getActiveRouteName(state) {
9889
- try {
9890
- if (!isNavState(state)) return null;
9891
- const route = state.routes[state.index];
9892
- if (!route) return null;
9893
- if (isNavState(route.state)) {
9894
- return getActiveRouteName(route.state);
9895
- }
9896
- return typeof route.name === "string" ? route.name : null;
9897
- } catch {
9898
- return null;
9899
- }
9900
- }
9901
- function usePaywalloScreenTracking() {
9902
- const lastScreenRef = (0, import_react22.useRef)(null);
9903
- return (0, import_react22.useCallback)((state) => {
9904
- if (!PaywalloClient.isReady()) return;
9905
- const screenName = getActiveRouteName(state);
9906
- if (!screenName || screenName === lastScreenRef.current) return;
9907
- lastScreenRef.current = screenName;
9908
- void PaywalloClient.track("screen_view", {
9909
- properties: { screen_name: screenName }
9910
- }).catch(() => {
9911
- });
9912
- }, []);
9913
- }
9914
-
9915
10010
  // src/PaywalloProvider.tsx
9916
10011
  var import_react28 = require("react");
9917
10012
  var import_react_native29 = require("react-native");
@@ -9919,6 +10014,7 @@ init_PaywalloClient();
9919
10014
  init_NativeStorage();
9920
10015
  init_SecureStorage();
9921
10016
  init_campaign();
10017
+ init_identity();
9922
10018
  init_paywall();
9923
10019
  init_paywall();
9924
10020
  init_paywall();
@@ -9930,7 +10026,9 @@ init_paywall();
9930
10026
  init_identity();
9931
10027
  var CONFIG_POLL_INTERVAL_MS = 250;
9932
10028
  var CONFIG_POLL_MAX_ATTEMPTS = 40;
10029
+ var CONFIG_RETRY_DELAYS_MS = [3e4, 6e4, 12e4];
9933
10030
  var lastSignature = null;
10031
+ var retryTimer = null;
9934
10032
  function log(debug, msg, data) {
9935
10033
  if (!debug) return;
9936
10034
  if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
@@ -9997,28 +10095,37 @@ async function waitForConfigured(mod, debug) {
9997
10095
  status = String(await mod.getConfigurationStatus());
9998
10096
  } catch (err) {
9999
10097
  log(debug, "getConfigurationStatus error", { err: String(err) });
10000
- return false;
10098
+ return "failed";
10001
10099
  }
10002
- if (status === "CONFIGURED") return true;
10100
+ if (status === "CONFIGURED") return "configured";
10003
10101
  if (status === "FAILED") {
10004
10102
  log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
10005
- return false;
10103
+ return "failed";
10006
10104
  }
10007
10105
  await delay(CONFIG_POLL_INTERVAL_MS);
10008
10106
  }
10009
10107
  log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
10010
- return false;
10108
+ return "timeout";
10011
10109
  }
10012
10110
  async function pushAttributionToSuperwall(debug = false) {
10111
+ clearRetryTimer();
10112
+ await attemptPush(debug, 0);
10113
+ }
10114
+ async function attemptPush(debug, retryIndex) {
10115
+ const native = await resolveSuperwallNativeModule(debug);
10116
+ if (!native) return;
10117
+ const waited = await waitForConfigured(native, debug);
10118
+ if (waited === "timeout") {
10119
+ scheduleRetry(debug, retryIndex);
10120
+ return;
10121
+ }
10122
+ if (waited !== "configured") return;
10013
10123
  const attrs = buildSuperwallAttributes(attributionTracker.get());
10014
10124
  const signature = JSON.stringify(attrs);
10015
10125
  if (signature === lastSignature) {
10016
10126
  log(debug, "attributes unchanged \u2014 skipping push");
10017
10127
  return;
10018
10128
  }
10019
- const native = await resolveSuperwallNativeModule(debug);
10020
- if (!native) return;
10021
- if (!await waitForConfigured(native, debug)) return;
10022
10129
  try {
10023
10130
  await native.setUserAttributes(attrs);
10024
10131
  lastSignature = signature;
@@ -10027,6 +10134,28 @@ async function pushAttributionToSuperwall(debug = false) {
10027
10134
  log(debug, "setUserAttributes error", { err: String(err) });
10028
10135
  }
10029
10136
  }
10137
+ function scheduleRetry(debug, retryIndex) {
10138
+ const delayMs = CONFIG_RETRY_DELAYS_MS[retryIndex];
10139
+ if (delayMs === void 0) {
10140
+ log(debug, "Superwall never configured \u2014 giving up attribute push");
10141
+ return;
10142
+ }
10143
+ clearRetryTimer();
10144
+ log(debug, "scheduling attribute push retry", { retryIndex, delayMs });
10145
+ retryTimer = setTimeout(() => {
10146
+ retryTimer = null;
10147
+ void attemptPush(debug, retryIndex + 1);
10148
+ }, delayMs);
10149
+ }
10150
+ function clearRetryTimer() {
10151
+ if (retryTimer !== null) {
10152
+ clearTimeout(retryTimer);
10153
+ retryTimer = null;
10154
+ }
10155
+ }
10156
+ function cancelSuperwallAttributeRetry() {
10157
+ clearRetryTimer();
10158
+ }
10030
10159
 
10031
10160
  // src/domains/paywall/SuperwallAutoBridge.ts
10032
10161
  init_PaywalloClient();
@@ -10059,6 +10188,7 @@ function buildDeterministicEventId(parts) {
10059
10188
 
10060
10189
  // src/domains/paywall/SuperwallAutoBridge.ts
10061
10190
  var bridgeStarted = false;
10191
+ var startInProgress = false;
10062
10192
  var unsubscribe = null;
10063
10193
  var debugEnabled = false;
10064
10194
  function log2(msg, data) {
@@ -10099,8 +10229,7 @@ function mapDismissToCloseReason(resultType) {
10099
10229
  case "purchased":
10100
10230
  return "purchased";
10101
10231
  case "restored":
10102
- return "purchased";
10103
- // restore counts as a purchase outcome for funnel
10232
+ return "restored";
10104
10233
  case "declined":
10105
10234
  return "dismissed";
10106
10235
  default:
@@ -10137,16 +10266,18 @@ function buildCallbacks() {
10137
10266
  log2("onPurchase", { productId, identifier });
10138
10267
  void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
10139
10268
  }
10140
- // onSuperwallEvent removed transaction sale events are sourced exclusively
10141
- // from server-side webhooks (Apple/Google/Superwall/RevenueCat).
10269
+ // onSuperwallEvent (transactionComplete transaction track) removed
10270
+ // sale/transaction events are sourced exclusively from server-side webhooks
10271
+ // (Apple/Google/Superwall/RevenueCat), the single source of truth for revenue.
10142
10272
  };
10143
10273
  }
10144
10274
  function startSuperwallAutoBridge(debug = false) {
10145
10275
  debugEnabled = debug;
10146
- if (bridgeStarted) {
10147
- log2("already started, skipping");
10276
+ if (bridgeStarted || startInProgress) {
10277
+ log2("already started or starting, skipping");
10148
10278
  return;
10149
10279
  }
10280
+ startInProgress = true;
10150
10281
  void detectAndStart();
10151
10282
  }
10152
10283
  async function detectAndStart() {
@@ -10157,6 +10288,7 @@ async function detectAndStart() {
10157
10288
  const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
10158
10289
  if (typeof internal.subscribeToSuperwallEvents !== "function") {
10159
10290
  log2("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
10291
+ startInProgress = false;
10160
10292
  return;
10161
10293
  }
10162
10294
  subscribeFn = internal.subscribeToSuperwallEvents;
@@ -10165,6 +10297,11 @@ async function detectAndStart() {
10165
10297
  }
10166
10298
  } catch {
10167
10299
  log2("expo-superwall not installed \u2014 bridge inactive");
10300
+ startInProgress = false;
10301
+ return;
10302
+ }
10303
+ if (!startInProgress) {
10304
+ log2("start cancelled by stop \u2014 bridge inactive");
10168
10305
  return;
10169
10306
  }
10170
10307
  try {
@@ -10174,7 +10311,18 @@ async function detectAndStart() {
10174
10311
  log2("bridge started");
10175
10312
  } catch (err) {
10176
10313
  log2("subscribe failed", { err: String(err) });
10314
+ } finally {
10315
+ startInProgress = false;
10316
+ }
10317
+ }
10318
+ function stopSuperwallAutoBridge() {
10319
+ if (unsubscribe) {
10320
+ unsubscribe();
10321
+ unsubscribe = null;
10177
10322
  }
10323
+ bridgeStarted = false;
10324
+ startInProgress = false;
10325
+ log2("bridge stopped");
10178
10326
  }
10179
10327
 
10180
10328
  // src/PaywalloProvider.tsx
@@ -10741,6 +10889,9 @@ function PaywalloProvider({ children, config }) {
10741
10889
  const configRef = (0, import_react28.useRef)(config);
10742
10890
  (0, import_react28.useEffect)(() => {
10743
10891
  let mounted = true;
10892
+ const unsubscribeAttribution = attributionTracker.onCapture(() => {
10893
+ void pushAttributionToSuperwall(configRef.current.debug);
10894
+ });
10744
10895
  const bootstrap = async () => {
10745
10896
  if (!PaywalloClient.isReady()) {
10746
10897
  initLocalization({ detectDevice: true });
@@ -10751,11 +10902,10 @@ function PaywalloProvider({ children, config }) {
10751
10902
  setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
10752
10903
  return;
10753
10904
  }
10754
- if (!mounted) return;
10755
- startSuperwallAutoBridge(configRef.current.debug);
10756
- void pushAttributionToSuperwall(configRef.current.debug);
10757
10905
  }
10758
10906
  if (!mounted) return;
10907
+ startSuperwallAutoBridge(configRef.current.debug);
10908
+ void pushAttributionToSuperwall(configRef.current.debug);
10759
10909
  setDistinctId(PaywalloClient.getDistinctId());
10760
10910
  setIsInitialized(true);
10761
10911
  if (!autoPreloadTriggeredRef.current) {
@@ -10773,6 +10923,9 @@ function PaywalloProvider({ children, config }) {
10773
10923
  void bootstrap();
10774
10924
  return () => {
10775
10925
  mounted = false;
10926
+ unsubscribeAttribution();
10927
+ stopSuperwallAutoBridge();
10928
+ cancelSuperwallAttributeRetry();
10776
10929
  };
10777
10930
  }, [preloadCampaign]);
10778
10931
  const getPaywallConfig = (0, import_react28.useCallback)(async (p) => {