@virex-tech/paywallo-sdk 2.5.1 → 2.5.3
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 +41 -30
- package/dist/index.d.ts +41 -30
- package/dist/index.js +1091 -806
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +914 -629
- 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.3") {
|
|
2615
|
+
return "2.5.3";
|
|
2552
2616
|
}
|
|
2553
2617
|
} catch {
|
|
2554
2618
|
}
|
|
@@ -3167,99 +3231,236 @@ var init_notifications = __esm({
|
|
|
3167
3231
|
}
|
|
3168
3232
|
});
|
|
3169
3233
|
|
|
3170
|
-
// src/domains/
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
}
|
|
3175
|
-
var CLOSE_REASON_MAP;
|
|
3176
|
-
var init_paywallCloseReason = __esm({
|
|
3177
|
-
"src/domains/paywall/paywallCloseReason.ts"() {
|
|
3234
|
+
// src/domains/onboarding/OnboardingError.ts
|
|
3235
|
+
var OnboardingError, ONBOARDING_ERROR_CODES;
|
|
3236
|
+
var init_OnboardingError = __esm({
|
|
3237
|
+
"src/domains/onboarding/OnboardingError.ts"() {
|
|
3178
3238
|
"use strict";
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
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"
|
|
3184
3249
|
};
|
|
3185
3250
|
}
|
|
3186
3251
|
});
|
|
3187
3252
|
|
|
3188
|
-
// src/domains/
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
if (!api) return;
|
|
3193
|
-
const sessionId = PaywalloClient.getSessionId();
|
|
3194
|
-
const closedAt = /* @__PURE__ */ new Date();
|
|
3195
|
-
const durationS = Math.max(0, Math.round((closedAt.getTime() - opts.presentedAt) / 1e3));
|
|
3196
|
-
await api.trackEvent("paywall", PaywalloClient.getDistinctId(), {
|
|
3197
|
-
type: "closed",
|
|
3198
|
-
paywall_id: opts.paywallId,
|
|
3199
|
-
placement: opts.placement,
|
|
3200
|
-
closed_at: closedAt.toISOString(),
|
|
3201
|
-
duration_s: durationS,
|
|
3202
|
-
close_reason: toCanonicalReason(opts.reason),
|
|
3203
|
-
...opts.variantId && { variant_id: opts.variantId },
|
|
3204
|
-
...opts.variantKey && { variant_key: opts.variantKey },
|
|
3205
|
-
...opts.campaignId && { campaign_id: opts.campaignId },
|
|
3206
|
-
...typeof opts.scrollDepth === "number" && { scroll_depth: opts.scrollDepth },
|
|
3207
|
-
...sessionId && { sessionId }
|
|
3208
|
-
});
|
|
3209
|
-
} catch (err) {
|
|
3210
|
-
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] emitPaywallClosed failed", err);
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
var init_emitPaywallClosed = __esm({
|
|
3214
|
-
"src/domains/paywall/emitPaywallClosed.ts"() {
|
|
3215
|
-
"use strict";
|
|
3216
|
-
init_PaywalloClient();
|
|
3217
|
-
init_paywallCloseReason();
|
|
3218
|
-
}
|
|
3219
|
-
});
|
|
3220
|
-
|
|
3221
|
-
// src/domains/paywall/extractFirstPageBackground.ts
|
|
3222
|
-
function extractFirstPageBackground(craftData) {
|
|
3223
|
-
if (!craftData) return DEFAULT_BACKGROUND;
|
|
3224
|
-
try {
|
|
3225
|
-
const data = JSON.parse(craftData);
|
|
3226
|
-
const nodes = { ...data.nodes, ...data.sharedNodes };
|
|
3227
|
-
const rootId = data.rootId;
|
|
3228
|
-
if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
|
|
3229
|
-
const firstPageId = nodes[rootId].children?.[0];
|
|
3230
|
-
if (!firstPageId) return DEFAULT_BACKGROUND;
|
|
3231
|
-
const bg = nodes[firstPageId]?.props?.backgroundColor;
|
|
3232
|
-
return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
|
|
3233
|
-
} catch {
|
|
3234
|
-
return DEFAULT_BACKGROUND;
|
|
3235
|
-
}
|
|
3236
|
-
}
|
|
3237
|
-
function isColorDark(color) {
|
|
3238
|
-
const hex = color.replace("#", "").trim();
|
|
3239
|
-
const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
3240
|
-
if (normalized.length !== 6) return false;
|
|
3241
|
-
const r = parseInt(normalized.slice(0, 2), 16);
|
|
3242
|
-
const g = parseInt(normalized.slice(2, 4), 16);
|
|
3243
|
-
const b = parseInt(normalized.slice(4, 6), 16);
|
|
3244
|
-
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
|
|
3245
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
3246
|
-
return luminance < 0.5;
|
|
3247
|
-
}
|
|
3248
|
-
var DEFAULT_BACKGROUND;
|
|
3249
|
-
var init_extractFirstPageBackground = __esm({
|
|
3250
|
-
"src/domains/paywall/extractFirstPageBackground.ts"() {
|
|
3253
|
+
// src/domains/onboarding/OnboardingManager.ts
|
|
3254
|
+
var OnboardingManager, onboardingManager;
|
|
3255
|
+
var init_OnboardingManager = __esm({
|
|
3256
|
+
"src/domains/onboarding/OnboardingManager.ts"() {
|
|
3251
3257
|
"use strict";
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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();
|
|
3359
|
+
}
|
|
3360
|
+
});
|
|
3361
|
+
|
|
3362
|
+
// src/domains/onboarding/index.ts
|
|
3363
|
+
var init_onboarding = __esm({
|
|
3364
|
+
"src/domains/onboarding/index.ts"() {
|
|
3365
|
+
"use strict";
|
|
3366
|
+
init_OnboardingError();
|
|
3367
|
+
init_OnboardingManager();
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
|
|
3371
|
+
// src/domains/paywall/paywallCloseReason.ts
|
|
3372
|
+
function toCanonicalReason(reason) {
|
|
3373
|
+
if (reason in CLOSE_REASON_MAP) return CLOSE_REASON_MAP[reason];
|
|
3374
|
+
return reason;
|
|
3375
|
+
}
|
|
3376
|
+
var CLOSE_REASON_MAP;
|
|
3377
|
+
var init_paywallCloseReason = __esm({
|
|
3378
|
+
"src/domains/paywall/paywallCloseReason.ts"() {
|
|
3379
|
+
"use strict";
|
|
3380
|
+
CLOSE_REASON_MAP = {
|
|
3381
|
+
dismissed: "dismiss",
|
|
3382
|
+
purchased: "purchase",
|
|
3383
|
+
backgrounded: "dismiss",
|
|
3384
|
+
timeout: "timeout"
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
});
|
|
3388
|
+
|
|
3389
|
+
// src/domains/paywall/emitPaywallClosed.ts
|
|
3390
|
+
async function emitPaywallClosed(opts) {
|
|
3391
|
+
try {
|
|
3392
|
+
const api = PaywalloClient.getApiClient();
|
|
3393
|
+
if (!api) return;
|
|
3394
|
+
const sessionId = PaywalloClient.getSessionId();
|
|
3395
|
+
const closedAt = /* @__PURE__ */ new Date();
|
|
3396
|
+
const durationS = Math.max(0, Math.round((closedAt.getTime() - opts.presentedAt) / 1e3));
|
|
3397
|
+
await api.trackEvent("paywall", PaywalloClient.getDistinctId(), {
|
|
3398
|
+
type: "closed",
|
|
3399
|
+
paywall_id: opts.paywallId,
|
|
3400
|
+
placement: opts.placement,
|
|
3401
|
+
closed_at: closedAt.toISOString(),
|
|
3402
|
+
duration_s: durationS,
|
|
3403
|
+
close_reason: toCanonicalReason(opts.reason),
|
|
3404
|
+
...opts.variantId && { variant_id: opts.variantId },
|
|
3405
|
+
...opts.variantKey && { variant_key: opts.variantKey },
|
|
3406
|
+
...opts.campaignId && { campaign_id: opts.campaignId },
|
|
3407
|
+
...typeof opts.scrollDepth === "number" && { scroll_depth: opts.scrollDepth },
|
|
3408
|
+
...sessionId && { sessionId }
|
|
3409
|
+
});
|
|
3410
|
+
} catch (err) {
|
|
3411
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] emitPaywallClosed failed", err);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
var init_emitPaywallClosed = __esm({
|
|
3415
|
+
"src/domains/paywall/emitPaywallClosed.ts"() {
|
|
3416
|
+
"use strict";
|
|
3417
|
+
init_PaywalloClient();
|
|
3418
|
+
init_paywallCloseReason();
|
|
3419
|
+
}
|
|
3420
|
+
});
|
|
3421
|
+
|
|
3422
|
+
// src/domains/paywall/extractFirstPageBackground.ts
|
|
3423
|
+
function extractFirstPageBackground(craftData) {
|
|
3424
|
+
if (!craftData) return DEFAULT_BACKGROUND;
|
|
3425
|
+
try {
|
|
3426
|
+
const data = JSON.parse(craftData);
|
|
3427
|
+
const nodes = { ...data.nodes, ...data.sharedNodes };
|
|
3428
|
+
const rootId = data.rootId;
|
|
3429
|
+
if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
|
|
3430
|
+
const firstPageId = nodes[rootId].children?.[0];
|
|
3431
|
+
if (!firstPageId) return DEFAULT_BACKGROUND;
|
|
3432
|
+
const bg = nodes[firstPageId]?.props?.backgroundColor;
|
|
3433
|
+
return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
|
|
3434
|
+
} catch {
|
|
3435
|
+
return DEFAULT_BACKGROUND;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
function isColorDark(color) {
|
|
3439
|
+
const hex = color.replace("#", "").trim();
|
|
3440
|
+
const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
3441
|
+
if (normalized.length !== 6) return false;
|
|
3442
|
+
const r = parseInt(normalized.slice(0, 2), 16);
|
|
3443
|
+
const g = parseInt(normalized.slice(2, 4), 16);
|
|
3444
|
+
const b = parseInt(normalized.slice(4, 6), 16);
|
|
3445
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
|
|
3446
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
3447
|
+
return luminance < 0.5;
|
|
3448
|
+
}
|
|
3449
|
+
var DEFAULT_BACKGROUND;
|
|
3450
|
+
var init_extractFirstPageBackground = __esm({
|
|
3451
|
+
"src/domains/paywall/extractFirstPageBackground.ts"() {
|
|
3452
|
+
"use strict";
|
|
3453
|
+
DEFAULT_BACKGROUND = "#FFFFFF";
|
|
3454
|
+
}
|
|
3455
|
+
});
|
|
3456
|
+
|
|
3457
|
+
// src/domains/paywall/parseWebViewMessage.ts
|
|
3458
|
+
function deriveMessageId(message) {
|
|
3459
|
+
if (message.id) return message.id;
|
|
3460
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
3461
|
+
return `${message.type}:${payloadKey}`;
|
|
3462
|
+
}
|
|
3463
|
+
function parseWebViewMessage(raw) {
|
|
3263
3464
|
try {
|
|
3264
3465
|
const parsed = JSON.parse(raw);
|
|
3265
3466
|
if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
|
|
@@ -3905,17 +4106,292 @@ var init_PaywallWebView = __esm({
|
|
|
3905
4106
|
}
|
|
3906
4107
|
});
|
|
3907
4108
|
|
|
4109
|
+
// src/domains/iap/PurchaseError.ts
|
|
4110
|
+
function createPurchaseError(code, message) {
|
|
4111
|
+
return new PurchaseError(
|
|
4112
|
+
PURCHASE_ERROR_CODES[code],
|
|
4113
|
+
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4114
|
+
code === "USER_CANCELLED"
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4118
|
+
var init_PurchaseError = __esm({
|
|
4119
|
+
"src/domains/iap/PurchaseError.ts"() {
|
|
4120
|
+
"use strict";
|
|
4121
|
+
init_PaywalloError();
|
|
4122
|
+
PurchaseError = class extends PaywalloError {
|
|
4123
|
+
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4124
|
+
super("purchase", code, message);
|
|
4125
|
+
this.name = "PurchaseError";
|
|
4126
|
+
this.userCancelled = userCancelled;
|
|
4127
|
+
this.httpStatus = httpStatus;
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
PURCHASE_ERROR_CODES = {
|
|
4131
|
+
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4132
|
+
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4133
|
+
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4134
|
+
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4135
|
+
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4136
|
+
USER_CANCELLED: "USER_CANCELLED",
|
|
4137
|
+
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4138
|
+
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4139
|
+
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4140
|
+
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4141
|
+
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4142
|
+
};
|
|
4143
|
+
DEFAULT_MESSAGES = {
|
|
4144
|
+
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4145
|
+
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4146
|
+
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4147
|
+
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4148
|
+
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4149
|
+
USER_CANCELLED: "Purchase was cancelled.",
|
|
4150
|
+
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4151
|
+
STORE_ERROR: "Store error. Please try again later.",
|
|
4152
|
+
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4153
|
+
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4154
|
+
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
});
|
|
4158
|
+
|
|
4159
|
+
// src/domains/iap/NativeStoreKit.ts
|
|
4160
|
+
var NativeStoreKit_exports = {};
|
|
4161
|
+
__export(NativeStoreKit_exports, {
|
|
4162
|
+
NativeStoreKit: () => NativeStoreKit,
|
|
4163
|
+
nativeStoreKit: () => nativeStoreKit
|
|
4164
|
+
});
|
|
4165
|
+
import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform11 } from "react-native";
|
|
4166
|
+
function isAvailable3() {
|
|
4167
|
+
return PaywalloStoreKitNative != null;
|
|
4168
|
+
}
|
|
4169
|
+
function mapNativeProduct(native) {
|
|
4170
|
+
const product = {
|
|
4171
|
+
productId: native.productId,
|
|
4172
|
+
title: native.title,
|
|
4173
|
+
description: native.description,
|
|
4174
|
+
price: native.price,
|
|
4175
|
+
priceValue: native.priceValue,
|
|
4176
|
+
currency: native.currency ?? "USD",
|
|
4177
|
+
localizedPrice: native.localizedPrice,
|
|
4178
|
+
type: mapProductType(native.type),
|
|
4179
|
+
subscriptionPeriod: native.subscriptionPeriod,
|
|
4180
|
+
introductoryPrice: native.introductoryPrice,
|
|
4181
|
+
introductoryPriceValue: native.introductoryPriceValue,
|
|
4182
|
+
freeTrialPeriod: native.freeTrialPeriod
|
|
4183
|
+
};
|
|
4184
|
+
if (native.subscriptionPeriod) {
|
|
4185
|
+
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
4186
|
+
product.pricePerMonth = prices.perMonth;
|
|
4187
|
+
product.pricePerWeek = prices.perWeek;
|
|
4188
|
+
product.pricePerDay = prices.perDay;
|
|
4189
|
+
}
|
|
4190
|
+
return product;
|
|
4191
|
+
}
|
|
4192
|
+
function mapProductType(type) {
|
|
4193
|
+
switch (type) {
|
|
4194
|
+
case "subscription":
|
|
4195
|
+
case "autoRenewable":
|
|
4196
|
+
return "subscription";
|
|
4197
|
+
case "consumable":
|
|
4198
|
+
return "consumable";
|
|
4199
|
+
default:
|
|
4200
|
+
return "non_consumable";
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
function calculatePerPeriodPrices(priceValue, period) {
|
|
4204
|
+
const totalDays = periodToDays(period);
|
|
4205
|
+
if (totalDays <= 0) return {};
|
|
4206
|
+
const perDay = priceValue / totalDays;
|
|
4207
|
+
const perWeek = perDay * 7;
|
|
4208
|
+
const perMonth = perDay * 30;
|
|
4209
|
+
return {
|
|
4210
|
+
perDay: perDay.toFixed(2),
|
|
4211
|
+
perWeek: perWeek.toFixed(2),
|
|
4212
|
+
perMonth: perMonth.toFixed(2)
|
|
4213
|
+
};
|
|
4214
|
+
}
|
|
4215
|
+
function periodToDays(period) {
|
|
4216
|
+
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
4217
|
+
if (!match) return 0;
|
|
4218
|
+
const value = parseInt(match[1], 10);
|
|
4219
|
+
switch (match[2]) {
|
|
4220
|
+
case "D":
|
|
4221
|
+
return value;
|
|
4222
|
+
case "W":
|
|
4223
|
+
return value * 7;
|
|
4224
|
+
case "M":
|
|
4225
|
+
return value * 30;
|
|
4226
|
+
case "Y":
|
|
4227
|
+
return value * 365;
|
|
4228
|
+
default:
|
|
4229
|
+
return 0;
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
function mapNativePurchase(native) {
|
|
4233
|
+
return {
|
|
4234
|
+
productId: native.productId,
|
|
4235
|
+
transactionId: native.transactionId,
|
|
4236
|
+
transactionDate: native.transactionDate,
|
|
4237
|
+
receipt: native.receipt,
|
|
4238
|
+
platform: Platform11.OS
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
function normalizeTransactionUpdate(raw) {
|
|
4242
|
+
if (!raw || typeof raw !== "object") return null;
|
|
4243
|
+
const r = raw;
|
|
4244
|
+
const type = r.type;
|
|
4245
|
+
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
4246
|
+
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
4247
|
+
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
4248
|
+
if (!transactionId) return null;
|
|
4249
|
+
const update = {
|
|
4250
|
+
type,
|
|
4251
|
+
transactionId,
|
|
4252
|
+
productId: productId ?? ""
|
|
4253
|
+
};
|
|
4254
|
+
if (typeof r.amount === "number") update.amount = r.amount;
|
|
4255
|
+
if (typeof r.currency === "string") update.currency = r.currency;
|
|
4256
|
+
return update;
|
|
4257
|
+
}
|
|
4258
|
+
var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
4259
|
+
var init_NativeStoreKit = __esm({
|
|
4260
|
+
"src/domains/iap/NativeStoreKit.ts"() {
|
|
4261
|
+
"use strict";
|
|
4262
|
+
init_PaywalloClient();
|
|
4263
|
+
init_uuid();
|
|
4264
|
+
init_IdentityManager();
|
|
4265
|
+
init_PurchaseError();
|
|
4266
|
+
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
4267
|
+
PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
|
|
4268
|
+
nativeStoreKit = {
|
|
4269
|
+
isAvailable: isAvailable3,
|
|
4270
|
+
async getProducts(productIds) {
|
|
4271
|
+
if (!PaywalloStoreKitNative) {
|
|
4272
|
+
return [];
|
|
4273
|
+
}
|
|
4274
|
+
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
4275
|
+
return nativeProducts.map(mapNativeProduct);
|
|
4276
|
+
},
|
|
4277
|
+
async purchase(productId) {
|
|
4278
|
+
if (!PaywalloStoreKitNative) {
|
|
4279
|
+
throw new PurchaseError(
|
|
4280
|
+
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
4281
|
+
"StoreKit native module not available"
|
|
4282
|
+
);
|
|
4283
|
+
}
|
|
4284
|
+
const distinctId = identityManager.getDistinctId();
|
|
4285
|
+
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
4286
|
+
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
4287
|
+
if (distinctId && appAccountToken === null) {
|
|
4288
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
4289
|
+
}
|
|
4290
|
+
try {
|
|
4291
|
+
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
4292
|
+
if (!result || result.pending) {
|
|
4293
|
+
return null;
|
|
4294
|
+
}
|
|
4295
|
+
return mapNativePurchase(result);
|
|
4296
|
+
} catch (err) {
|
|
4297
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4298
|
+
if (message.toLowerCase().includes("cancel")) {
|
|
4299
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
4300
|
+
}
|
|
4301
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
4302
|
+
}
|
|
4303
|
+
},
|
|
4304
|
+
async finishTransaction(transactionId) {
|
|
4305
|
+
if (!PaywalloStoreKitNative) return;
|
|
4306
|
+
try {
|
|
4307
|
+
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
4308
|
+
} catch (err) {
|
|
4309
|
+
console.error("[Paywallo] finishTransaction native error", err);
|
|
4310
|
+
}
|
|
4311
|
+
},
|
|
4312
|
+
async getActiveTransactions() {
|
|
4313
|
+
if (!PaywalloStoreKitNative) return [];
|
|
4314
|
+
try {
|
|
4315
|
+
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
4316
|
+
return results.map(mapNativePurchase);
|
|
4317
|
+
} catch (err) {
|
|
4318
|
+
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
4319
|
+
return [];
|
|
4320
|
+
}
|
|
4321
|
+
},
|
|
4322
|
+
/**
|
|
4323
|
+
* Subscribes to native transaction update events (renewals, refunds,
|
|
4324
|
+
* cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
|
|
4325
|
+
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
4326
|
+
*
|
|
4327
|
+
* Returns a subscription with a `remove()` method. The caller is
|
|
4328
|
+
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
4329
|
+
*
|
|
4330
|
+
* Returns `null` when the native module isn't available (non-mobile env,
|
|
4331
|
+
* native module not linked) — callers should treat null as a no-op.
|
|
4332
|
+
*/
|
|
4333
|
+
subscribeToTransactionUpdates(listener) {
|
|
4334
|
+
if (!PaywalloStoreKitNative) return null;
|
|
4335
|
+
try {
|
|
4336
|
+
const EmitterCtor = NativeEventEmitter3;
|
|
4337
|
+
const emitter = new EmitterCtor(PaywalloStoreKitNative);
|
|
4338
|
+
const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
|
|
4339
|
+
const update = normalizeTransactionUpdate(payload);
|
|
4340
|
+
if (update) listener(update);
|
|
4341
|
+
});
|
|
4342
|
+
return { remove: () => sub.remove() };
|
|
4343
|
+
} catch (err) {
|
|
4344
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] subscribeToTransactionUpdates failed", err);
|
|
4345
|
+
return null;
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
};
|
|
4349
|
+
NativeStoreKit = nativeStoreKit;
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
4352
|
+
|
|
3908
4353
|
// src/domains/iap/IAPTransactionEmitter.ts
|
|
3909
4354
|
var IAPTransactionEmitter;
|
|
3910
4355
|
var init_IAPTransactionEmitter = __esm({
|
|
3911
4356
|
"src/domains/iap/IAPTransactionEmitter.ts"() {
|
|
3912
4357
|
"use strict";
|
|
3913
4358
|
init_PaywalloClient();
|
|
4359
|
+
init_NativeStoreKit();
|
|
3914
4360
|
IAPTransactionEmitter = class {
|
|
3915
4361
|
constructor(getProductFromCache, getApiClientFn, getDebug) {
|
|
3916
4362
|
this.getProductFromCache = getProductFromCache;
|
|
3917
4363
|
this.getApiClientFn = getApiClientFn;
|
|
3918
4364
|
this.getDebug = getDebug;
|
|
4365
|
+
this.transactionUpdateSubscription = null;
|
|
4366
|
+
}
|
|
4367
|
+
/**
|
|
4368
|
+
* Emits `transaction {type: "completed"}` after a server-validated purchase.
|
|
4369
|
+
* Fire-and-forget — a tracking failure must never poison the purchase flow.
|
|
4370
|
+
*/
|
|
4371
|
+
async emitTransactionCompleted(purchase, productId, options) {
|
|
4372
|
+
try {
|
|
4373
|
+
const apiClient = this.getApiClientFn();
|
|
4374
|
+
if (!apiClient) return;
|
|
4375
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4376
|
+
if (!distinctId) return;
|
|
4377
|
+
const product = this.getProductFromCache(productId);
|
|
4378
|
+
const properties = {
|
|
4379
|
+
type: "completed",
|
|
4380
|
+
tx_id: purchase.transactionId,
|
|
4381
|
+
product_id: purchase.productId
|
|
4382
|
+
};
|
|
4383
|
+
if (product?.priceValue !== void 0 && product.priceValue > 0) {
|
|
4384
|
+
properties.amount = product.priceValue;
|
|
4385
|
+
}
|
|
4386
|
+
if (product?.currency) {
|
|
4387
|
+
properties.currency = product.currency;
|
|
4388
|
+
}
|
|
4389
|
+
if (options?.paywallId) properties.paywall_id = options.paywallId;
|
|
4390
|
+
if (options?.variantId) properties.variant_id = options.variantId;
|
|
4391
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4392
|
+
} catch (err) {
|
|
4393
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
|
|
4394
|
+
}
|
|
3919
4395
|
}
|
|
3920
4396
|
/**
|
|
3921
4397
|
* Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
|
|
@@ -3940,19 +4416,69 @@ var init_IAPTransactionEmitter = __esm({
|
|
|
3940
4416
|
});
|
|
3941
4417
|
if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
|
|
3942
4418
|
} catch (err) {
|
|
3943
|
-
if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
|
|
4419
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
/**
|
|
4423
|
+
* Forwards a native transaction update to the EventBatcher.
|
|
4424
|
+
* Public so tests can exercise the wiring without a fake NativeEventEmitter.
|
|
4425
|
+
*/
|
|
4426
|
+
async emitTransactionUpdate(update) {
|
|
4427
|
+
try {
|
|
4428
|
+
const apiClient = this.getApiClientFn() ?? PaywalloClient.getApiClient();
|
|
4429
|
+
if (!apiClient) {
|
|
4430
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no apiClient)", update.type);
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4434
|
+
if (!distinctId) {
|
|
4435
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update dropped (no distinctId)", update.type);
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
const properties = {
|
|
4439
|
+
type: update.type,
|
|
4440
|
+
tx_id: update.transactionId,
|
|
4441
|
+
product_id: update.productId
|
|
4442
|
+
};
|
|
4443
|
+
if (update.type !== "canceled") {
|
|
4444
|
+
const cached2 = this.getProductFromCache(update.productId);
|
|
4445
|
+
const amount = update.amount ?? cached2?.priceValue;
|
|
4446
|
+
const currency = update.currency ?? cached2?.currency;
|
|
4447
|
+
if (typeof amount === "number" && amount > 0) properties.amount = amount;
|
|
4448
|
+
if (currency) properties.currency = currency;
|
|
4449
|
+
}
|
|
4450
|
+
await apiClient.trackEvent("transaction", distinctId, properties, void 0, { priority: "critical" });
|
|
4451
|
+
if (this.getDebug()) console.log(`[Paywallo IAP] emitted transaction {${update.type}}`, update.transactionId);
|
|
4452
|
+
} catch (err) {
|
|
4453
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update emission failed", err);
|
|
3944
4454
|
}
|
|
3945
4455
|
}
|
|
3946
4456
|
/**
|
|
3947
|
-
*
|
|
3948
|
-
* exclusively from server-side webhooks (Apple/Google/Superwall/RevenueCat).
|
|
4457
|
+
* Starts listening to native Transaction.updates. Idempotent.
|
|
3949
4458
|
*/
|
|
3950
4459
|
startListener() {
|
|
4460
|
+
if (this.transactionUpdateSubscription) return;
|
|
4461
|
+
this.transactionUpdateSubscription = nativeStoreKit.subscribeToTransactionUpdates(
|
|
4462
|
+
(update) => {
|
|
4463
|
+
void this.emitTransactionUpdate(update);
|
|
4464
|
+
}
|
|
4465
|
+
);
|
|
4466
|
+
if (this.getDebug() && this.transactionUpdateSubscription) {
|
|
4467
|
+
console.log("[Paywallo IAP] transaction update listener started");
|
|
4468
|
+
}
|
|
3951
4469
|
}
|
|
3952
4470
|
/**
|
|
3953
|
-
*
|
|
4471
|
+
* Removes the native listener. Safe to call multiple times.
|
|
3954
4472
|
*/
|
|
3955
4473
|
stopListener() {
|
|
4474
|
+
if (!this.transactionUpdateSubscription) return;
|
|
4475
|
+
try {
|
|
4476
|
+
this.transactionUpdateSubscription.remove();
|
|
4477
|
+
} catch (err) {
|
|
4478
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction listener removal failed", err);
|
|
4479
|
+
}
|
|
4480
|
+
this.transactionUpdateSubscription = null;
|
|
4481
|
+
if (this.getDebug()) console.log("[Paywallo IAP] transaction update listener stopped");
|
|
3956
4482
|
}
|
|
3957
4483
|
};
|
|
3958
4484
|
}
|
|
@@ -4841,58 +5367,8 @@ var init_queue = __esm({
|
|
|
4841
5367
|
}
|
|
4842
5368
|
});
|
|
4843
5369
|
|
|
4844
|
-
// src/domains/iap/PurchaseError.ts
|
|
4845
|
-
function createPurchaseError(code, message) {
|
|
4846
|
-
return new PurchaseError(
|
|
4847
|
-
PURCHASE_ERROR_CODES[code],
|
|
4848
|
-
message ?? DEFAULT_MESSAGES[code] ?? "Unknown purchase error",
|
|
4849
|
-
code === "USER_CANCELLED"
|
|
4850
|
-
);
|
|
4851
|
-
}
|
|
4852
|
-
var PurchaseError, PURCHASE_ERROR_CODES, DEFAULT_MESSAGES;
|
|
4853
|
-
var init_PurchaseError = __esm({
|
|
4854
|
-
"src/domains/iap/PurchaseError.ts"() {
|
|
4855
|
-
"use strict";
|
|
4856
|
-
init_PaywalloError();
|
|
4857
|
-
PurchaseError = class extends PaywalloError {
|
|
4858
|
-
constructor(code, message, userCancelled = false, httpStatus) {
|
|
4859
|
-
super("purchase", code, message);
|
|
4860
|
-
this.name = "PurchaseError";
|
|
4861
|
-
this.userCancelled = userCancelled;
|
|
4862
|
-
this.httpStatus = httpStatus;
|
|
4863
|
-
}
|
|
4864
|
-
};
|
|
4865
|
-
PURCHASE_ERROR_CODES = {
|
|
4866
|
-
NOT_INITIALIZED: "PURCHASE_NOT_INITIALIZED",
|
|
4867
|
-
PRODUCT_NOT_FOUND: "PRODUCT_NOT_FOUND",
|
|
4868
|
-
PURCHASE_FAILED: "PURCHASE_FAILED",
|
|
4869
|
-
RESTORE_FAILED: "RESTORE_FAILED",
|
|
4870
|
-
VALIDATION_FAILED: "VALIDATION_FAILED",
|
|
4871
|
-
USER_CANCELLED: "USER_CANCELLED",
|
|
4872
|
-
NETWORK_ERROR: "PURCHASE_NETWORK_ERROR",
|
|
4873
|
-
STORE_ERROR: "PURCHASE_STORE_ERROR",
|
|
4874
|
-
PENDING_PURCHASE: "PURCHASE_PENDING",
|
|
4875
|
-
DEFERRED_PURCHASE: "PURCHASE_DEFERRED",
|
|
4876
|
-
STORE_NOT_AVAILABLE: "PURCHASE_STORE_NOT_AVAILABLE"
|
|
4877
|
-
};
|
|
4878
|
-
DEFAULT_MESSAGES = {
|
|
4879
|
-
NOT_INITIALIZED: "Purchase controller not initialized. Call init() first.",
|
|
4880
|
-
PRODUCT_NOT_FOUND: "Product not found in store.",
|
|
4881
|
-
PURCHASE_FAILED: "Purchase failed. Please try again.",
|
|
4882
|
-
RESTORE_FAILED: "Failed to restore purchases. Please try again.",
|
|
4883
|
-
VALIDATION_FAILED: "Failed to validate purchase with server.",
|
|
4884
|
-
USER_CANCELLED: "Purchase was cancelled.",
|
|
4885
|
-
NETWORK_ERROR: "Network error. Please check your connection.",
|
|
4886
|
-
STORE_ERROR: "Store error. Please try again later.",
|
|
4887
|
-
PENDING_PURCHASE: "Purchase is pending approval.",
|
|
4888
|
-
DEFERRED_PURCHASE: "Purchase is deferred (e.g., parental approval required).",
|
|
4889
|
-
STORE_NOT_AVAILABLE: "In-app purchases are not available on this device."
|
|
4890
|
-
};
|
|
4891
|
-
}
|
|
4892
|
-
});
|
|
4893
|
-
|
|
4894
5370
|
// src/domains/iap/IAPValidator.ts
|
|
4895
|
-
import { Platform as
|
|
5371
|
+
import { Platform as Platform12 } from "react-native";
|
|
4896
5372
|
var IAPValidator;
|
|
4897
5373
|
var init_IAPValidator = __esm({
|
|
4898
5374
|
"src/domains/iap/IAPValidator.ts"() {
|
|
@@ -4922,7 +5398,7 @@ var init_IAPValidator = __esm({
|
|
|
4922
5398
|
async validateWithServer(purchase, productId, options) {
|
|
4923
5399
|
const apiClient = this.getApiClientFn();
|
|
4924
5400
|
const product = this.getProductFromCache(productId);
|
|
4925
|
-
const platform =
|
|
5401
|
+
const platform = Platform12.OS;
|
|
4926
5402
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4927
5403
|
return this.validateWithRetry(
|
|
4928
5404
|
() => apiClient.validatePurchase(
|
|
@@ -4957,7 +5433,7 @@ var init_IAPValidator = __esm({
|
|
|
4957
5433
|
try {
|
|
4958
5434
|
const apiClient = this.getApiClientFn();
|
|
4959
5435
|
const product = this.getProductFromCache(productId);
|
|
4960
|
-
const platform =
|
|
5436
|
+
const platform = Platform12.OS;
|
|
4961
5437
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4962
5438
|
const appKey = apiClient.getAppKey();
|
|
4963
5439
|
const body = {
|
|
@@ -4976,210 +5452,16 @@ var init_IAPValidator = __esm({
|
|
|
4976
5452
|
"POST",
|
|
4977
5453
|
"/sdk/purchases/validate",
|
|
4978
5454
|
body,
|
|
4979
|
-
{ "X-App-Key": appKey },
|
|
4980
|
-
`validate:${purchase.transactionId}`,
|
|
4981
|
-
"critical"
|
|
4982
|
-
);
|
|
4983
|
-
if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
|
|
4984
|
-
} catch (err) {
|
|
4985
|
-
console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
|
|
4986
|
-
}
|
|
4987
|
-
}
|
|
4988
|
-
};
|
|
4989
|
-
}
|
|
4990
|
-
});
|
|
4991
|
-
|
|
4992
|
-
// src/domains/iap/NativeStoreKit.ts
|
|
4993
|
-
var NativeStoreKit_exports = {};
|
|
4994
|
-
__export(NativeStoreKit_exports, {
|
|
4995
|
-
NativeStoreKit: () => NativeStoreKit,
|
|
4996
|
-
nativeStoreKit: () => nativeStoreKit
|
|
4997
|
-
});
|
|
4998
|
-
import { NativeEventEmitter as NativeEventEmitter3, NativeModules as NativeModules6, Platform as Platform12 } from "react-native";
|
|
4999
|
-
function isAvailable3() {
|
|
5000
|
-
return PaywalloStoreKitNative != null;
|
|
5001
|
-
}
|
|
5002
|
-
function mapNativeProduct(native) {
|
|
5003
|
-
const product = {
|
|
5004
|
-
productId: native.productId,
|
|
5005
|
-
title: native.title,
|
|
5006
|
-
description: native.description,
|
|
5007
|
-
price: native.price,
|
|
5008
|
-
priceValue: native.priceValue,
|
|
5009
|
-
currency: native.currency ?? "USD",
|
|
5010
|
-
localizedPrice: native.localizedPrice,
|
|
5011
|
-
type: mapProductType(native.type),
|
|
5012
|
-
subscriptionPeriod: native.subscriptionPeriod,
|
|
5013
|
-
introductoryPrice: native.introductoryPrice,
|
|
5014
|
-
introductoryPriceValue: native.introductoryPriceValue,
|
|
5015
|
-
freeTrialPeriod: native.freeTrialPeriod
|
|
5016
|
-
};
|
|
5017
|
-
if (native.subscriptionPeriod) {
|
|
5018
|
-
const prices = calculatePerPeriodPrices(native.priceValue, native.subscriptionPeriod);
|
|
5019
|
-
product.pricePerMonth = prices.perMonth;
|
|
5020
|
-
product.pricePerWeek = prices.perWeek;
|
|
5021
|
-
product.pricePerDay = prices.perDay;
|
|
5022
|
-
}
|
|
5023
|
-
return product;
|
|
5024
|
-
}
|
|
5025
|
-
function mapProductType(type) {
|
|
5026
|
-
switch (type) {
|
|
5027
|
-
case "subscription":
|
|
5028
|
-
case "autoRenewable":
|
|
5029
|
-
return "subscription";
|
|
5030
|
-
case "consumable":
|
|
5031
|
-
return "consumable";
|
|
5032
|
-
default:
|
|
5033
|
-
return "non_consumable";
|
|
5034
|
-
}
|
|
5035
|
-
}
|
|
5036
|
-
function calculatePerPeriodPrices(priceValue, period) {
|
|
5037
|
-
const totalDays = periodToDays(period);
|
|
5038
|
-
if (totalDays <= 0) return {};
|
|
5039
|
-
const perDay = priceValue / totalDays;
|
|
5040
|
-
const perWeek = perDay * 7;
|
|
5041
|
-
const perMonth = perDay * 30;
|
|
5042
|
-
return {
|
|
5043
|
-
perDay: perDay.toFixed(2),
|
|
5044
|
-
perWeek: perWeek.toFixed(2),
|
|
5045
|
-
perMonth: perMonth.toFixed(2)
|
|
5046
|
-
};
|
|
5047
|
-
}
|
|
5048
|
-
function periodToDays(period) {
|
|
5049
|
-
const match = period.match(/^P(\d+)([DWMY])$/);
|
|
5050
|
-
if (!match) return 0;
|
|
5051
|
-
const value = parseInt(match[1], 10);
|
|
5052
|
-
switch (match[2]) {
|
|
5053
|
-
case "D":
|
|
5054
|
-
return value;
|
|
5055
|
-
case "W":
|
|
5056
|
-
return value * 7;
|
|
5057
|
-
case "M":
|
|
5058
|
-
return value * 30;
|
|
5059
|
-
case "Y":
|
|
5060
|
-
return value * 365;
|
|
5061
|
-
default:
|
|
5062
|
-
return 0;
|
|
5063
|
-
}
|
|
5064
|
-
}
|
|
5065
|
-
function mapNativePurchase(native) {
|
|
5066
|
-
return {
|
|
5067
|
-
productId: native.productId,
|
|
5068
|
-
transactionId: native.transactionId,
|
|
5069
|
-
transactionDate: native.transactionDate,
|
|
5070
|
-
receipt: native.receipt,
|
|
5071
|
-
platform: Platform12.OS
|
|
5072
|
-
};
|
|
5073
|
-
}
|
|
5074
|
-
function normalizeTransactionUpdate(raw) {
|
|
5075
|
-
if (!raw || typeof raw !== "object") return null;
|
|
5076
|
-
const r = raw;
|
|
5077
|
-
const type = r.type;
|
|
5078
|
-
if (type !== "renewed" && type !== "refunded" && type !== "canceled") return null;
|
|
5079
|
-
const transactionId = typeof r.transactionId === "string" ? r.transactionId : null;
|
|
5080
|
-
const productId = typeof r.productId === "string" ? r.productId : null;
|
|
5081
|
-
if (!transactionId) return null;
|
|
5082
|
-
const update = {
|
|
5083
|
-
type,
|
|
5084
|
-
transactionId,
|
|
5085
|
-
productId: productId ?? ""
|
|
5086
|
-
};
|
|
5087
|
-
if (typeof r.amount === "number") update.amount = r.amount;
|
|
5088
|
-
if (typeof r.currency === "string") update.currency = r.currency;
|
|
5089
|
-
return update;
|
|
5090
|
-
}
|
|
5091
|
-
var TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
|
|
5092
|
-
var init_NativeStoreKit = __esm({
|
|
5093
|
-
"src/domains/iap/NativeStoreKit.ts"() {
|
|
5094
|
-
"use strict";
|
|
5095
|
-
init_PaywalloClient();
|
|
5096
|
-
init_uuid();
|
|
5097
|
-
init_IdentityManager();
|
|
5098
|
-
init_PurchaseError();
|
|
5099
|
-
TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
|
|
5100
|
-
PaywalloStoreKitNative = NativeModules6.PaywalloStoreKit ?? null;
|
|
5101
|
-
nativeStoreKit = {
|
|
5102
|
-
isAvailable: isAvailable3,
|
|
5103
|
-
async getProducts(productIds) {
|
|
5104
|
-
if (!PaywalloStoreKitNative) {
|
|
5105
|
-
return [];
|
|
5106
|
-
}
|
|
5107
|
-
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
5108
|
-
return nativeProducts.map(mapNativeProduct);
|
|
5109
|
-
},
|
|
5110
|
-
async purchase(productId) {
|
|
5111
|
-
if (!PaywalloStoreKitNative) {
|
|
5112
|
-
throw new PurchaseError(
|
|
5113
|
-
PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE,
|
|
5114
|
-
"StoreKit native module not available"
|
|
5115
|
-
);
|
|
5116
|
-
}
|
|
5117
|
-
const distinctId = identityManager.getDistinctId();
|
|
5118
|
-
const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
|
|
5119
|
-
const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
|
|
5120
|
-
if (distinctId && appAccountToken === null) {
|
|
5121
|
-
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
|
|
5122
|
-
}
|
|
5123
|
-
try {
|
|
5124
|
-
const result = await PaywalloStoreKitNative.purchase(productId, appAccountToken);
|
|
5125
|
-
if (!result || result.pending) {
|
|
5126
|
-
return null;
|
|
5127
|
-
}
|
|
5128
|
-
return mapNativePurchase(result);
|
|
5129
|
-
} catch (err) {
|
|
5130
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5131
|
-
if (message.toLowerCase().includes("cancel")) {
|
|
5132
|
-
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
5133
|
-
}
|
|
5134
|
-
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
5135
|
-
}
|
|
5136
|
-
},
|
|
5137
|
-
async finishTransaction(transactionId) {
|
|
5138
|
-
if (!PaywalloStoreKitNative) return;
|
|
5139
|
-
try {
|
|
5140
|
-
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
5141
|
-
} catch (err) {
|
|
5142
|
-
console.error("[Paywallo] finishTransaction native error", err);
|
|
5143
|
-
}
|
|
5144
|
-
},
|
|
5145
|
-
async getActiveTransactions() {
|
|
5146
|
-
if (!PaywalloStoreKitNative) return [];
|
|
5147
|
-
try {
|
|
5148
|
-
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
5149
|
-
return results.map(mapNativePurchase);
|
|
5150
|
-
} catch (err) {
|
|
5151
|
-
console.error("[Paywallo] getActiveTransactions failed", err);
|
|
5152
|
-
return [];
|
|
5153
|
-
}
|
|
5154
|
-
},
|
|
5155
|
-
/**
|
|
5156
|
-
* Subscribes to native transaction update events (renewals, refunds,
|
|
5157
|
-
* cancellations) from StoreKit 2 (`Transaction.updates`) on iOS or
|
|
5158
|
-
* `PurchasesUpdatedListener` + refund diff on Android.
|
|
5159
|
-
*
|
|
5160
|
-
* Returns a subscription with a `remove()` method. The caller is
|
|
5161
|
-
* responsible for cleanup (typically on `Paywallo.reset()`).
|
|
5162
|
-
*
|
|
5163
|
-
* Returns `null` when the native module isn't available (non-mobile env,
|
|
5164
|
-
* native module not linked) — callers should treat null as a no-op.
|
|
5165
|
-
*/
|
|
5166
|
-
subscribeToTransactionUpdates(listener) {
|
|
5167
|
-
if (!PaywalloStoreKitNative) return null;
|
|
5168
|
-
try {
|
|
5169
|
-
const EmitterCtor = NativeEventEmitter3;
|
|
5170
|
-
const emitter = new EmitterCtor(PaywalloStoreKitNative);
|
|
5171
|
-
const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
|
|
5172
|
-
const update = normalizeTransactionUpdate(payload);
|
|
5173
|
-
if (update) listener(update);
|
|
5174
|
-
});
|
|
5175
|
-
return { remove: () => sub.remove() };
|
|
5455
|
+
{ "X-App-Key": appKey },
|
|
5456
|
+
`validate:${purchase.transactionId}`,
|
|
5457
|
+
"critical"
|
|
5458
|
+
);
|
|
5459
|
+
if (this.getDebug()) console.log("[Paywallo PURCHASE] validation retry enqueued", purchase.transactionId);
|
|
5176
5460
|
} catch (err) {
|
|
5177
|
-
|
|
5178
|
-
return null;
|
|
5461
|
+
console.error("[Paywallo PURCHASE] failed to enqueue validation retry", err);
|
|
5179
5462
|
}
|
|
5180
5463
|
}
|
|
5181
5464
|
};
|
|
5182
|
-
NativeStoreKit = nativeStoreKit;
|
|
5183
5465
|
}
|
|
5184
5466
|
});
|
|
5185
5467
|
|
|
@@ -5283,8 +5565,8 @@ var init_IAPService = __esm({
|
|
|
5283
5565
|
stopTransactionListener() {
|
|
5284
5566
|
this.emitter.stopListener();
|
|
5285
5567
|
}
|
|
5286
|
-
|
|
5287
|
-
|
|
5568
|
+
async emitTransactionUpdate(update) {
|
|
5569
|
+
return this.emitter.emitTransactionUpdate(update);
|
|
5288
5570
|
}
|
|
5289
5571
|
async purchase(productId, options) {
|
|
5290
5572
|
if (!nativeStoreKit.isAvailable()) {
|
|
@@ -5313,6 +5595,10 @@ var init_IAPService = __esm({
|
|
|
5313
5595
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
5314
5596
|
await this.finishTransaction(purchase.transactionId);
|
|
5315
5597
|
if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
5598
|
+
await this.emitter.emitTransactionCompleted(purchase, productId, {
|
|
5599
|
+
paywallId: options?.paywallId,
|
|
5600
|
+
variantId: options?.variantId
|
|
5601
|
+
});
|
|
5316
5602
|
}).catch(async (err) => {
|
|
5317
5603
|
const kind = this.validator.classifyValidationError(err);
|
|
5318
5604
|
if (kind === "server4xx") {
|
|
@@ -6413,143 +6699,6 @@ var init_session = __esm({
|
|
|
6413
6699
|
}
|
|
6414
6700
|
});
|
|
6415
6701
|
|
|
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
6702
|
// src/domains/analytics/AnalyticsError.ts
|
|
6554
6703
|
var AnalyticsError, ANALYTICS_ERROR_CODES;
|
|
6555
6704
|
var init_AnalyticsError = __esm({
|
|
@@ -6622,7 +6771,21 @@ var init_schemas = __esm({
|
|
|
6622
6771
|
// `closed`-specific
|
|
6623
6772
|
closed_at: z.string().optional(),
|
|
6624
6773
|
duration_s: z.number().nonnegative().optional(),
|
|
6625
|
-
|
|
6774
|
+
// Two emitter dialects coexist: the canonical emitters
|
|
6775
|
+
// (`emitPaywallClosed`/heartbeat) send "dismiss"/"cta"/"purchase"/"error"/
|
|
6776
|
+
// "timeout"; the SuperwallAutoBridge forwards the server-enum values
|
|
6777
|
+
// "purchased"/"dismissed"/"restored" straight through. The server accepts
|
|
6778
|
+
// both — the schema must too, or every bridge close fails debug validation.
|
|
6779
|
+
close_reason: z.enum([
|
|
6780
|
+
"dismiss",
|
|
6781
|
+
"cta",
|
|
6782
|
+
"purchase",
|
|
6783
|
+
"error",
|
|
6784
|
+
"timeout",
|
|
6785
|
+
"purchased",
|
|
6786
|
+
"dismissed",
|
|
6787
|
+
"restored"
|
|
6788
|
+
]).optional(),
|
|
6626
6789
|
scroll_depth: z.number().min(0).max(1).optional(),
|
|
6627
6790
|
// `purchased`-specific (mínimo — transaction canônica leva detalhes)
|
|
6628
6791
|
product_id: z.string().optional()
|
|
@@ -6630,21 +6793,33 @@ var init_schemas = __esm({
|
|
|
6630
6793
|
transactionEventSchema = z.object({
|
|
6631
6794
|
type: z.enum([
|
|
6632
6795
|
"completed",
|
|
6796
|
+
"trial_started",
|
|
6633
6797
|
"failed",
|
|
6634
6798
|
"refunded",
|
|
6635
6799
|
"renewed",
|
|
6636
6800
|
"canceled",
|
|
6637
6801
|
"expired"
|
|
6638
6802
|
]),
|
|
6639
|
-
|
|
6803
|
+
// Server contract key is `transaction_id` (what IngestTranslator reads and
|
|
6804
|
+
// the SuperwallAutoBridge emits); `tx_id` is the legacy key still emitted
|
|
6805
|
+
// by IAPTransactionEmitter — the server accepts both. At least one is
|
|
6806
|
+
// required (enforced by the refine below).
|
|
6807
|
+
transaction_id: z.string().min(1).optional(),
|
|
6808
|
+
tx_id: z.string().min(1).optional(),
|
|
6640
6809
|
amount: z.number().optional(),
|
|
6810
|
+
// Full (post-trial) price carried on trial_started for analytics, since
|
|
6811
|
+
// `amount` is 0 for a free trial.
|
|
6812
|
+
full_price: z.number().nonnegative().optional(),
|
|
6641
6813
|
currency: z.string().length(3).optional(),
|
|
6642
6814
|
product_id: z.string().optional(),
|
|
6643
6815
|
subscription_id: z.string().optional(),
|
|
6644
6816
|
paywall_id: z.string().optional(),
|
|
6645
6817
|
variant_id: z.string().optional(),
|
|
6646
6818
|
reason: z.string().optional()
|
|
6647
|
-
}).strict()
|
|
6819
|
+
}).strict().refine((v) => v.transaction_id !== void 0 || v.tx_id !== void 0, {
|
|
6820
|
+
message: "transaction requires transaction_id (or legacy tx_id)",
|
|
6821
|
+
path: ["transaction_id"]
|
|
6822
|
+
});
|
|
6648
6823
|
onboardingEventSchema = z.object({
|
|
6649
6824
|
type: z.enum(["step", "complete", "drop"]),
|
|
6650
6825
|
step_index: z.number().int().nonnegative().optional(),
|
|
@@ -7474,8 +7649,13 @@ var init_ApiClientFlags = __esm({
|
|
|
7474
7649
|
});
|
|
7475
7650
|
|
|
7476
7651
|
// src/core/ApiClientQueue.ts
|
|
7652
|
+
function isServerError(res) {
|
|
7653
|
+
if (typeof res !== "object" || res === null) return false;
|
|
7654
|
+
const r = res;
|
|
7655
|
+
return r.ok === false && typeof r.status === "number" && r.status >= 500;
|
|
7656
|
+
}
|
|
7477
7657
|
async function postWithQueue(deps, url, payload, label, priority) {
|
|
7478
|
-
|
|
7658
|
+
const enqueue = async () => {
|
|
7479
7659
|
await offlineQueue.enqueue(
|
|
7480
7660
|
"POST",
|
|
7481
7661
|
url,
|
|
@@ -7484,22 +7664,22 @@ async function postWithQueue(deps, url, payload, label, priority) {
|
|
|
7484
7664
|
void 0,
|
|
7485
7665
|
priority
|
|
7486
7666
|
);
|
|
7667
|
+
};
|
|
7668
|
+
if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
|
|
7669
|
+
await enqueue();
|
|
7487
7670
|
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Queued for offline:", label, priority ?? "normal");
|
|
7488
7671
|
return;
|
|
7489
7672
|
}
|
|
7490
7673
|
try {
|
|
7491
|
-
await deps.post(url, payload);
|
|
7674
|
+
const res = await deps.post(url, payload);
|
|
7675
|
+
if (isServerError(res) && deps.isOfflineQueueEnabled()) {
|
|
7676
|
+
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Server error, queued for retry:", label, priority ?? "normal");
|
|
7677
|
+
await enqueue();
|
|
7678
|
+
}
|
|
7492
7679
|
} catch (error) {
|
|
7493
7680
|
if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
|
|
7494
7681
|
if (deps.isOfflineQueueEnabled()) {
|
|
7495
|
-
await
|
|
7496
|
-
"POST",
|
|
7497
|
-
url,
|
|
7498
|
-
payload,
|
|
7499
|
-
{ "X-App-Key": deps.getAppKey() },
|
|
7500
|
-
void 0,
|
|
7501
|
-
priority
|
|
7502
|
-
);
|
|
7682
|
+
await enqueue();
|
|
7503
7683
|
return;
|
|
7504
7684
|
}
|
|
7505
7685
|
throw error;
|
|
@@ -8904,11 +9084,11 @@ var init_PaywalloClient = __esm({
|
|
|
8904
9084
|
init_campaign();
|
|
8905
9085
|
init_identity();
|
|
8906
9086
|
init_notifications();
|
|
9087
|
+
init_onboarding();
|
|
8907
9088
|
init_paywall();
|
|
8908
9089
|
init_plan();
|
|
8909
9090
|
init_session();
|
|
8910
9091
|
init_ClientError();
|
|
8911
|
-
init_onboarding();
|
|
8912
9092
|
init_network();
|
|
8913
9093
|
init_PaywalloAnalytics();
|
|
8914
9094
|
init_PaywalloFlags();
|
|
@@ -9536,8 +9716,41 @@ function usePlanPurchase() {
|
|
|
9536
9716
|
};
|
|
9537
9717
|
}
|
|
9538
9718
|
|
|
9719
|
+
// src/hooks/usePaywalloScreenTracking.ts
|
|
9720
|
+
init_PaywalloClient();
|
|
9721
|
+
import { useCallback as useCallback11, useRef as useRef8 } from "react";
|
|
9722
|
+
function isNavState(value) {
|
|
9723
|
+
return typeof value === "object" && value !== null && "routes" in value && "index" in value && Array.isArray(value.routes) && typeof value.index === "number";
|
|
9724
|
+
}
|
|
9725
|
+
function getActiveRouteName(state) {
|
|
9726
|
+
try {
|
|
9727
|
+
if (!isNavState(state)) return null;
|
|
9728
|
+
const route = state.routes[state.index];
|
|
9729
|
+
if (!route) return null;
|
|
9730
|
+
if (isNavState(route.state)) {
|
|
9731
|
+
return getActiveRouteName(route.state);
|
|
9732
|
+
}
|
|
9733
|
+
return typeof route.name === "string" ? route.name : null;
|
|
9734
|
+
} catch {
|
|
9735
|
+
return null;
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
function usePaywalloScreenTracking() {
|
|
9739
|
+
const lastScreenRef = useRef8(null);
|
|
9740
|
+
return useCallback11((state) => {
|
|
9741
|
+
if (!PaywalloClient.isReady()) return;
|
|
9742
|
+
const screenName = getActiveRouteName(state);
|
|
9743
|
+
if (!screenName || screenName === lastScreenRef.current) return;
|
|
9744
|
+
lastScreenRef.current = screenName;
|
|
9745
|
+
void PaywalloClient.track("screen_view", {
|
|
9746
|
+
properties: { screen_name: screenName }
|
|
9747
|
+
}).catch(() => {
|
|
9748
|
+
});
|
|
9749
|
+
}, []);
|
|
9750
|
+
}
|
|
9751
|
+
|
|
9539
9752
|
// src/hooks/usePlans.ts
|
|
9540
|
-
import { useCallback as
|
|
9753
|
+
import { useCallback as useCallback12, useEffect as useEffect10, useState as useState10 } from "react";
|
|
9541
9754
|
async function getPlanService() {
|
|
9542
9755
|
try {
|
|
9543
9756
|
const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
|
|
@@ -9551,7 +9764,7 @@ function usePlans() {
|
|
|
9551
9764
|
const [currentPlan, setCurrentPlan] = useState10(null);
|
|
9552
9765
|
const [isLoading, setIsLoading] = useState10(true);
|
|
9553
9766
|
const [error, setError] = useState10(null);
|
|
9554
|
-
const fetchPlans =
|
|
9767
|
+
const fetchPlans = useCallback12(async (forceRefresh = false) => {
|
|
9555
9768
|
setIsLoading(true);
|
|
9556
9769
|
setError(null);
|
|
9557
9770
|
try {
|
|
@@ -9572,7 +9785,7 @@ function usePlans() {
|
|
|
9572
9785
|
setIsLoading(false);
|
|
9573
9786
|
}
|
|
9574
9787
|
}, []);
|
|
9575
|
-
const refresh =
|
|
9788
|
+
const refresh = useCallback12(() => {
|
|
9576
9789
|
return fetchPlans(true);
|
|
9577
9790
|
}, [fetchPlans]);
|
|
9578
9791
|
useEffect10(() => {
|
|
@@ -9582,7 +9795,7 @@ function usePlans() {
|
|
|
9582
9795
|
}
|
|
9583
9796
|
|
|
9584
9797
|
// src/hooks/useProducts.ts
|
|
9585
|
-
import { useCallback as
|
|
9798
|
+
import { useCallback as useCallback13, useEffect as useEffect11, useState as useState11 } from "react";
|
|
9586
9799
|
|
|
9587
9800
|
// src/utils/productMappers.ts
|
|
9588
9801
|
function mapToIAPProduct(product) {
|
|
@@ -9625,7 +9838,7 @@ function useProducts(initialProductIds) {
|
|
|
9625
9838
|
const [formattedProducts, setFormattedProducts] = useState11([]);
|
|
9626
9839
|
const [isLoading, setIsLoading] = useState11(false);
|
|
9627
9840
|
const [error, setError] = useState11(null);
|
|
9628
|
-
const loadProducts =
|
|
9841
|
+
const loadProducts = useCallback13(async (productIds) => {
|
|
9629
9842
|
setIsLoading(true);
|
|
9630
9843
|
setError(null);
|
|
9631
9844
|
try {
|
|
@@ -9644,19 +9857,19 @@ function useProducts(initialProductIds) {
|
|
|
9644
9857
|
void loadProducts(initialProductIds);
|
|
9645
9858
|
}
|
|
9646
9859
|
}, []);
|
|
9647
|
-
const getProduct =
|
|
9860
|
+
const getProduct = useCallback13(
|
|
9648
9861
|
(productId) => {
|
|
9649
9862
|
return products.find((p) => p.productId === productId);
|
|
9650
9863
|
},
|
|
9651
9864
|
[products]
|
|
9652
9865
|
);
|
|
9653
|
-
const getFormattedProduct =
|
|
9866
|
+
const getFormattedProduct = useCallback13(
|
|
9654
9867
|
(productId) => {
|
|
9655
9868
|
return formattedProducts.find((p) => p.productId === productId);
|
|
9656
9869
|
},
|
|
9657
9870
|
[formattedProducts]
|
|
9658
9871
|
);
|
|
9659
|
-
const getPriceInfo =
|
|
9872
|
+
const getPriceInfo = useCallback13(
|
|
9660
9873
|
(productId) => {
|
|
9661
9874
|
const p = formattedProducts.find((fp) => fp.productId === productId);
|
|
9662
9875
|
if (!p) return void 0;
|
|
@@ -9685,14 +9898,14 @@ function useProducts(initialProductIds) {
|
|
|
9685
9898
|
}
|
|
9686
9899
|
|
|
9687
9900
|
// src/hooks/usePurchase.ts
|
|
9688
|
-
import { useCallback as
|
|
9901
|
+
import { useCallback as useCallback14, useState as useState12 } from "react";
|
|
9689
9902
|
function toIAPPurchase(p) {
|
|
9690
9903
|
return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
|
|
9691
9904
|
}
|
|
9692
9905
|
function usePurchase() {
|
|
9693
9906
|
const [state, setState] = useState12("idle");
|
|
9694
9907
|
const [error, setError] = useState12(null);
|
|
9695
|
-
const purchase =
|
|
9908
|
+
const purchase = useCallback14(async (productId) => {
|
|
9696
9909
|
setState("purchasing");
|
|
9697
9910
|
setError(null);
|
|
9698
9911
|
try {
|
|
@@ -9720,7 +9933,7 @@ function usePurchase() {
|
|
|
9720
9933
|
return { status: "failed", error: purchaseError };
|
|
9721
9934
|
}
|
|
9722
9935
|
}, []);
|
|
9723
|
-
const restore =
|
|
9936
|
+
const restore = useCallback14(async () => {
|
|
9724
9937
|
setState("restoring");
|
|
9725
9938
|
setError(null);
|
|
9726
9939
|
try {
|
|
@@ -9739,14 +9952,14 @@ function usePurchase() {
|
|
|
9739
9952
|
}
|
|
9740
9953
|
|
|
9741
9954
|
// src/hooks/useSubscription.ts
|
|
9742
|
-
import { useCallback as
|
|
9955
|
+
import { useCallback as useCallback15, useEffect as useEffect12, useState as useState13 } from "react";
|
|
9743
9956
|
init_SubscriptionManager();
|
|
9744
9957
|
var EMPTY_ENTITLEMENTS = [];
|
|
9745
9958
|
function useSubscription() {
|
|
9746
9959
|
const [status, setStatus] = useState13(null);
|
|
9747
9960
|
const [isLoading, setIsLoading] = useState13(true);
|
|
9748
9961
|
const [error, setError] = useState13(null);
|
|
9749
|
-
const refresh =
|
|
9962
|
+
const refresh = useCallback15(async () => {
|
|
9750
9963
|
setIsLoading(true);
|
|
9751
9964
|
setError(null);
|
|
9752
9965
|
try {
|
|
@@ -9759,7 +9972,7 @@ function useSubscription() {
|
|
|
9759
9972
|
setIsLoading(false);
|
|
9760
9973
|
}
|
|
9761
9974
|
}, []);
|
|
9762
|
-
const restore =
|
|
9975
|
+
const restore = useCallback15(async () => {
|
|
9763
9976
|
setIsLoading(true);
|
|
9764
9977
|
setError(null);
|
|
9765
9978
|
try {
|
|
@@ -9799,39 +10012,6 @@ function useSubscription() {
|
|
|
9799
10012
|
};
|
|
9800
10013
|
}
|
|
9801
10014
|
|
|
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
10015
|
// src/PaywalloProvider.tsx
|
|
9836
10016
|
import { useCallback as useCallback20, useEffect as useEffect14, useLayoutEffect, useMemo as useMemo3, useRef as useRef12, useState as useState17 } from "react";
|
|
9837
10017
|
import { Animated as Animated4, Dimensions as Dimensions5, Easing as Easing2, StyleSheet as StyleSheet5 } from "react-native";
|
|
@@ -9839,6 +10019,7 @@ init_PaywalloClient();
|
|
|
9839
10019
|
init_NativeStorage();
|
|
9840
10020
|
init_SecureStorage();
|
|
9841
10021
|
init_campaign();
|
|
10022
|
+
init_identity();
|
|
9842
10023
|
init_paywall();
|
|
9843
10024
|
init_paywall();
|
|
9844
10025
|
init_paywall();
|
|
@@ -9850,7 +10031,9 @@ init_paywall();
|
|
|
9850
10031
|
init_identity();
|
|
9851
10032
|
var CONFIG_POLL_INTERVAL_MS = 250;
|
|
9852
10033
|
var CONFIG_POLL_MAX_ATTEMPTS = 40;
|
|
10034
|
+
var CONFIG_RETRY_DELAYS_MS = [3e4, 6e4, 12e4];
|
|
9853
10035
|
var lastSignature = null;
|
|
10036
|
+
var retryTimer = null;
|
|
9854
10037
|
function log(debug, msg, data) {
|
|
9855
10038
|
if (!debug) return;
|
|
9856
10039
|
if (data !== void 0) console.log(`[Paywallo:SuperwallAttr] ${msg}`, data);
|
|
@@ -9917,28 +10100,37 @@ async function waitForConfigured(mod, debug) {
|
|
|
9917
10100
|
status = String(await mod.getConfigurationStatus());
|
|
9918
10101
|
} catch (err) {
|
|
9919
10102
|
log(debug, "getConfigurationStatus error", { err: String(err) });
|
|
9920
|
-
return
|
|
10103
|
+
return "failed";
|
|
9921
10104
|
}
|
|
9922
|
-
if (status === "CONFIGURED") return
|
|
10105
|
+
if (status === "CONFIGURED") return "configured";
|
|
9923
10106
|
if (status === "FAILED") {
|
|
9924
10107
|
log(debug, "Superwall configuration FAILED \u2014 skipping attribute push");
|
|
9925
|
-
return
|
|
10108
|
+
return "failed";
|
|
9926
10109
|
}
|
|
9927
10110
|
await delay(CONFIG_POLL_INTERVAL_MS);
|
|
9928
10111
|
}
|
|
9929
10112
|
log(debug, "Superwall not configured within timeout \u2014 skipping attribute push");
|
|
9930
|
-
return
|
|
10113
|
+
return "timeout";
|
|
9931
10114
|
}
|
|
9932
10115
|
async function pushAttributionToSuperwall(debug = false) {
|
|
10116
|
+
clearRetryTimer();
|
|
10117
|
+
await attemptPush(debug, 0);
|
|
10118
|
+
}
|
|
10119
|
+
async function attemptPush(debug, retryIndex) {
|
|
10120
|
+
const native = await resolveSuperwallNativeModule(debug);
|
|
10121
|
+
if (!native) return;
|
|
10122
|
+
const waited = await waitForConfigured(native, debug);
|
|
10123
|
+
if (waited === "timeout") {
|
|
10124
|
+
scheduleRetry(debug, retryIndex);
|
|
10125
|
+
return;
|
|
10126
|
+
}
|
|
10127
|
+
if (waited !== "configured") return;
|
|
9933
10128
|
const attrs = buildSuperwallAttributes(attributionTracker.get());
|
|
9934
10129
|
const signature = JSON.stringify(attrs);
|
|
9935
10130
|
if (signature === lastSignature) {
|
|
9936
10131
|
log(debug, "attributes unchanged \u2014 skipping push");
|
|
9937
10132
|
return;
|
|
9938
10133
|
}
|
|
9939
|
-
const native = await resolveSuperwallNativeModule(debug);
|
|
9940
|
-
if (!native) return;
|
|
9941
|
-
if (!await waitForConfigured(native, debug)) return;
|
|
9942
10134
|
try {
|
|
9943
10135
|
await native.setUserAttributes(attrs);
|
|
9944
10136
|
lastSignature = signature;
|
|
@@ -9947,6 +10139,28 @@ async function pushAttributionToSuperwall(debug = false) {
|
|
|
9947
10139
|
log(debug, "setUserAttributes error", { err: String(err) });
|
|
9948
10140
|
}
|
|
9949
10141
|
}
|
|
10142
|
+
function scheduleRetry(debug, retryIndex) {
|
|
10143
|
+
const delayMs = CONFIG_RETRY_DELAYS_MS[retryIndex];
|
|
10144
|
+
if (delayMs === void 0) {
|
|
10145
|
+
log(debug, "Superwall never configured \u2014 giving up attribute push");
|
|
10146
|
+
return;
|
|
10147
|
+
}
|
|
10148
|
+
clearRetryTimer();
|
|
10149
|
+
log(debug, "scheduling attribute push retry", { retryIndex, delayMs });
|
|
10150
|
+
retryTimer = setTimeout(() => {
|
|
10151
|
+
retryTimer = null;
|
|
10152
|
+
void attemptPush(debug, retryIndex + 1);
|
|
10153
|
+
}, delayMs);
|
|
10154
|
+
}
|
|
10155
|
+
function clearRetryTimer() {
|
|
10156
|
+
if (retryTimer !== null) {
|
|
10157
|
+
clearTimeout(retryTimer);
|
|
10158
|
+
retryTimer = null;
|
|
10159
|
+
}
|
|
10160
|
+
}
|
|
10161
|
+
function cancelSuperwallAttributeRetry() {
|
|
10162
|
+
clearRetryTimer();
|
|
10163
|
+
}
|
|
9950
10164
|
|
|
9951
10165
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9952
10166
|
init_PaywalloClient();
|
|
@@ -9979,6 +10193,7 @@ function buildDeterministicEventId(parts) {
|
|
|
9979
10193
|
|
|
9980
10194
|
// src/domains/paywall/SuperwallAutoBridge.ts
|
|
9981
10195
|
var bridgeStarted = false;
|
|
10196
|
+
var startInProgress = false;
|
|
9982
10197
|
var unsubscribe = null;
|
|
9983
10198
|
var debugEnabled = false;
|
|
9984
10199
|
function log2(msg, data) {
|
|
@@ -10019,8 +10234,7 @@ function mapDismissToCloseReason(resultType) {
|
|
|
10019
10234
|
case "purchased":
|
|
10020
10235
|
return "purchased";
|
|
10021
10236
|
case "restored":
|
|
10022
|
-
return "
|
|
10023
|
-
// restore counts as a purchase outcome for funnel
|
|
10237
|
+
return "restored";
|
|
10024
10238
|
case "declined":
|
|
10025
10239
|
return "dismissed";
|
|
10026
10240
|
default:
|
|
@@ -10056,17 +10270,66 @@ function buildCallbacks() {
|
|
|
10056
10270
|
const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
|
|
10057
10271
|
log2("onPurchase", { productId, identifier });
|
|
10058
10272
|
void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
|
|
10273
|
+
},
|
|
10274
|
+
// Global Superwall event listener — used specifically for `transactionComplete`
|
|
10275
|
+
// which is the only event that exposes the full product (currency/price) +
|
|
10276
|
+
// transaction (storeTransactionId) shape needed to create a server-side
|
|
10277
|
+
// `transactions` row. Other events are skipped (paywallOpen/Dismiss already
|
|
10278
|
+
// handled above with their typed callbacks).
|
|
10279
|
+
onSuperwallEvent: (info) => {
|
|
10280
|
+
const ev = info?.event;
|
|
10281
|
+
if (!ev || ev.event !== "transactionComplete") return;
|
|
10282
|
+
const ts = Date.now();
|
|
10283
|
+
const product = ev.product;
|
|
10284
|
+
const transaction = ev.transaction;
|
|
10285
|
+
if (!product) {
|
|
10286
|
+
log2("transactionComplete missing product \u2014 skipping transaction track");
|
|
10287
|
+
return;
|
|
10288
|
+
}
|
|
10289
|
+
const productId = product.productIdentifier ?? product.id ?? "unknown";
|
|
10290
|
+
const currency = product.currencyCode ?? "USD";
|
|
10291
|
+
const price = product.price ?? 0;
|
|
10292
|
+
const transactionId = transaction?.purchaseToken ?? transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
|
|
10293
|
+
const introPrice = product.trialPeriodPrice ?? 0;
|
|
10294
|
+
const isTrial = product.hasFreeTrial === true && introPrice <= 0;
|
|
10295
|
+
const isPaidIntro = introPrice > 0;
|
|
10296
|
+
const paywallIdentifier = ev.paywallInfo?.identifier;
|
|
10297
|
+
log2("onSuperwallEvent: transactionComplete", { productId, currency, price, introPrice, transactionId, isTrial });
|
|
10298
|
+
void (async () => {
|
|
10299
|
+
try {
|
|
10300
|
+
await PaywalloClient.track("transaction", {
|
|
10301
|
+
priority: "critical",
|
|
10302
|
+
properties: {
|
|
10303
|
+
type: isTrial ? "trial_started" : "completed",
|
|
10304
|
+
// Server's IngestTranslator looks for `transaction_id`/`product_id`
|
|
10305
|
+
// /`amount`/`currency`/`paywall_id` keys. A free trial MUST carry
|
|
10306
|
+
// amount=0 (no revenue at start); a paid intro offer carries its
|
|
10307
|
+
// intro amount (real revenue); a normal purchase carries `price`.
|
|
10308
|
+
// `full_price` carries the recurring full price so the server can
|
|
10309
|
+
// value the eventual conversion (free trial) or keep the headline
|
|
10310
|
+
// price alongside the discounted intro amount (paid intro).
|
|
10311
|
+
transaction_id: transactionId,
|
|
10312
|
+
product_id: productId,
|
|
10313
|
+
amount: isTrial ? 0 : isPaidIntro ? introPrice : price,
|
|
10314
|
+
currency,
|
|
10315
|
+
...isTrial || isPaidIntro ? { full_price: price } : {},
|
|
10316
|
+
...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
|
|
10317
|
+
}
|
|
10318
|
+
});
|
|
10319
|
+
} catch (err) {
|
|
10320
|
+
log2("transactionComplete track error", { err: String(err) });
|
|
10321
|
+
}
|
|
10322
|
+
})();
|
|
10059
10323
|
}
|
|
10060
|
-
// onSuperwallEvent removed — transaction sale events are sourced exclusively
|
|
10061
|
-
// from server-side webhooks (Apple/Google/Superwall/RevenueCat).
|
|
10062
10324
|
};
|
|
10063
10325
|
}
|
|
10064
10326
|
function startSuperwallAutoBridge(debug = false) {
|
|
10065
10327
|
debugEnabled = debug;
|
|
10066
|
-
if (bridgeStarted) {
|
|
10067
|
-
log2("already started, skipping");
|
|
10328
|
+
if (bridgeStarted || startInProgress) {
|
|
10329
|
+
log2("already started or starting, skipping");
|
|
10068
10330
|
return;
|
|
10069
10331
|
}
|
|
10332
|
+
startInProgress = true;
|
|
10070
10333
|
void detectAndStart();
|
|
10071
10334
|
}
|
|
10072
10335
|
async function detectAndStart() {
|
|
@@ -10077,6 +10340,7 @@ async function detectAndStart() {
|
|
|
10077
10340
|
const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
|
|
10078
10341
|
if (typeof internal.subscribeToSuperwallEvents !== "function") {
|
|
10079
10342
|
log2("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
|
|
10343
|
+
startInProgress = false;
|
|
10080
10344
|
return;
|
|
10081
10345
|
}
|
|
10082
10346
|
subscribeFn = internal.subscribeToSuperwallEvents;
|
|
@@ -10085,6 +10349,11 @@ async function detectAndStart() {
|
|
|
10085
10349
|
}
|
|
10086
10350
|
} catch {
|
|
10087
10351
|
log2("expo-superwall not installed \u2014 bridge inactive");
|
|
10352
|
+
startInProgress = false;
|
|
10353
|
+
return;
|
|
10354
|
+
}
|
|
10355
|
+
if (!startInProgress) {
|
|
10356
|
+
log2("start cancelled by stop \u2014 bridge inactive");
|
|
10088
10357
|
return;
|
|
10089
10358
|
}
|
|
10090
10359
|
try {
|
|
@@ -10094,7 +10363,18 @@ async function detectAndStart() {
|
|
|
10094
10363
|
log2("bridge started");
|
|
10095
10364
|
} catch (err) {
|
|
10096
10365
|
log2("subscribe failed", { err: String(err) });
|
|
10366
|
+
} finally {
|
|
10367
|
+
startInProgress = false;
|
|
10368
|
+
}
|
|
10369
|
+
}
|
|
10370
|
+
function stopSuperwallAutoBridge() {
|
|
10371
|
+
if (unsubscribe) {
|
|
10372
|
+
unsubscribe();
|
|
10373
|
+
unsubscribe = null;
|
|
10097
10374
|
}
|
|
10375
|
+
bridgeStarted = false;
|
|
10376
|
+
startInProgress = false;
|
|
10377
|
+
log2("bridge stopped");
|
|
10098
10378
|
}
|
|
10099
10379
|
|
|
10100
10380
|
// src/PaywalloProvider.tsx
|
|
@@ -10661,6 +10941,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10661
10941
|
const configRef = useRef12(config);
|
|
10662
10942
|
useEffect14(() => {
|
|
10663
10943
|
let mounted = true;
|
|
10944
|
+
const unsubscribeAttribution = attributionTracker.onCapture(() => {
|
|
10945
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10946
|
+
});
|
|
10664
10947
|
const bootstrap = async () => {
|
|
10665
10948
|
if (!PaywalloClient.isReady()) {
|
|
10666
10949
|
initLocalization({ detectDevice: true });
|
|
@@ -10671,11 +10954,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
10671
10954
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
10672
10955
|
return;
|
|
10673
10956
|
}
|
|
10674
|
-
if (!mounted) return;
|
|
10675
|
-
startSuperwallAutoBridge(configRef.current.debug);
|
|
10676
|
-
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10677
10957
|
}
|
|
10678
10958
|
if (!mounted) return;
|
|
10959
|
+
startSuperwallAutoBridge(configRef.current.debug);
|
|
10960
|
+
void pushAttributionToSuperwall(configRef.current.debug);
|
|
10679
10961
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
10680
10962
|
setIsInitialized(true);
|
|
10681
10963
|
if (!autoPreloadTriggeredRef.current) {
|
|
@@ -10693,6 +10975,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
10693
10975
|
void bootstrap();
|
|
10694
10976
|
return () => {
|
|
10695
10977
|
mounted = false;
|
|
10978
|
+
unsubscribeAttribution();
|
|
10979
|
+
stopSuperwallAutoBridge();
|
|
10980
|
+
cancelSuperwallAttributeRetry();
|
|
10696
10981
|
};
|
|
10697
10982
|
}, [preloadCampaign]);
|
|
10698
10983
|
const getPaywallConfig = useCallback20(async (p) => {
|