@virex-tech/paywallo-sdk 1.1.1 → 1.1.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/android/src/main/java/com/paywallo/sdk/PaywalloWebView.kt +45 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewManager.kt +1 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +427 -145
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +498 -207
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloWebView.h +1 -0
- package/ios/PaywalloWebView.m +20 -0
- package/ios/PaywalloWebViewManager.m +1 -0
- package/ios/PaywalloWebViewModule.h +1 -0
- package/ios/PaywalloWebViewModule.m +8 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -951,11 +951,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
951
951
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
952
952
|
const map = /* @__PURE__ */ new Map();
|
|
953
953
|
for (const p of storeProducts) map.set(p.productId, p);
|
|
954
|
-
if (PaywalloClient.getConfig()?.debug) {
|
|
955
|
-
for (const [id, p] of map) {
|
|
956
|
-
console.warn(`[Paywallo][CURRENCY-CHECK] product=${id} currency=${p.currency} price="${p.price}" localizedPrice="${p.localizedPrice}" priceValue=${p.priceValue} fallbackUSD=${!p.currency || p.currency === "USD" ? "POSSIBLE" : "NO"}`);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
954
|
setProducts(map);
|
|
960
955
|
} catch {
|
|
961
956
|
setProducts(/* @__PURE__ */ new Map());
|
|
@@ -1035,6 +1030,13 @@ function getWebViewEmitter() {
|
|
|
1035
1030
|
}
|
|
1036
1031
|
return webViewEmitter;
|
|
1037
1032
|
}
|
|
1033
|
+
function parseErrorEvent(raw) {
|
|
1034
|
+
return {
|
|
1035
|
+
code: typeof raw["code"] === "number" ? raw["code"] : -1,
|
|
1036
|
+
description: typeof raw["description"] === "string" ? raw["description"] : "WebView error",
|
|
1037
|
+
url: typeof raw["url"] === "string" ? raw["url"] : void 0
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1038
1040
|
function PaywallWebView({
|
|
1039
1041
|
paywallId,
|
|
1040
1042
|
craftData,
|
|
@@ -1045,11 +1047,14 @@ function PaywallWebView({
|
|
|
1045
1047
|
onPurchase,
|
|
1046
1048
|
onClose,
|
|
1047
1049
|
onRestore,
|
|
1048
|
-
onReady
|
|
1050
|
+
onReady,
|
|
1051
|
+
onError
|
|
1049
1052
|
}) {
|
|
1050
1053
|
const paywallDataSent = (0, import_react4.useRef)(false);
|
|
1051
1054
|
const loadEndReceived = (0, import_react4.useRef)(false);
|
|
1052
1055
|
const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
|
|
1056
|
+
const onErrorRef = (0, import_react4.useRef)(onError);
|
|
1057
|
+
onErrorRef.current = onError;
|
|
1053
1058
|
const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
|
|
1054
1059
|
const webUrl = (0, import_react4.useMemo)(() => PaywalloClient.getWebUrl() ?? "http://localhost:3000", []);
|
|
1055
1060
|
const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
|
|
@@ -1109,17 +1114,51 @@ function PaywallWebView({
|
|
|
1109
1114
|
const message = parseWebViewMessage(event.data);
|
|
1110
1115
|
if (message) handleParsedMessage(message);
|
|
1111
1116
|
});
|
|
1117
|
+
const errorSub = emitter.addListener(
|
|
1118
|
+
"PaywalloWebViewError",
|
|
1119
|
+
(event) => {
|
|
1120
|
+
onErrorRef.current?.(parseErrorEvent(event));
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1112
1123
|
return () => {
|
|
1113
1124
|
loadEndSub.remove();
|
|
1114
1125
|
messageSub.remove();
|
|
1126
|
+
errorSub.remove();
|
|
1115
1127
|
};
|
|
1116
1128
|
}, [buildAndSetInjectionScript, handleParsedMessage]);
|
|
1129
|
+
(0, import_react4.useEffect)(() => {
|
|
1130
|
+
const timer = setTimeout(() => {
|
|
1131
|
+
if (!paywallDataSent.current) {
|
|
1132
|
+
onErrorRef.current?.({
|
|
1133
|
+
code: -1001,
|
|
1134
|
+
description: "Paywall load timed out after 10 seconds"
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
}, 1e4);
|
|
1138
|
+
return () => clearTimeout(timer);
|
|
1139
|
+
}, []);
|
|
1117
1140
|
const handleLoadEnd = (0, import_react4.useCallback)(() => {
|
|
1118
1141
|
if (!loadEndReceived.current) {
|
|
1119
1142
|
loadEndReceived.current = true;
|
|
1120
1143
|
buildAndSetInjectionScript();
|
|
1121
1144
|
}
|
|
1122
1145
|
}, [buildAndSetInjectionScript]);
|
|
1146
|
+
const handleNativeError = (0, import_react4.useCallback)((event) => {
|
|
1147
|
+
const ne = event.nativeEvent;
|
|
1148
|
+
let errorData;
|
|
1149
|
+
if (ne["payload"] && typeof ne["payload"] === "object") {
|
|
1150
|
+
errorData = ne["payload"];
|
|
1151
|
+
} else if (typeof ne["data"] === "string") {
|
|
1152
|
+
try {
|
|
1153
|
+
errorData = JSON.parse(ne["data"]);
|
|
1154
|
+
} catch {
|
|
1155
|
+
errorData = {};
|
|
1156
|
+
}
|
|
1157
|
+
} else {
|
|
1158
|
+
errorData = {};
|
|
1159
|
+
}
|
|
1160
|
+
onErrorRef.current?.(parseErrorEvent(errorData));
|
|
1161
|
+
}, []);
|
|
1123
1162
|
const handleMessage = (0, import_react4.useCallback)(
|
|
1124
1163
|
(event) => {
|
|
1125
1164
|
const message = parseWebViewMessage(event.nativeEvent.data);
|
|
@@ -1139,6 +1178,7 @@ function PaywallWebView({
|
|
|
1139
1178
|
injectedJavaScript: scriptToInject,
|
|
1140
1179
|
onMessage: handleMessage,
|
|
1141
1180
|
onLoadEnd: handleLoadEnd,
|
|
1181
|
+
onError: handleNativeError,
|
|
1142
1182
|
style: styles.webview
|
|
1143
1183
|
}
|
|
1144
1184
|
) });
|
|
@@ -1169,6 +1209,25 @@ function PaywallModal({
|
|
|
1169
1209
|
campaignId
|
|
1170
1210
|
);
|
|
1171
1211
|
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId);
|
|
1212
|
+
const [webViewError, setWebViewError] = (0, import_react5.useState)(null);
|
|
1213
|
+
const [retryCount, setRetryCount] = (0, import_react5.useState)(0);
|
|
1214
|
+
const retryCountRef = (0, import_react5.useRef)(0);
|
|
1215
|
+
const handleWebViewError = (0, import_react5.useCallback)(
|
|
1216
|
+
(err) => {
|
|
1217
|
+
if (retryCountRef.current >= 1) {
|
|
1218
|
+
setWebViewError({ code: err.code, description: err.description });
|
|
1219
|
+
} else {
|
|
1220
|
+
retryCountRef.current += 1;
|
|
1221
|
+
setRetryCount(retryCountRef.current);
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
[]
|
|
1225
|
+
);
|
|
1226
|
+
const handleRetry = (0, import_react5.useCallback)(() => {
|
|
1227
|
+
retryCountRef.current = 0;
|
|
1228
|
+
setWebViewError(null);
|
|
1229
|
+
setRetryCount(0);
|
|
1230
|
+
}, []);
|
|
1172
1231
|
const openAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(SCREEN_HEIGHT2)).current;
|
|
1173
1232
|
(0, import_react5.useEffect)(() => {
|
|
1174
1233
|
if (visible) {
|
|
@@ -1193,7 +1252,12 @@ function PaywallModal({
|
|
|
1193
1252
|
statusBarTranslucent: true,
|
|
1194
1253
|
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles2.container, children: [
|
|
1195
1254
|
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.errorText, children: error.message }) }),
|
|
1196
|
-
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1255
|
+
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native5.View, { style: styles2.fallbackCard, children: [
|
|
1256
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.fallbackTitle, children: "Algo deu errado" }),
|
|
1257
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.fallbackDescription, children: webViewError.description }),
|
|
1258
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Pressable, { style: styles2.retryButton, onPress: handleRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.retryButtonText, children: "Tentar novamente" }) }),
|
|
1259
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Pressable, { style: styles2.closeButton, onPress: handleClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native5.Text, { style: styles2.closeButtonText, children: "Fechar" }) })
|
|
1260
|
+
] }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1197
1261
|
PaywallWebView,
|
|
1198
1262
|
{
|
|
1199
1263
|
paywallId: paywallConfig.id,
|
|
@@ -1204,39 +1268,67 @@ function PaywallModal({
|
|
|
1204
1268
|
isPurchasing,
|
|
1205
1269
|
onClose: handleClose,
|
|
1206
1270
|
onPurchase: handlePurchase,
|
|
1207
|
-
onRestore: handleRestore
|
|
1208
|
-
|
|
1209
|
-
|
|
1271
|
+
onRestore: handleRestore,
|
|
1272
|
+
onError: handleWebViewError
|
|
1273
|
+
},
|
|
1274
|
+
retryCount
|
|
1275
|
+
) })
|
|
1210
1276
|
] }) }) })
|
|
1211
1277
|
}
|
|
1212
1278
|
);
|
|
1213
1279
|
}
|
|
1214
1280
|
var styles2 = import_react_native5.StyleSheet.create({
|
|
1215
|
-
overlay: {
|
|
1281
|
+
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1282
|
+
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1283
|
+
container: { flex: 1, backgroundColor: "#fff" },
|
|
1284
|
+
errorContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20 },
|
|
1285
|
+
errorText: { fontSize: 16, color: "#ef4444", textAlign: "center" },
|
|
1286
|
+
fallbackContainer: {
|
|
1216
1287
|
flex: 1,
|
|
1217
|
-
|
|
1288
|
+
justifyContent: "center",
|
|
1289
|
+
alignItems: "center",
|
|
1290
|
+
backgroundColor: "#111010",
|
|
1291
|
+
padding: 24
|
|
1218
1292
|
},
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1293
|
+
fallbackCard: {
|
|
1294
|
+
width: "100%",
|
|
1295
|
+
maxWidth: 360,
|
|
1296
|
+
alignItems: "center",
|
|
1297
|
+
backgroundColor: "rgba(255,255,255,0.07)",
|
|
1298
|
+
borderRadius: 16,
|
|
1299
|
+
padding: 28
|
|
1224
1300
|
},
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1301
|
+
fallbackTitle: {
|
|
1302
|
+
fontSize: 18,
|
|
1303
|
+
fontWeight: "600",
|
|
1304
|
+
color: "#f5f5f5",
|
|
1305
|
+
marginBottom: 10,
|
|
1306
|
+
textAlign: "center"
|
|
1228
1307
|
},
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1308
|
+
fallbackDescription: {
|
|
1309
|
+
fontSize: 14,
|
|
1310
|
+
color: "rgba(255,255,255,0.55)",
|
|
1311
|
+
textAlign: "center",
|
|
1312
|
+
marginBottom: 28,
|
|
1313
|
+
lineHeight: 20
|
|
1314
|
+
},
|
|
1315
|
+
retryButton: {
|
|
1316
|
+
width: "100%",
|
|
1317
|
+
backgroundColor: "#ffffff",
|
|
1318
|
+
borderRadius: 10,
|
|
1319
|
+
paddingVertical: 14,
|
|
1232
1320
|
alignItems: "center",
|
|
1233
|
-
|
|
1321
|
+
marginBottom: 12
|
|
1234
1322
|
},
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1323
|
+
retryButtonText: { fontSize: 15, fontWeight: "600", color: "#111010" },
|
|
1324
|
+
closeButton: {
|
|
1325
|
+
width: "100%",
|
|
1326
|
+
borderRadius: 10,
|
|
1327
|
+
paddingVertical: 14,
|
|
1328
|
+
alignItems: "center",
|
|
1329
|
+
backgroundColor: "rgba(255,255,255,0.1)"
|
|
1330
|
+
},
|
|
1331
|
+
closeButtonText: { fontSize: 15, fontWeight: "500", color: "rgba(255,255,255,0.7)" }
|
|
1240
1332
|
});
|
|
1241
1333
|
|
|
1242
1334
|
// src/domains/paywall/PaywallErrorBoundary.tsx
|
|
@@ -1551,6 +1643,92 @@ var PaywalloAffiliate = {
|
|
|
1551
1643
|
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1552
1644
|
};
|
|
1553
1645
|
|
|
1646
|
+
// src/domains/subscription/SubscriptionCache.ts
|
|
1647
|
+
var CACHE_KEY = "subscription_cache";
|
|
1648
|
+
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1649
|
+
var SubscriptionCache = class {
|
|
1650
|
+
constructor(ttl = DEFAULT_TTL) {
|
|
1651
|
+
this.memoryCache = null;
|
|
1652
|
+
this.ttl = ttl;
|
|
1653
|
+
this.storage = new SecureStorage();
|
|
1654
|
+
}
|
|
1655
|
+
async get() {
|
|
1656
|
+
if (this.memoryCache && !this.isExpired(this.memoryCache)) {
|
|
1657
|
+
return this.memoryCache;
|
|
1658
|
+
}
|
|
1659
|
+
try {
|
|
1660
|
+
const stored = await this.storage.get(CACHE_KEY);
|
|
1661
|
+
if (!stored) {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
const parsed = JSON.parse(stored);
|
|
1665
|
+
if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
|
|
1666
|
+
await this.storage.remove(CACHE_KEY);
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
const cached = parsed;
|
|
1670
|
+
if (cached.data.subscription?.expiresAt) {
|
|
1671
|
+
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
1672
|
+
}
|
|
1673
|
+
if (cached.data.subscription?.originalPurchaseDate) {
|
|
1674
|
+
cached.data.subscription.originalPurchaseDate = new Date(
|
|
1675
|
+
cached.data.subscription.originalPurchaseDate
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
if (cached.data.subscription?.latestPurchaseDate) {
|
|
1679
|
+
cached.data.subscription.latestPurchaseDate = new Date(
|
|
1680
|
+
cached.data.subscription.latestPurchaseDate
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
if (cached.data.subscription?.cancellationDate) {
|
|
1684
|
+
cached.data.subscription.cancellationDate = new Date(
|
|
1685
|
+
cached.data.subscription.cancellationDate
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
1689
|
+
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
1690
|
+
cached.data.subscription.gracePeriodExpiresAt
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
cached.isStale = this.isExpired(cached);
|
|
1694
|
+
if (!cached.isStale) {
|
|
1695
|
+
this.memoryCache = cached;
|
|
1696
|
+
}
|
|
1697
|
+
return cached;
|
|
1698
|
+
} catch {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
async set(data) {
|
|
1703
|
+
const cached = {
|
|
1704
|
+
data,
|
|
1705
|
+
cachedAt: Date.now(),
|
|
1706
|
+
isStale: false
|
|
1707
|
+
};
|
|
1708
|
+
this.memoryCache = cached;
|
|
1709
|
+
try {
|
|
1710
|
+
await this.storage.set(CACHE_KEY, JSON.stringify(cached));
|
|
1711
|
+
} catch (error) {
|
|
1712
|
+
if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async invalidate() {
|
|
1716
|
+
this.memoryCache = null;
|
|
1717
|
+
try {
|
|
1718
|
+
await this.storage.remove(CACHE_KEY);
|
|
1719
|
+
} catch (error) {
|
|
1720
|
+
if (__DEV__) console.warn("[SubscriptionCache] invalidate() failed:", error instanceof Error ? error.message : String(error));
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
isExpired(cached) {
|
|
1724
|
+
return Date.now() - cached.cachedAt > this.ttl;
|
|
1725
|
+
}
|
|
1726
|
+
setTTL(ttl) {
|
|
1727
|
+
this.ttl = ttl;
|
|
1728
|
+
}
|
|
1729
|
+
};
|
|
1730
|
+
var subscriptionCache = new SubscriptionCache();
|
|
1731
|
+
|
|
1554
1732
|
// src/core/ApiClient.ts
|
|
1555
1733
|
var import_react_native7 = require("react-native");
|
|
1556
1734
|
|
|
@@ -2413,7 +2591,7 @@ var ApiCache = class {
|
|
|
2413
2591
|
// src/core/ApiClient.ts
|
|
2414
2592
|
var SDK_VERSION = "1.0.0";
|
|
2415
2593
|
var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
|
|
2416
|
-
var
|
|
2594
|
+
var _ApiClient = class _ApiClient {
|
|
2417
2595
|
constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2418
2596
|
this.cache = new ApiCache();
|
|
2419
2597
|
this.appKey = appKey;
|
|
@@ -2503,6 +2681,11 @@ var ApiClient = class {
|
|
|
2503
2681
|
const key = `${flagKey}:${distinctId}`;
|
|
2504
2682
|
const hit = this.cache.getVariant(key);
|
|
2505
2683
|
if (hit !== void 0) return hit;
|
|
2684
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
2685
|
+
if (persisted !== null) {
|
|
2686
|
+
this.cache.setVariant(key, persisted);
|
|
2687
|
+
return persisted;
|
|
2688
|
+
}
|
|
2506
2689
|
try {
|
|
2507
2690
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2508
2691
|
if (res.status === 404) {
|
|
@@ -2517,6 +2700,7 @@ var ApiClient = class {
|
|
|
2517
2700
|
return { variant: null };
|
|
2518
2701
|
}
|
|
2519
2702
|
this.cache.setVariant(key, res.data);
|
|
2703
|
+
await this.writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2520
2704
|
return res.data;
|
|
2521
2705
|
} catch (error) {
|
|
2522
2706
|
this.log("Failed to get variant:", flagKey, error);
|
|
@@ -2598,6 +2782,35 @@ var ApiClient = class {
|
|
|
2598
2782
|
this.log("Subscription status:", result.hasActiveSubscription);
|
|
2599
2783
|
return result;
|
|
2600
2784
|
}
|
|
2785
|
+
async evaluateFlags(keys, distinctId) {
|
|
2786
|
+
const query = keys.map(encodeURIComponent).join(",");
|
|
2787
|
+
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
2788
|
+
try {
|
|
2789
|
+
const res = await this.httpClient.get(path, {
|
|
2790
|
+
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
2791
|
+
});
|
|
2792
|
+
if (!res.ok) {
|
|
2793
|
+
this.log("Non-OK response for evaluateFlags:", res.status);
|
|
2794
|
+
return {};
|
|
2795
|
+
}
|
|
2796
|
+
const raw = res.data;
|
|
2797
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
2798
|
+
const record = raw;
|
|
2799
|
+
const result = {};
|
|
2800
|
+
for (const key of Object.keys(record)) {
|
|
2801
|
+
const value = record[key];
|
|
2802
|
+
const variant = typeof value === "boolean" ? String(value) : typeof value === "string" ? value : null;
|
|
2803
|
+
result[key] = variant;
|
|
2804
|
+
const flagVariant = { variant };
|
|
2805
|
+
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
2806
|
+
await this.writeFlagToStorage(key, distinctId, flagVariant);
|
|
2807
|
+
}
|
|
2808
|
+
return result;
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
this.log("Failed to evaluateFlags:", error);
|
|
2811
|
+
return {};
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2601
2814
|
async getConditionalFlag(flagKey, context) {
|
|
2602
2815
|
try {
|
|
2603
2816
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
@@ -2614,7 +2827,43 @@ var ApiClient = class {
|
|
|
2614
2827
|
return { value: false, flagKey };
|
|
2615
2828
|
}
|
|
2616
2829
|
}
|
|
2617
|
-
// ───
|
|
2830
|
+
// ─── Cache-only reads ────────────────────────────────────────────────────────
|
|
2831
|
+
/**
|
|
2832
|
+
* Reads a flag variant from cache layers only — no network call.
|
|
2833
|
+
* Checks in-memory cache first, then SecureStorage.
|
|
2834
|
+
* Returns null when both layers miss.
|
|
2835
|
+
*/
|
|
2836
|
+
async getVariantFromCache(flagKey, distinctId) {
|
|
2837
|
+
const key = `${flagKey}:${distinctId}`;
|
|
2838
|
+
const hit = this.cache.getVariant(key);
|
|
2839
|
+
if (hit !== void 0) return hit;
|
|
2840
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
2841
|
+
if (persisted !== null) {
|
|
2842
|
+
this.cache.setVariant(key, persisted);
|
|
2843
|
+
return persisted;
|
|
2844
|
+
}
|
|
2845
|
+
return null;
|
|
2846
|
+
}
|
|
2847
|
+
async readFlagFromStorage(flagKey, distinctId) {
|
|
2848
|
+
try {
|
|
2849
|
+
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
2850
|
+
if (!raw) return null;
|
|
2851
|
+
const parsed = JSON.parse(raw);
|
|
2852
|
+
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
2853
|
+
const { variant, payload, cachedAt } = parsed;
|
|
2854
|
+
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
2855
|
+
return { variant, payload };
|
|
2856
|
+
} catch {
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
2861
|
+
try {
|
|
2862
|
+
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
2863
|
+
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
2864
|
+
} catch {
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2618
2867
|
/**
|
|
2619
2868
|
* POST with offline queue fallback. Enqueues when offline or on network failure.
|
|
2620
2869
|
* Throws only when offlineQueueEnabled is false and the request fails.
|
|
@@ -2641,6 +2890,9 @@ var ApiClient = class {
|
|
|
2641
2890
|
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
2642
2891
|
}
|
|
2643
2892
|
};
|
|
2893
|
+
// ─── Private helpers ─────────────────────────────────────────────────────────
|
|
2894
|
+
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2895
|
+
var ApiClient = _ApiClient;
|
|
2644
2896
|
|
|
2645
2897
|
// src/domains/campaign/CampaignError.ts
|
|
2646
2898
|
var CampaignError = class extends PaywalloError {
|
|
@@ -2668,18 +2920,21 @@ var CampaignGateService = class {
|
|
|
2668
2920
|
if (!distinctId) return null;
|
|
2669
2921
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2670
2922
|
}
|
|
2671
|
-
async presentCampaign(apiClient, placement, paywallPresenter, campaignPresenter, context) {
|
|
2923
|
+
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2672
2924
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2673
2925
|
if (!campaign) {
|
|
2674
2926
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2675
2927
|
}
|
|
2928
|
+
if (!context?.forceShow && await hasActive()) {
|
|
2929
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
2930
|
+
}
|
|
2676
2931
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2677
2932
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2678
2933
|
return { ...await paywallPresenter(campaign.paywall.placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2679
2934
|
}
|
|
2680
2935
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2681
2936
|
if (await hasActive()) return true;
|
|
2682
|
-
const r = await this.presentCampaign(apiClient, placement, paywallPresenter, campaignPresenter, context);
|
|
2937
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2683
2938
|
return r.purchased || r.restored;
|
|
2684
2939
|
}
|
|
2685
2940
|
async preloadCampaign(apiClient, placement, context) {
|
|
@@ -3216,6 +3471,20 @@ function isValidEventName(name) {
|
|
|
3216
3471
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3217
3472
|
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3218
3473
|
}
|
|
3474
|
+
var OFFLINE_ACTIVE_STATUSES = ["active", "grace_period"];
|
|
3475
|
+
async function resolveClientOfflineFromCache() {
|
|
3476
|
+
try {
|
|
3477
|
+
const cached = await subscriptionCache.get();
|
|
3478
|
+
if (!cached) return false;
|
|
3479
|
+
const sub = cached.data.subscription;
|
|
3480
|
+
if (!sub) return false;
|
|
3481
|
+
if (!OFFLINE_ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
3482
|
+
if (!sub.expiresAt) return true;
|
|
3483
|
+
return sub.expiresAt > /* @__PURE__ */ new Date();
|
|
3484
|
+
} catch {
|
|
3485
|
+
return false;
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3219
3488
|
var _PaywalloClientClass = class _PaywalloClientClass {
|
|
3220
3489
|
constructor() {
|
|
3221
3490
|
this.config = null;
|
|
@@ -3232,27 +3501,27 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3232
3501
|
this.initAttempts = 0;
|
|
3233
3502
|
}
|
|
3234
3503
|
async init(config) {
|
|
3235
|
-
console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
|
|
3504
|
+
if (config.debug) console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
|
|
3236
3505
|
if (this.config && this.apiClient) {
|
|
3237
|
-
console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3506
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3238
3507
|
return;
|
|
3239
3508
|
}
|
|
3240
3509
|
if (this.initPromise) {
|
|
3241
|
-
console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3510
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3242
3511
|
await this.initPromise;
|
|
3243
|
-
console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3512
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3244
3513
|
return;
|
|
3245
3514
|
}
|
|
3246
3515
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3247
|
-
console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3516
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3248
3517
|
this.pendingConfig = config;
|
|
3249
3518
|
this.initAttempts = 0;
|
|
3250
3519
|
this.initPromise = this.initWithRetry(config);
|
|
3251
3520
|
try {
|
|
3252
3521
|
await this.initPromise;
|
|
3253
|
-
console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3522
|
+
if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3254
3523
|
} catch (error) {
|
|
3255
|
-
console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3524
|
+
if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3256
3525
|
this.initPromise = null;
|
|
3257
3526
|
this.config = null;
|
|
3258
3527
|
this.apiClient = null;
|
|
@@ -3322,26 +3591,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3322
3591
|
async doInit(config) {
|
|
3323
3592
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3324
3593
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3325
|
-
console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3594
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3326
3595
|
await Promise.all([
|
|
3327
3596
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3328
3597
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3329
3598
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3330
3599
|
)
|
|
3331
3600
|
]);
|
|
3332
|
-
console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3333
|
-
console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3601
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3602
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3334
3603
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3335
3604
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3336
3605
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3337
|
-
console.warn("[Paywallo INIT]", "Step 2 DONE");
|
|
3338
|
-
console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
|
|
3606
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 2 DONE");
|
|
3607
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
|
|
3339
3608
|
await Promise.all([
|
|
3340
3609
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3341
3610
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3342
3611
|
]);
|
|
3343
3612
|
const distinctId = identityManager.getDistinctId();
|
|
3344
|
-
console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3613
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3345
3614
|
if (config.autoStartSession !== false) {
|
|
3346
3615
|
sessionManager.startSession().catch(() => {
|
|
3347
3616
|
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
@@ -3350,7 +3619,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3350
3619
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3351
3620
|
});
|
|
3352
3621
|
this.config = config;
|
|
3353
|
-
console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
|
|
3622
|
+
if (config.debug) console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
|
|
3354
3623
|
}
|
|
3355
3624
|
async identify(options) {
|
|
3356
3625
|
this.ensureInitialized();
|
|
@@ -3360,7 +3629,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3360
3629
|
const apiClient = this.getApiClientOrThrow();
|
|
3361
3630
|
const distinctId = identityManager.getDistinctId();
|
|
3362
3631
|
if (!distinctId) {
|
|
3363
|
-
console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3632
|
+
if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3364
3633
|
return;
|
|
3365
3634
|
}
|
|
3366
3635
|
if (!isValidEventName(eventName)) {
|
|
@@ -3369,7 +3638,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3369
3638
|
}
|
|
3370
3639
|
const sessionId = sessionManager.getSessionId();
|
|
3371
3640
|
if (eventName === "$purchase_completed") {
|
|
3372
|
-
console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3641
|
+
if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3373
3642
|
distinctId: distinctId.substring(0, 8) + "...",
|
|
3374
3643
|
productId: options?.properties?.productId,
|
|
3375
3644
|
priceUsd: options?.properties?.priceUsd,
|
|
@@ -3378,14 +3647,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3378
3647
|
placement: options?.properties?.placement
|
|
3379
3648
|
}));
|
|
3380
3649
|
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3381
|
-
console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3650
|
+
if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3382
3651
|
distinctId: distinctId.substring(0, 8) + "...",
|
|
3383
3652
|
placement: options?.properties?.placement,
|
|
3384
3653
|
variantKey: options?.properties?.variantKey,
|
|
3385
3654
|
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3386
3655
|
}));
|
|
3387
3656
|
} else {
|
|
3388
|
-
console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3657
|
+
if (this.config?.debug) console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3389
3658
|
}
|
|
3390
3659
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3391
3660
|
...identityManager.getProperties(),
|
|
@@ -3436,15 +3705,25 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3436
3705
|
...sessionId && { sessionId }
|
|
3437
3706
|
});
|
|
3438
3707
|
}
|
|
3708
|
+
async getVariantCached(flagKey, defaultValue) {
|
|
3709
|
+
this.ensureInitialized();
|
|
3710
|
+
const apiClient = this.getApiClientOrThrow();
|
|
3711
|
+
const distinctId = identityManager.getDistinctId();
|
|
3712
|
+
const result = await apiClient.getVariantFromCache(flagKey, distinctId);
|
|
3713
|
+
if (result) return result;
|
|
3714
|
+
void apiClient.getVariant(flagKey, distinctId).catch(() => {
|
|
3715
|
+
});
|
|
3716
|
+
return { variant: defaultValue ?? null, payload: void 0 };
|
|
3717
|
+
}
|
|
3439
3718
|
async getVariant(flagKey) {
|
|
3440
3719
|
try {
|
|
3441
3720
|
const distinctId = identityManager.getDistinctId();
|
|
3442
3721
|
if (!distinctId) {
|
|
3443
|
-
console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3722
|
+
if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3444
3723
|
return { variant: null };
|
|
3445
3724
|
}
|
|
3446
3725
|
const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3447
|
-
console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3726
|
+
if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3448
3727
|
return result;
|
|
3449
3728
|
} catch (error) {
|
|
3450
3729
|
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
@@ -3452,6 +3731,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3452
3731
|
return { variant: null };
|
|
3453
3732
|
}
|
|
3454
3733
|
}
|
|
3734
|
+
async evaluateFlags(keys) {
|
|
3735
|
+
this.ensureInitialized();
|
|
3736
|
+
return this.getApiClientOrThrow().evaluateFlags(keys, identityManager.getDistinctId());
|
|
3737
|
+
}
|
|
3455
3738
|
async getConditionalFlag(flagKey, context) {
|
|
3456
3739
|
try {
|
|
3457
3740
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3477,7 +3760,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3477
3760
|
}
|
|
3478
3761
|
async presentCampaign(placement, context) {
|
|
3479
3762
|
this.ensureInitialized();
|
|
3480
|
-
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, this.paywallPresenter, this.campaignPresenter, context);
|
|
3763
|
+
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
3481
3764
|
}
|
|
3482
3765
|
async hasActiveSubscription() {
|
|
3483
3766
|
this.ensureInitialized();
|
|
@@ -3485,11 +3768,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3485
3768
|
if (this.activeChecker) return await this.activeChecker();
|
|
3486
3769
|
const distinctId = identityManager.getDistinctId();
|
|
3487
3770
|
if (!distinctId || !this.apiClient) return false;
|
|
3488
|
-
|
|
3771
|
+
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
3772
|
+
return status.hasActiveSubscription;
|
|
3489
3773
|
} catch {
|
|
3490
|
-
if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014
|
|
3774
|
+
if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014 using persistent cache");
|
|
3491
3775
|
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", "hasActiveSubscription failed");
|
|
3492
|
-
return
|
|
3776
|
+
return resolveClientOfflineFromCache();
|
|
3493
3777
|
}
|
|
3494
3778
|
}
|
|
3495
3779
|
async getSubscription() {
|
|
@@ -3518,6 +3802,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3518
3802
|
const wasDebug = this.config?.debug ?? false;
|
|
3519
3803
|
await sessionManager.endSession();
|
|
3520
3804
|
await identityManager.reset();
|
|
3805
|
+
await subscriptionCache.invalidate();
|
|
3521
3806
|
queueProcessor.dispose();
|
|
3522
3807
|
offlineQueue.dispose();
|
|
3523
3808
|
networkMonitor.dispose();
|
|
@@ -3874,86 +4159,6 @@ var import_react13 = require("react");
|
|
|
3874
4159
|
|
|
3875
4160
|
// src/domains/subscription/SubscriptionManager.ts
|
|
3876
4161
|
var import_react_native13 = require("react-native");
|
|
3877
|
-
|
|
3878
|
-
// src/domains/subscription/SubscriptionCache.ts
|
|
3879
|
-
var CACHE_KEY = "subscription_cache";
|
|
3880
|
-
var DEFAULT_TTL = 5 * 60 * 1e3;
|
|
3881
|
-
var SubscriptionCache = class {
|
|
3882
|
-
constructor(ttl = DEFAULT_TTL) {
|
|
3883
|
-
this.memoryCache = null;
|
|
3884
|
-
this.ttl = ttl;
|
|
3885
|
-
this.storage = new SecureStorage();
|
|
3886
|
-
}
|
|
3887
|
-
async get() {
|
|
3888
|
-
if (this.memoryCache && !this.isExpired(this.memoryCache)) {
|
|
3889
|
-
return this.memoryCache;
|
|
3890
|
-
}
|
|
3891
|
-
try {
|
|
3892
|
-
const stored = await this.storage.get(CACHE_KEY);
|
|
3893
|
-
if (!stored) {
|
|
3894
|
-
return null;
|
|
3895
|
-
}
|
|
3896
|
-
const cached = JSON.parse(stored);
|
|
3897
|
-
if (cached.data.subscription?.expiresAt) {
|
|
3898
|
-
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
3899
|
-
}
|
|
3900
|
-
if (cached.data.subscription?.originalPurchaseDate) {
|
|
3901
|
-
cached.data.subscription.originalPurchaseDate = new Date(
|
|
3902
|
-
cached.data.subscription.originalPurchaseDate
|
|
3903
|
-
);
|
|
3904
|
-
}
|
|
3905
|
-
if (cached.data.subscription?.latestPurchaseDate) {
|
|
3906
|
-
cached.data.subscription.latestPurchaseDate = new Date(
|
|
3907
|
-
cached.data.subscription.latestPurchaseDate
|
|
3908
|
-
);
|
|
3909
|
-
}
|
|
3910
|
-
if (cached.data.subscription?.cancellationDate) {
|
|
3911
|
-
cached.data.subscription.cancellationDate = new Date(
|
|
3912
|
-
cached.data.subscription.cancellationDate
|
|
3913
|
-
);
|
|
3914
|
-
}
|
|
3915
|
-
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
3916
|
-
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
3917
|
-
cached.data.subscription.gracePeriodExpiresAt
|
|
3918
|
-
);
|
|
3919
|
-
}
|
|
3920
|
-
cached.isStale = this.isExpired(cached);
|
|
3921
|
-
if (!cached.isStale) {
|
|
3922
|
-
this.memoryCache = cached;
|
|
3923
|
-
}
|
|
3924
|
-
return cached;
|
|
3925
|
-
} catch {
|
|
3926
|
-
return null;
|
|
3927
|
-
}
|
|
3928
|
-
}
|
|
3929
|
-
async set(data) {
|
|
3930
|
-
const cached = {
|
|
3931
|
-
data,
|
|
3932
|
-
cachedAt: Date.now(),
|
|
3933
|
-
isStale: false
|
|
3934
|
-
};
|
|
3935
|
-
this.memoryCache = cached;
|
|
3936
|
-
try {
|
|
3937
|
-
await this.storage.set(CACHE_KEY, JSON.stringify(cached));
|
|
3938
|
-
} catch {
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
async invalidate() {
|
|
3942
|
-
this.memoryCache = null;
|
|
3943
|
-
try {
|
|
3944
|
-
await this.storage.remove(CACHE_KEY);
|
|
3945
|
-
} catch {
|
|
3946
|
-
}
|
|
3947
|
-
}
|
|
3948
|
-
isExpired(cached) {
|
|
3949
|
-
return Date.now() - cached.cachedAt > this.ttl;
|
|
3950
|
-
}
|
|
3951
|
-
setTTL(ttl) {
|
|
3952
|
-
this.ttl = ttl;
|
|
3953
|
-
}
|
|
3954
|
-
};
|
|
3955
|
-
|
|
3956
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3957
4162
|
var SubscriptionManagerClass = class {
|
|
3958
4163
|
constructor() {
|
|
3959
4164
|
this.config = null;
|
|
@@ -4369,12 +4574,54 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4369
4574
|
// src/hooks/useSubscriptionSync.ts
|
|
4370
4575
|
var import_react17 = require("react");
|
|
4371
4576
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4372
|
-
var ACTIVE_STATUSES = ["active", "
|
|
4577
|
+
var ACTIVE_STATUSES = ["active", "grace_period"];
|
|
4578
|
+
function toDomainSubscriptionInfo(sub) {
|
|
4579
|
+
return {
|
|
4580
|
+
id: "",
|
|
4581
|
+
productId: sub.productId,
|
|
4582
|
+
// The hook's SubscriptionStatus is a subset of the domain's — cast is safe
|
|
4583
|
+
status: sub.status,
|
|
4584
|
+
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4585
|
+
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4586
|
+
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4587
|
+
willRenew: sub.autoRenewEnabled,
|
|
4588
|
+
isTrial: false,
|
|
4589
|
+
isIntroductoryPeriod: false,
|
|
4590
|
+
platform: sub.platform
|
|
4591
|
+
};
|
|
4592
|
+
}
|
|
4593
|
+
function toDomainResponse(status) {
|
|
4594
|
+
return {
|
|
4595
|
+
hasActiveSubscription: status.hasActiveSubscription,
|
|
4596
|
+
subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
|
|
4597
|
+
entitlements: []
|
|
4598
|
+
};
|
|
4599
|
+
}
|
|
4600
|
+
async function resolveOfflineFromCache() {
|
|
4601
|
+
try {
|
|
4602
|
+
const cached = await subscriptionCache.get();
|
|
4603
|
+
if (!cached) return false;
|
|
4604
|
+
const sub = cached.data.subscription;
|
|
4605
|
+
if (!sub) return false;
|
|
4606
|
+
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4607
|
+
const hasValidExpiry = sub.expiresAt.getTime() === 0 || sub.expiresAt > /* @__PURE__ */ new Date();
|
|
4608
|
+
return hasValidExpiry;
|
|
4609
|
+
} catch {
|
|
4610
|
+
return false;
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
function isActiveCacheEntry(entry) {
|
|
4614
|
+
if (!entry.data) return false;
|
|
4615
|
+
if (!ACTIVE_STATUSES.includes(entry.data.status)) return false;
|
|
4616
|
+
if (entry.data.expiresAt && entry.data.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4617
|
+
return true;
|
|
4618
|
+
}
|
|
4373
4619
|
function useSubscriptionSync() {
|
|
4374
4620
|
const [subscription, setSubscription] = (0, import_react17.useState)(null);
|
|
4375
4621
|
const cacheRef = (0, import_react17.useRef)(null);
|
|
4376
4622
|
const invalidateCache = (0, import_react17.useCallback)(() => {
|
|
4377
4623
|
cacheRef.current = null;
|
|
4624
|
+
void subscriptionCache.invalidate();
|
|
4378
4625
|
}, []);
|
|
4379
4626
|
const fetchAndCache = (0, import_react17.useCallback)(async () => {
|
|
4380
4627
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4387,6 +4634,7 @@ function useSubscriptionSync() {
|
|
|
4387
4634
|
} : null;
|
|
4388
4635
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4389
4636
|
setSubscription(sub);
|
|
4637
|
+
void subscriptionCache.set(toDomainResponse(status));
|
|
4390
4638
|
return sub;
|
|
4391
4639
|
}, []);
|
|
4392
4640
|
const hasActiveSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4395,10 +4643,28 @@ function useSubscriptionSync() {
|
|
|
4395
4643
|
if (!apiClient || !distinctId) return false;
|
|
4396
4644
|
const cached = cacheRef.current;
|
|
4397
4645
|
if (cached && cached.expiresAt > Date.now()) {
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4646
|
+
return isActiveCacheEntry(cached);
|
|
4647
|
+
}
|
|
4648
|
+
try {
|
|
4649
|
+
const persisted = await subscriptionCache.get();
|
|
4650
|
+
if (persisted) {
|
|
4651
|
+
if (!persisted.isStale) {
|
|
4652
|
+
const sub = persisted.data.subscription ? {
|
|
4653
|
+
productId: persisted.data.subscription.productId,
|
|
4654
|
+
status: persisted.data.subscription.status,
|
|
4655
|
+
expiresAt: persisted.data.subscription.expiresAt ?? null,
|
|
4656
|
+
platform: persisted.data.subscription.platform,
|
|
4657
|
+
autoRenewEnabled: persisted.data.subscription.willRenew,
|
|
4658
|
+
inGracePeriod: persisted.data.subscription.status === "grace_period"
|
|
4659
|
+
} : null;
|
|
4660
|
+
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4661
|
+
setSubscription(sub);
|
|
4662
|
+
return persisted.data.hasActiveSubscription;
|
|
4663
|
+
}
|
|
4664
|
+
void fetchAndCache();
|
|
4665
|
+
return persisted.data.hasActiveSubscription;
|
|
4666
|
+
}
|
|
4667
|
+
} catch {
|
|
4402
4668
|
}
|
|
4403
4669
|
try {
|
|
4404
4670
|
const status = await apiClient.getSubscriptionStatus(distinctId);
|
|
@@ -4408,11 +4674,12 @@ function useSubscriptionSync() {
|
|
|
4408
4674
|
} : null;
|
|
4409
4675
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4410
4676
|
setSubscription(sub);
|
|
4677
|
+
void subscriptionCache.set(toDomainResponse(status));
|
|
4411
4678
|
return status.hasActiveSubscription;
|
|
4412
4679
|
} catch {
|
|
4413
|
-
return
|
|
4680
|
+
return await resolveOfflineFromCache();
|
|
4414
4681
|
}
|
|
4415
|
-
}, []);
|
|
4682
|
+
}, [fetchAndCache]);
|
|
4416
4683
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
4417
4684
|
const apiClient = PaywalloClient.getApiClient();
|
|
4418
4685
|
const distinctId = PaywalloClient.getDistinctId();
|
|
@@ -4422,7 +4689,22 @@ function useSubscriptionSync() {
|
|
|
4422
4689
|
try {
|
|
4423
4690
|
return await fetchAndCache();
|
|
4424
4691
|
} catch {
|
|
4425
|
-
|
|
4692
|
+
try {
|
|
4693
|
+
const persisted = await subscriptionCache.get();
|
|
4694
|
+
if (!persisted?.data.subscription) return null;
|
|
4695
|
+
const s = persisted.data.subscription;
|
|
4696
|
+
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
4697
|
+
return {
|
|
4698
|
+
productId: s.productId,
|
|
4699
|
+
status: normalisedStatus,
|
|
4700
|
+
expiresAt: s.expiresAt ?? null,
|
|
4701
|
+
platform: s.platform,
|
|
4702
|
+
autoRenewEnabled: s.willRenew,
|
|
4703
|
+
inGracePeriod: normalisedStatus === "in_grace_period"
|
|
4704
|
+
};
|
|
4705
|
+
} catch {
|
|
4706
|
+
return null;
|
|
4707
|
+
}
|
|
4426
4708
|
}
|
|
4427
4709
|
}, [fetchAndCache]);
|
|
4428
4710
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|