@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/README.md +23 -2
- package/android/src/main/AndroidManifest.xml +8 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +14 -2
- package/dist/index.d.mts +40 -29
- package/dist/index.d.ts +40 -29
- package/dist/index.js +456 -303
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +443 -290
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloAPNSProxy.m +138 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -271,8 +271,8 @@ var init_ClientError = __esm({
|
|
|
271
271
|
|
|
272
272
|
// src/utils/NativeDeviceInfo.ts
|
|
273
273
|
import { Platform } from "react-native";
|
|
274
|
-
import * as Device from "expo-device";
|
|
275
274
|
import * as Application from "expo-application";
|
|
275
|
+
import * as Device from "expo-device";
|
|
276
276
|
import * as Localization from "expo-localization";
|
|
277
277
|
function assertExpoModules() {
|
|
278
278
|
if (!Device || Device.osName === void 0) {
|
|
@@ -952,7 +952,9 @@ var init_AdvertisingIdManager = __esm({
|
|
|
952
952
|
return this.cached;
|
|
953
953
|
}
|
|
954
954
|
async collect(requestATT = false) {
|
|
955
|
-
if (this.cached
|
|
955
|
+
if (this.cached && !(requestATT && this.cached.attStatus === "undetermined")) {
|
|
956
|
+
return this.cached;
|
|
957
|
+
}
|
|
956
958
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
957
959
|
try {
|
|
958
960
|
const tracking = await import("expo-tracking-transparency");
|
|
@@ -1375,6 +1377,7 @@ var init_AttributionTracker = __esm({
|
|
|
1375
1377
|
constructor(debug = false) {
|
|
1376
1378
|
this.cache = null;
|
|
1377
1379
|
this.captureInProgress = false;
|
|
1380
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1378
1381
|
this.storage = new SecureStorage(debug);
|
|
1379
1382
|
}
|
|
1380
1383
|
/**
|
|
@@ -1414,10 +1417,31 @@ var init_AttributionTracker = __esm({
|
|
|
1414
1417
|
};
|
|
1415
1418
|
await this.storage.set(ATTRIBUTION_STORAGE_KEY, JSON.stringify(capture));
|
|
1416
1419
|
this.cache = capture;
|
|
1420
|
+
this.notifyListeners(capture);
|
|
1417
1421
|
} finally {
|
|
1418
1422
|
this.captureInProgress = false;
|
|
1419
1423
|
}
|
|
1420
1424
|
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Subscribes to the first real attribution write (first-write-wins — no-op
|
|
1427
|
+
* captures never fire). Useful for late enrichment consumers (e.g. re-push
|
|
1428
|
+
* of Superwall attributes when a warm deep link / deferred match lands after
|
|
1429
|
+
* init). Returns an unsubscribe function.
|
|
1430
|
+
*/
|
|
1431
|
+
onCapture(listener) {
|
|
1432
|
+
this.listeners.add(listener);
|
|
1433
|
+
return () => {
|
|
1434
|
+
this.listeners.delete(listener);
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
notifyListeners(capture) {
|
|
1438
|
+
for (const listener of this.listeners) {
|
|
1439
|
+
try {
|
|
1440
|
+
listener(capture);
|
|
1441
|
+
} catch {
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1421
1445
|
/**
|
|
1422
1446
|
* Returns the persisted attribution capture, or `null` if none exists.
|
|
1423
1447
|
* Synchronous — reads from in-memory cache (populated via `loadFromStorage()`).
|
|
@@ -1511,6 +1535,68 @@ var init_InstallIdempotency = __esm({
|
|
|
1511
1535
|
}
|
|
1512
1536
|
});
|
|
1513
1537
|
|
|
1538
|
+
// src/domains/identity/deferredAppLink.ts
|
|
1539
|
+
function parseQuery(s) {
|
|
1540
|
+
const result = {};
|
|
1541
|
+
for (const pair of s.split("&")) {
|
|
1542
|
+
const eqIndex = pair.indexOf("=");
|
|
1543
|
+
if (eqIndex === -1) continue;
|
|
1544
|
+
try {
|
|
1545
|
+
const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
|
|
1546
|
+
const value = decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));
|
|
1547
|
+
if (key) result[key] = value;
|
|
1548
|
+
} catch {
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return result;
|
|
1552
|
+
}
|
|
1553
|
+
function parseDeferredAppLink(url) {
|
|
1554
|
+
if (!url || url.trim() === "") return null;
|
|
1555
|
+
const raw = url.slice(0, 2048);
|
|
1556
|
+
const cleanRaw = raw.split("#")[0];
|
|
1557
|
+
const qIndex = cleanRaw.indexOf("?");
|
|
1558
|
+
if (qIndex === -1) return null;
|
|
1559
|
+
const topParams = parseQuery(cleanRaw.slice(qIndex + 1));
|
|
1560
|
+
let fbclid = nz(topParams["fbclid"]);
|
|
1561
|
+
let utmSource = nz(topParams["utm_source"]);
|
|
1562
|
+
let utmMedium = nz(topParams["utm_medium"]);
|
|
1563
|
+
let utmCampaign = nz(topParams["utm_campaign"]);
|
|
1564
|
+
let ttclid = nz(topParams["ttclid"]);
|
|
1565
|
+
let trackingId = nz(topParams["tracking_id"]);
|
|
1566
|
+
let targetUrl = null;
|
|
1567
|
+
if (topParams["target_url"]) {
|
|
1568
|
+
try {
|
|
1569
|
+
targetUrl = decodeURIComponent(topParams["target_url"]);
|
|
1570
|
+
} catch {
|
|
1571
|
+
targetUrl = topParams["target_url"];
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
if (!fbclid && targetUrl) {
|
|
1575
|
+
const cleanTargetUrl = targetUrl.split("#")[0];
|
|
1576
|
+
const innerQ = cleanTargetUrl.indexOf("?");
|
|
1577
|
+
if (innerQ !== -1) {
|
|
1578
|
+
const inner = parseQuery(cleanTargetUrl.slice(innerQ + 1));
|
|
1579
|
+
fbclid = fbclid ?? nz(inner["fbclid"]);
|
|
1580
|
+
utmSource = utmSource ?? nz(inner["utm_source"]);
|
|
1581
|
+
utmMedium = utmMedium ?? nz(inner["utm_medium"]);
|
|
1582
|
+
utmCampaign = utmCampaign ?? nz(inner["utm_campaign"]);
|
|
1583
|
+
ttclid = ttclid ?? nz(inner["ttclid"]);
|
|
1584
|
+
trackingId = trackingId ?? nz(inner["tracking_id"]);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
if (!fbclid && !ttclid && !trackingId && !utmSource && !utmMedium && !utmCampaign) {
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
return { fbclid, utmSource, utmMedium, utmCampaign, ttclid, trackingId, targetUrl, raw };
|
|
1591
|
+
}
|
|
1592
|
+
var nz;
|
|
1593
|
+
var init_deferredAppLink = __esm({
|
|
1594
|
+
"src/domains/identity/deferredAppLink.ts"() {
|
|
1595
|
+
"use strict";
|
|
1596
|
+
nz = (v) => typeof v === "string" && v.trim().length > 0 ? v : null;
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
|
|
1514
1600
|
// src/domains/identity/MetaBridge.ts
|
|
1515
1601
|
import { NativeModules as NativeModules2 } from "react-native";
|
|
1516
1602
|
var TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
|
|
@@ -1609,68 +1695,6 @@ var init_MetaBridge = __esm({
|
|
|
1609
1695
|
}
|
|
1610
1696
|
});
|
|
1611
1697
|
|
|
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
|
-
|
|
1674
1698
|
// src/domains/identity/InstallReferrerManager.ts
|
|
1675
1699
|
import { Platform as Platform3 } from "react-native";
|
|
1676
1700
|
var REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
|
|
@@ -1678,8 +1702,8 @@ var init_InstallReferrerManager = __esm({
|
|
|
1678
1702
|
"src/domains/identity/InstallReferrerManager.ts"() {
|
|
1679
1703
|
"use strict";
|
|
1680
1704
|
init_NativeDeviceInfo();
|
|
1681
|
-
init_MetaBridge();
|
|
1682
1705
|
init_deferredAppLink();
|
|
1706
|
+
init_MetaBridge();
|
|
1683
1707
|
REFERRER_TIMEOUT_MS = 5e3;
|
|
1684
1708
|
InstallReferrerManager = class {
|
|
1685
1709
|
async getReferrer() {
|
|
@@ -1780,7 +1804,17 @@ var init_InstallReferrerManager = __esm({
|
|
|
1780
1804
|
|
|
1781
1805
|
// src/domains/identity/InstallTracker.ts
|
|
1782
1806
|
import { Dimensions, PixelRatio, Platform as Platform4 } from "react-native";
|
|
1783
|
-
|
|
1807
|
+
function extractDeferredAttribution(raw) {
|
|
1808
|
+
if (raw === null || typeof raw !== "object") return null;
|
|
1809
|
+
const source = raw;
|
|
1810
|
+
const out = {};
|
|
1811
|
+
for (const field of DEFERRED_ATTRIBUTION_FIELDS) {
|
|
1812
|
+
const value = source[field];
|
|
1813
|
+
if (typeof value === "string" && value !== "") out[field] = value;
|
|
1814
|
+
}
|
|
1815
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1816
|
+
}
|
|
1817
|
+
var InstallTracker, DEFERRED_ATTRIBUTION_FIELDS;
|
|
1784
1818
|
var init_InstallTracker = __esm({
|
|
1785
1819
|
"src/domains/identity/InstallTracker.ts"() {
|
|
1786
1820
|
"use strict";
|
|
@@ -1945,7 +1979,7 @@ var init_InstallTracker = __esm({
|
|
|
1945
1979
|
const baseUrl = apiClient.getBaseUrl();
|
|
1946
1980
|
const appKey = apiClient.getAppKey();
|
|
1947
1981
|
const referrer = await installReferrerManager.getReferrer();
|
|
1948
|
-
await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
|
|
1982
|
+
const response = await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
|
|
1949
1983
|
method: "POST",
|
|
1950
1984
|
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
1951
1985
|
body: JSON.stringify({
|
|
@@ -1967,12 +2001,42 @@ var init_InstallTracker = __esm({
|
|
|
1967
2001
|
});
|
|
1968
2002
|
await storage.set(INSTALL_KEYS.DEFERRED_MATCH_DONE, "1").catch(() => {
|
|
1969
2003
|
});
|
|
2004
|
+
await this.applyDeferredMatchResponse(response);
|
|
1970
2005
|
} catch {
|
|
1971
2006
|
} finally {
|
|
1972
2007
|
clearTimeout(timer);
|
|
1973
2008
|
}
|
|
1974
2009
|
}
|
|
2010
|
+
async applyDeferredMatchResponse(response) {
|
|
2011
|
+
try {
|
|
2012
|
+
const body = await response.json();
|
|
2013
|
+
if (body === null || typeof body !== "object") return;
|
|
2014
|
+
const outer = body;
|
|
2015
|
+
const source = typeof outer.data === "object" && outer.data !== null ? outer.data : body;
|
|
2016
|
+
if (source.matched !== true) return;
|
|
2017
|
+
const attribution = extractDeferredAttribution(source.attribution);
|
|
2018
|
+
if (!attribution) return;
|
|
2019
|
+
await attributionTracker.capture({
|
|
2020
|
+
...attribution,
|
|
2021
|
+
installReferrerSource: "deferred_match"
|
|
2022
|
+
});
|
|
2023
|
+
} catch {
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1975
2026
|
};
|
|
2027
|
+
DEFERRED_ATTRIBUTION_FIELDS = [
|
|
2028
|
+
"fbclid",
|
|
2029
|
+
"gclid",
|
|
2030
|
+
"ttclid",
|
|
2031
|
+
"utmSource",
|
|
2032
|
+
"utmMedium",
|
|
2033
|
+
"utmCampaign",
|
|
2034
|
+
"utmContent",
|
|
2035
|
+
"utmTerm",
|
|
2036
|
+
"tiktokCampaignId",
|
|
2037
|
+
"tiktokAdgroupId",
|
|
2038
|
+
"tiktokAdId"
|
|
2039
|
+
];
|
|
1976
2040
|
}
|
|
1977
2041
|
});
|
|
1978
2042
|
|
|
@@ -2547,8 +2611,8 @@ var init_NotificationHandlerSetup = __esm({
|
|
|
2547
2611
|
// src/core/version.ts
|
|
2548
2612
|
function resolveVersion() {
|
|
2549
2613
|
try {
|
|
2550
|
-
if ("2.5.
|
|
2551
|
-
return "2.5.
|
|
2614
|
+
if ("2.5.2") {
|
|
2615
|
+
return "2.5.2";
|
|
2552
2616
|
}
|
|
2553
2617
|
} catch {
|
|
2554
2618
|
}
|
|
@@ -3153,17 +3217,154 @@ var init_NotificationsManager = __esm({
|
|
|
3153
3217
|
this.currentToken = null;
|
|
3154
3218
|
}
|
|
3155
3219
|
};
|
|
3156
|
-
notificationsManager = new NotificationsManager();
|
|
3220
|
+
notificationsManager = new NotificationsManager();
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
|
|
3224
|
+
// src/domains/notifications/index.ts
|
|
3225
|
+
var init_notifications = __esm({
|
|
3226
|
+
"src/domains/notifications/index.ts"() {
|
|
3227
|
+
"use strict";
|
|
3228
|
+
init_NativePushBridge();
|
|
3229
|
+
init_NotificationsError();
|
|
3230
|
+
init_NotificationsManager();
|
|
3231
|
+
}
|
|
3232
|
+
});
|
|
3233
|
+
|
|
3234
|
+
// src/domains/onboarding/OnboardingError.ts
|
|
3235
|
+
var OnboardingError, ONBOARDING_ERROR_CODES;
|
|
3236
|
+
var init_OnboardingError = __esm({
|
|
3237
|
+
"src/domains/onboarding/OnboardingError.ts"() {
|
|
3238
|
+
"use strict";
|
|
3239
|
+
init_PaywalloError();
|
|
3240
|
+
OnboardingError = class extends PaywalloError {
|
|
3241
|
+
constructor(code, message) {
|
|
3242
|
+
super("onboarding", code, message);
|
|
3243
|
+
this.name = "OnboardingError";
|
|
3244
|
+
}
|
|
3245
|
+
};
|
|
3246
|
+
ONBOARDING_ERROR_CODES = {
|
|
3247
|
+
NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
|
|
3248
|
+
INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
|
|
3249
|
+
};
|
|
3250
|
+
}
|
|
3251
|
+
});
|
|
3252
|
+
|
|
3253
|
+
// src/domains/onboarding/OnboardingManager.ts
|
|
3254
|
+
var OnboardingManager, onboardingManager;
|
|
3255
|
+
var init_OnboardingManager = __esm({
|
|
3256
|
+
"src/domains/onboarding/OnboardingManager.ts"() {
|
|
3257
|
+
"use strict";
|
|
3258
|
+
init_OnboardingError();
|
|
3259
|
+
OnboardingManager = class {
|
|
3260
|
+
constructor() {
|
|
3261
|
+
this.pipeline = null;
|
|
3262
|
+
this.debug = false;
|
|
3263
|
+
this.lastStep = null;
|
|
3264
|
+
this.finished = false;
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Injects runtime dependencies. Called by PaywalloClient during init.
|
|
3268
|
+
*/
|
|
3269
|
+
injectDeps(config) {
|
|
3270
|
+
this.pipeline = config.pipeline;
|
|
3271
|
+
this.debug = config.debug ?? false;
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Tracks an onboarding step view.
|
|
3275
|
+
* Saves the step name internally for implicit drop detection on the backend.
|
|
3276
|
+
*
|
|
3277
|
+
* @param stepName - Unique name for this onboarding step.
|
|
3278
|
+
* @param order - Optional explicit display order (accepts decimals, e.g. 2.1 for A/B variants).
|
|
3279
|
+
*/
|
|
3280
|
+
async step(stepName, order) {
|
|
3281
|
+
this.assertConfig();
|
|
3282
|
+
this.validateStepName(stepName, "step");
|
|
3283
|
+
this.lastStep = stepName;
|
|
3284
|
+
const stepPayload = { step_name: stepName };
|
|
3285
|
+
if (order !== void 0 && Number.isFinite(order) && order >= 0) {
|
|
3286
|
+
stepPayload.order = order;
|
|
3287
|
+
}
|
|
3288
|
+
const payload = {
|
|
3289
|
+
family: "onboarding",
|
|
3290
|
+
type: "step",
|
|
3291
|
+
...stepPayload
|
|
3292
|
+
};
|
|
3293
|
+
return this.emit("onboarding", payload, "step");
|
|
3294
|
+
}
|
|
3295
|
+
/**
|
|
3296
|
+
* Tracks successful completion of the onboarding flow.
|
|
3297
|
+
* Marks the flow as finished.
|
|
3298
|
+
*/
|
|
3299
|
+
async complete() {
|
|
3300
|
+
this.assertConfig();
|
|
3301
|
+
this.finished = true;
|
|
3302
|
+
return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Tracks an explicit drop (abandonment) at the given step.
|
|
3306
|
+
* Marks the flow as finished.
|
|
3307
|
+
*
|
|
3308
|
+
* @param stepName - The step name where the user dropped off.
|
|
3309
|
+
*/
|
|
3310
|
+
async drop(stepName) {
|
|
3311
|
+
this.assertConfig();
|
|
3312
|
+
this.validateStepName(stepName, "drop");
|
|
3313
|
+
this.finished = true;
|
|
3314
|
+
const payload = {
|
|
3315
|
+
family: "onboarding",
|
|
3316
|
+
type: "drop",
|
|
3317
|
+
...{ step_name: stepName }
|
|
3318
|
+
};
|
|
3319
|
+
return this.emit("onboarding", payload, "drop");
|
|
3320
|
+
}
|
|
3321
|
+
/** Returns the last step name seen, or null if no step was tracked yet. */
|
|
3322
|
+
getLastStep() {
|
|
3323
|
+
return this.lastStep;
|
|
3324
|
+
}
|
|
3325
|
+
/** Returns whether the flow has been marked as finished (complete or drop). */
|
|
3326
|
+
isFinished() {
|
|
3327
|
+
return this.finished;
|
|
3328
|
+
}
|
|
3329
|
+
async emit(eventName, properties, logLabel) {
|
|
3330
|
+
const pipeline = this.pipeline;
|
|
3331
|
+
const distinctId = pipeline.getDistinctId();
|
|
3332
|
+
if (!distinctId) {
|
|
3333
|
+
if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
await pipeline.trackEvent(eventName, distinctId, properties);
|
|
3337
|
+
if (this.debug) {
|
|
3338
|
+
console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
assertConfig() {
|
|
3342
|
+
if (!this.pipeline) {
|
|
3343
|
+
throw new OnboardingError(
|
|
3344
|
+
ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
|
|
3345
|
+
"OnboardingManager not initialized. Call Paywallo.init() first."
|
|
3346
|
+
);
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
validateStepName(stepName, method) {
|
|
3350
|
+
if (typeof stepName !== "string" || stepName.trim().length === 0) {
|
|
3351
|
+
throw new OnboardingError(
|
|
3352
|
+
ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
|
|
3353
|
+
`OnboardingManager.${method}(): stepName must be a non-empty string.`
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
};
|
|
3358
|
+
onboardingManager = new OnboardingManager();
|
|
3157
3359
|
}
|
|
3158
3360
|
});
|
|
3159
3361
|
|
|
3160
|
-
// src/domains/
|
|
3161
|
-
var
|
|
3162
|
-
"src/domains/
|
|
3362
|
+
// src/domains/onboarding/index.ts
|
|
3363
|
+
var init_onboarding = __esm({
|
|
3364
|
+
"src/domains/onboarding/index.ts"() {
|
|
3163
3365
|
"use strict";
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
init_NotificationsManager();
|
|
3366
|
+
init_OnboardingError();
|
|
3367
|
+
init_OnboardingManager();
|
|
3167
3368
|
}
|
|
3168
3369
|
});
|
|
3169
3370
|
|
|
@@ -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: z.string().optional(),
|
|
6624
6688
|
duration_s: z.number().nonnegative().optional(),
|
|
6625
|
-
|
|
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: z.enum([
|
|
6695
|
+
"dismiss",
|
|
6696
|
+
"cta",
|
|
6697
|
+
"purchase",
|
|
6698
|
+
"error",
|
|
6699
|
+
"timeout",
|
|
6700
|
+
"purchased",
|
|
6701
|
+
"dismissed",
|
|
6702
|
+
"restored"
|
|
6703
|
+
]).optional(),
|
|
6626
6704
|
scroll_depth: z.number().min(0).max(1).optional(),
|
|
6627
6705
|
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6628
6706
|
product_id: z.string().optional()
|
|
@@ -6630,21 +6708,33 @@ var init_schemas = __esm({
|
|
|
6630
6708
|
transactionEventSchema = z.object({
|
|
6631
6709
|
type: 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
|
-
|
|
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: z.string().min(1).optional(),
|
|
6723
|
+
tx_id: z.string().min(1).optional(),
|
|
6640
6724
|
amount: 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: z.number().nonnegative().optional(),
|
|
6641
6728
|
currency: z.string().length(3).optional(),
|
|
6642
6729
|
product_id: z.string().optional(),
|
|
6643
6730
|
subscription_id: z.string().optional(),
|
|
6644
6731
|
paywall_id: z.string().optional(),
|
|
6645
6732
|
variant_id: z.string().optional(),
|
|
6646
6733
|
reason: 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 = z.object({
|
|
6649
6739
|
type: z.enum(["step", "complete", "drop"]),
|
|
6650
6740
|
step_index: 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
|
-
|
|
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
|
|
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;
|
|
@@ -8904,11 +8999,11 @@ var init_PaywalloClient = __esm({
|
|
|
8904
8999
|
init_campaign();
|
|
8905
9000
|
init_identity();
|
|
8906
9001
|
init_notifications();
|
|
9002
|
+
init_onboarding();
|
|
8907
9003
|
init_paywall();
|
|
8908
9004
|
init_plan();
|
|
8909
9005
|
init_session();
|
|
8910
9006
|
init_ClientError();
|
|
8911
|
-
init_onboarding();
|
|
8912
9007
|
init_network();
|
|
8913
9008
|
init_PaywalloAnalytics();
|
|
8914
9009
|
init_PaywalloFlags();
|
|
@@ -9536,8 +9631,41 @@ function usePlanPurchase() {
|
|
|
9536
9631
|
};
|
|
9537
9632
|
}
|
|
9538
9633
|
|
|
9634
|
+
// src/hooks/usePaywalloScreenTracking.ts
|
|
9635
|
+
init_PaywalloClient();
|
|
9636
|
+
import { useCallback as useCallback11, useRef as useRef8 } from "react";
|
|
9637
|
+
function isNavState(value) {
|
|
9638
|
+
return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
|
|
9639
|
+
}
|
|
9640
|
+
function getActiveRouteName(state) {
|
|
9641
|
+
try {
|
|
9642
|
+
if (!isNavState(state)) return null;
|
|
9643
|
+
const route = state.routes[state.index];
|
|
9644
|
+
if (!route) return null;
|
|
9645
|
+
if (isNavState(route.state)) {
|
|
9646
|
+
return getActiveRouteName(route.state);
|
|
9647
|
+
}
|
|
9648
|
+
return typeof route.name === "string" ? route.name : null;
|
|
9649
|
+
} catch {
|
|
9650
|
+
return null;
|
|
9651
|
+
}
|
|
9652
|
+
}
|
|
9653
|
+
function usePaywalloScreenTracking() {
|
|
9654
|
+
const lastScreenRef = useRef8(null);
|
|
9655
|
+
return useCallback11((state) => {
|
|
9656
|
+
if (!PaywalloClient.isReady()) return;
|
|
9657
|
+
const screenName = getActiveRouteName(state);
|
|
9658
|
+
if (!screenName || screenName === lastScreenRef.current) return;
|
|
9659
|
+
lastScreenRef.current = screenName;
|
|
9660
|
+
void PaywalloClient.track("screen_view", {
|
|
9661
|
+
properties: { screen_name: screenName }
|
|
9662
|
+
}).catch(() => {
|
|
9663
|
+
});
|
|
9664
|
+
}, []);
|
|
9665
|
+
}
|
|
9666
|
+
|
|
9539
9667
|
// src/hooks/usePlans.ts
|
|
9540
|
-
import { useCallback as
|
|
9668
|
+
import { useCallback as useCallback12, useEffect as useEffect10, useState as useState10 } from "react";
|
|
9541
9669
|
async function getPlanService() {
|
|
9542
9670
|
try {
|
|
9543
9671
|
const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
|
|
@@ -9551,7 +9679,7 @@ function usePlans() {
|
|
|
9551
9679
|
const [currentPlan, setCurrentPlan] = useState10(null);
|
|
9552
9680
|
const [isLoading, setIsLoading] = useState10(true);
|
|
9553
9681
|
const [error, setError] = useState10(null);
|
|
9554
|
-
const fetchPlans =
|
|
9682
|
+
const fetchPlans = useCallback12(async (forceRefresh = false) => {
|
|
9555
9683
|
setIsLoading(true);
|
|
9556
9684
|
setError(null);
|
|
9557
9685
|
try {
|
|
@@ -9572,7 +9700,7 @@ function usePlans() {
|
|
|
9572
9700
|
setIsLoading(false);
|
|
9573
9701
|
}
|
|
9574
9702
|
}, []);
|
|
9575
|
-
const refresh =
|
|
9703
|
+
const refresh = useCallback12(() => {
|
|
9576
9704
|
return fetchPlans(true);
|
|
9577
9705
|
}, [fetchPlans]);
|
|
9578
9706
|
useEffect10(() => {
|
|
@@ -9582,7 +9710,7 @@ function usePlans() {
|
|
|
9582
9710
|
}
|
|
9583
9711
|
|
|
9584
9712
|
// src/hooks/useProducts.ts
|
|
9585
|
-
import { useCallback as
|
|
9713
|
+
import { useCallback as useCallback13, useEffect as useEffect11, useState as useState11 } from "react";
|
|
9586
9714
|
|
|
9587
9715
|
// src/utils/productMappers.ts
|
|
9588
9716
|
function mapToIAPProduct(product) {
|
|
@@ -9625,7 +9753,7 @@ function useProducts(initialProductIds) {
|
|
|
9625
9753
|
const [formattedProducts, setFormattedProducts] = useState11([]);
|
|
9626
9754
|
const [isLoading, setIsLoading] = useState11(false);
|
|
9627
9755
|
const [error, setError] = useState11(null);
|
|
9628
|
-
const loadProducts =
|
|
9756
|
+
const loadProducts = useCallback13(async (productIds) => {
|
|
9629
9757
|
setIsLoading(true);
|
|
9630
9758
|
setError(null);
|
|
9631
9759
|
try {
|
|
@@ -9644,19 +9772,19 @@ function useProducts(initialProductIds) {
|
|
|
9644
9772
|
void loadProducts(initialProductIds);
|
|
9645
9773
|
}
|
|
9646
9774
|
}, []);
|
|
9647
|
-
const getProduct =
|
|
9775
|
+
const getProduct = useCallback13(
|
|
9648
9776
|
(productId) => {
|
|
9649
9777
|
return products.find((p) => p.productId === productId);
|
|
9650
9778
|
},
|
|
9651
9779
|
[products]
|
|
9652
9780
|
);
|
|
9653
|
-
const getFormattedProduct =
|
|
9781
|
+
const getFormattedProduct = useCallback13(
|
|
9654
9782
|
(productId) => {
|
|
9655
9783
|
return formattedProducts.find((p) => p.productId === productId);
|
|
9656
9784
|
},
|
|
9657
9785
|
[formattedProducts]
|
|
9658
9786
|
);
|
|
9659
|
-
const getPriceInfo =
|
|
9787
|
+
const getPriceInfo = useCallback13(
|
|
9660
9788
|
(productId) => {
|
|
9661
9789
|
const p = formattedProducts.find((fp) => fp.productId === productId);
|
|
9662
9790
|
if (!p) return void 0;
|
|
@@ -9685,14 +9813,14 @@ function useProducts(initialProductIds) {
|
|
|
9685
9813
|
}
|
|
9686
9814
|
|
|
9687
9815
|
// src/hooks/usePurchase.ts
|
|
9688
|
-
import { useCallback as
|
|
9816
|
+
import { useCallback as useCallback14, useState as useState12 } from "react";
|
|
9689
9817
|
function toIAPPurchase(p) {
|
|
9690
9818
|
return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
|
|
9691
9819
|
}
|
|
9692
9820
|
function usePurchase() {
|
|
9693
9821
|
const [state, setState] = useState12("idle");
|
|
9694
9822
|
const [error, setError] = useState12(null);
|
|
9695
|
-
const purchase =
|
|
9823
|
+
const purchase = useCallback14(async (productId) => {
|
|
9696
9824
|
setState("purchasing");
|
|
9697
9825
|
setError(null);
|
|
9698
9826
|
try {
|
|
@@ -9720,7 +9848,7 @@ function usePurchase() {
|
|
|
9720
9848
|
return { status: "failed", error: purchaseError };
|
|
9721
9849
|
}
|
|
9722
9850
|
}, []);
|
|
9723
|
-
const restore =
|
|
9851
|
+
const restore = useCallback14(async () => {
|
|
9724
9852
|
setState("restoring");
|
|
9725
9853
|
setError(null);
|
|
9726
9854
|
try {
|
|
@@ -9739,14 +9867,14 @@ function usePurchase() {
|
|
|
9739
9867
|
}
|
|
9740
9868
|
|
|
9741
9869
|
// src/hooks/useSubscription.ts
|
|
9742
|
-
import { useCallback as
|
|
9870
|
+
import { useCallback as useCallback15, useEffect as useEffect12, useState as useState13 } from "react";
|
|
9743
9871
|
init_SubscriptionManager();
|
|
9744
9872
|
var EMPTY_ENTITLEMENTS = [];
|
|
9745
9873
|
function useSubscription() {
|
|
9746
9874
|
const [status, setStatus] = useState13(null);
|
|
9747
9875
|
const [isLoading, setIsLoading] = useState13(true);
|
|
9748
9876
|
const [error, setError] = useState13(null);
|
|
9749
|
-
const refresh =
|
|
9877
|
+
const refresh = useCallback15(async () => {
|
|
9750
9878
|
setIsLoading(true);
|
|
9751
9879
|
setError(null);
|
|
9752
9880
|
try {
|
|
@@ -9759,7 +9887,7 @@ function useSubscription() {
|
|
|
9759
9887
|
setIsLoading(false);
|
|
9760
9888
|
}
|
|
9761
9889
|
}, []);
|
|
9762
|
-
const restore =
|
|
9890
|
+
const restore = useCallback15(async () => {
|
|
9763
9891
|
setIsLoading(true);
|
|
9764
9892
|
setError(null);
|
|
9765
9893
|
try {
|
|
@@ -9799,39 +9927,6 @@ function useSubscription() {
|
|
|
9799
9927
|
};
|
|
9800
9928
|
}
|
|
9801
9929
|
|
|
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
|
-
|
|
9835
9930
|
// src/PaywalloProvider.tsx
|
|
9836
9931
|
import { useCallback as useCallback20, useEffect as useEffect14, useLayoutEffect, useMemo as useMemo3, useRef as useRef12, useState as useState17 } from "react";
|
|
9837
9932
|
import { Animated as Animated4, Dimensions as Dimensions5, Easing as Easing2, StyleSheet as StyleSheet5 } from "react-native";
|
|
@@ -9839,6 +9934,7 @@ init_PaywalloClient();
|
|
|
9839
9934
|
init_NativeStorage();
|
|
9840
9935
|
init_SecureStorage();
|
|
9841
9936
|
init_campaign();
|
|
9937
|
+
init_identity();
|
|
9842
9938
|
init_paywall();
|
|
9843
9939
|
init_paywall();
|
|
9844
9940
|
init_paywall();
|
|
@@ -9850,7 +9946,9 @@ init_paywall();
|
|
|
9850
9946
|
init_identity();
|
|
9851
9947
|
var CONFIG_POLL_INTERVAL_MS = 250;
|
|
9852
9948
|
var CONFIG_POLL_MAX_ATTEMPTS = 40;
|
|
9949
|
+
var CONFIG_RETRY_DELAYS_MS = [3e4, 6e4, 12e4];
|
|
9853
9950
|
var lastSignature = null;
|
|
9951
|
+
var retryTimer = null;
|
|
9854
9952
|
function log(debug, msg, data) {
|
|
9855
9953
|
if (!debug) return;
|
|
9856
9954
|
if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
|
|
@@ -9917,28 +10015,37 @@ async function waitForConfigured(mod, debug) {
|
|
|
9917
10015
|
status = String(await mod.getConfigurationStatus());
|
|
9918
10016
|
} catch (err) {
|
|
9919
10017
|
log(debug, "getConfigurationStatus error", { err: String(err) });
|
|
9920
|
-
return
|
|
10018
|
+
return "failed";
|
|
9921
10019
|
}
|
|
9922
|
-
if (status === "CONFIGURED") return
|
|
10020
|
+
if (status === "CONFIGURED") return "configured";
|
|
9923
10021
|
if (status === "FAILED") {
|
|
9924
10022
|
log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
|
|
9925
|
-
return
|
|
10023
|
+
return "failed";
|
|
9926
10024
|
}
|
|
9927
10025
|
await delay(CONFIG_POLL_INTERVAL_MS);
|
|
9928
10026
|
}
|
|
9929
10027
|
log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
|
|
9930
|
-
return
|
|
10028
|
+
return "timeout";
|
|
9931
10029
|
}
|
|
9932
10030
|
async function pushAttributionToSuperwall(debug = false) {
|
|
10031
|
+
clearRetryTimer();
|
|
10032
|
+
await attemptPush(debug, 0);
|
|
10033
|
+
}
|
|
10034
|
+
async function attemptPush(debug, retryIndex) {
|
|
10035
|
+
const native = await resolveSuperwallNativeModule(debug);
|
|
10036
|
+
if (!native) return;
|
|
10037
|
+
const waited = await waitForConfigured(native, debug);
|
|
10038
|
+
if (waited === "timeout") {
|
|
10039
|
+
scheduleRetry(debug, retryIndex);
|
|
10040
|
+
return;
|
|
10041
|
+
}
|
|
10042
|
+
if (waited !== "configured") return;
|
|
9933
10043
|
const attrs = buildSuperwallAttributes(attributionTracker.get());
|
|
9934
10044
|
const signature = JSON.stringify(attrs);
|
|
9935
10045
|
if (signature === lastSignature) {
|
|
9936
10046
|
log(debug, "attributes unchanged \u2014 skipping push");
|
|
9937
10047
|
return;
|
|
9938
10048
|
}
|
|
9939
|
-
const native = await resolveSuperwallNativeModule(debug);
|
|
9940
|
-
if (!native) return;
|
|
9941
|
-
if (!await waitForConfigured(native, debug)) return;
|
|
9942
10049
|
try {
|
|
9943
10050
|
await native.setUserAttributes(attrs);
|
|
9944
10051
|
lastSignature = signature;
|
|
@@ -9947,6 +10054,28 @@ async function pushAttributionToSuperwall(debug = false) {
|
|
|
9947
10054
|
log(debug, "setUserAttributes error", { err: String(err) });
|
|
9948
10055
|
}
|
|
9949
10056
|
}
|
|
10057
|
+
function scheduleRetry(debug, retryIndex) {
|
|
10058
|
+
const delayMs = CONFIG_RETRY_DELAYS_MS[retryIndex];
|
|
10059
|
+
if (delayMs === void 0) {
|
|
10060
|
+
log(debug, "Superwall never configured \u2014 giving up attribute push");
|
|
10061
|
+
return;
|
|
10062
|
+
}
|
|
10063
|
+
clearRetryTimer();
|
|
10064
|
+
log(debug, "scheduling attribute push retry", { retryIndex, delayMs });
|
|
10065
|
+
retryTimer = setTimeout(() => {
|
|
10066
|
+
retryTimer = null;
|
|
10067
|
+
void attemptPush(debug, retryIndex + 1);
|
|
10068
|
+
}, delayMs);
|
|
10069
|
+
}
|
|
10070
|
+
function clearRetryTimer() {
|
|
10071
|
+
if (retryTimer !== null) {
|
|
10072
|
+
clearTimeout(retryTimer);
|
|
10073
|
+
retryTimer = null;
|
|
10074
|
+
}
|
|
10075
|
+
}
|
|
10076
|
+
function cancelSuperwallAttributeRetry() {
|
|
10077
|
+
clearRetryTimer();
|
|
10078
|
+
}
|
|
9950
10079
|
|
|
9951
10080
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9952
10081
|
init_PaywalloClient();
|
|
@@ -9979,6 +10108,7 @@ function buildDeterministicEventId(parts) {
|
|
|
9979
10108
|
|
|
9980
10109
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9981
10110
|
var bridgeStarted = false;
|
|
10111
|
+
var startInProgress = false;
|
|
9982
10112
|
var unsubscribe = null;
|
|
9983
10113
|
var debugEnabled = false;
|
|
9984
10114
|
function log2(msg, data) {
|
|
@@ -10019,8 +10149,7 @@ function mapDismissToCloseReason(resultType) {
|
|
|
10019
10149
|
case "purchased":
|
|
10020
10150
|
return "purchased";
|
|
10021
10151
|
case "restored":
|
|
10022
|
-
return "
|
|
10023
|
-
// restore counts as a purchase outcome for funnel
|
|
10152
|
+
return "restored";
|
|
10024
10153
|
case "declined":
|
|
10025
10154
|
return "dismissed";
|
|
10026
10155
|
default:
|
|
@@ -10057,16 +10186,18 @@ function buildCallbacks() {
|
|
|
10057
10186
|
log2("onPurchase", { productId, identifier });
|
|
10058
10187
|
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10059
10188
|
}
|
|
10060
|
-
// onSuperwallEvent
|
|
10061
|
-
// from server-side webhooks
|
|
10189
|
+
// onSuperwallEvent (transactionComplete → transaction track) removed —
|
|
10190
|
+
// sale/transaction events are sourced exclusively from server-side webhooks
|
|
10191
|
+
// (Apple/Google/Superwall/RevenueCat), the single source of truth for revenue.
|
|
10062
10192
|
};
|
|
10063
10193
|
}
|
|
10064
10194
|
function startSuperwallAutoBridge(debug = false) {
|
|
10065
10195
|
debugEnabled = debug;
|
|
10066
|
-
if (bridgeStarted) {
|
|
10067
|
-
log2("already started, skipping");
|
|
10196
|
+
if (bridgeStarted || startInProgress) {
|
|
10197
|
+
log2("already started or starting, skipping");
|
|
10068
10198
|
return;
|
|
10069
10199
|
}
|
|
10200
|
+
startInProgress = true;
|
|
10070
10201
|
void detectAndStart();
|
|
10071
10202
|
}
|
|
10072
10203
|
async function detectAndStart() {
|
|
@@ -10077,6 +10208,7 @@ async function detectAndStart() {
|
|
|
10077
10208
|
const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
|
|
10078
10209
|
if (typeof internal.subscribeToSuperwallEvents !== "function") {
|
|
10079
10210
|
log2("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
|
|
10211
|
+
startInProgress = false;
|
|
10080
10212
|
return;
|
|
10081
10213
|
}
|
|
10082
10214
|
subscribeFn = internal.subscribeToSuperwallEvents;
|
|
@@ -10085,6 +10217,11 @@ async function detectAndStart() {
|
|
|
10085
10217
|
}
|
|
10086
10218
|
} catch {
|
|
10087
10219
|
log2("expo-superwall not installed \u2014 bridge inactive");
|
|
10220
|
+
startInProgress = false;
|
|
10221
|
+
return;
|
|
10222
|
+
}
|
|
10223
|
+
if (!startInProgress) {
|
|
10224
|
+
log2("start cancelled by stop \u2014 bridge inactive");
|
|
10088
10225
|
return;
|
|
10089
10226
|
}
|
|
10090
10227
|
try {
|
|
@@ -10094,7 +10231,18 @@ async function detectAndStart() {
|
|
|
10094
10231
|
log2("bridge started");
|
|
10095
10232
|
} catch (err) {
|
|
10096
10233
|
log2("subscribe failed", { err: String(err) });
|
|
10234
|
+
} finally {
|
|
10235
|
+
startInProgress = false;
|
|
10236
|
+
}
|
|
10237
|
+
}
|
|
10238
|
+
function stopSuperwallAutoBridge() {
|
|
10239
|
+
if (unsubscribe) {
|
|
10240
|
+
unsubscribe();
|
|
10241
|
+
unsubscribe = null;
|
|
10097
10242
|
}
|
|
10243
|
+
bridgeStarted = false;
|
|
10244
|
+
startInProgress = false;
|
|
10245
|
+
log2("bridge stopped");
|
|
10098
10246
|
}
|
|
10099
10247
|
|
|
10100
10248
|
// src/PaywalloProvider.tsx
|
|
@@ -10661,6 +10809,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10661
10809
|
const configRef = useRef12(config);
|
|
10662
10810
|
useEffect14(() => {
|
|
10663
10811
|
let mounted = true;
|
|
10812
|
+
const unsubscribeAttribution = attributionTracker.onCapture(() => {
|
|
10813
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10814
|
+
});
|
|
10664
10815
|
const bootstrap = async () => {
|
|
10665
10816
|
if (!PaywalloClient.isReady()) {
|
|
10666
10817
|
initLocalization({ detectDevice: true });
|
|
@@ -10671,11 +10822,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
10671
10822
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
10672
10823
|
return;
|
|
10673
10824
|
}
|
|
10674
|
-
if (!mounted) return;
|
|
10675
|
-
startSuperwallAutoBridge(configRef.current.debug);
|
|
10676
|
-
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10677
10825
|
}
|
|
10678
10826
|
if (!mounted) return;
|
|
10827
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
10828
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10679
10829
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10680
10830
|
setIsInitialized(true);
|
|
10681
10831
|
if (!autoPreloadTriggeredRef.current) {
|
|
@@ -10693,6 +10843,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10693
10843
|
void bootstrap();
|
|
10694
10844
|
return () => {
|
|
10695
10845
|
mounted = false;
|
|
10846
|
+
unsubscribeAttribution();
|
|
10847
|
+
stopSuperwallAutoBridge();
|
|
10848
|
+
cancelSuperwallAttributeRetry();
|
|
10696
10849
|
};
|
|
10697
10850
|
}, [preloadCampaign]);
|
|
10698
10851
|
const getPaywallConfig = useCallback20(async (p) => {
|