@virex-tech/paywallo-sdk 1.1.0 → 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 -140
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +498 -202
- 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
|
@@ -1030,6 +1030,13 @@ function getWebViewEmitter() {
|
|
|
1030
1030
|
}
|
|
1031
1031
|
return webViewEmitter;
|
|
1032
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
|
+
}
|
|
1033
1040
|
function PaywallWebView({
|
|
1034
1041
|
paywallId,
|
|
1035
1042
|
craftData,
|
|
@@ -1040,11 +1047,14 @@ function PaywallWebView({
|
|
|
1040
1047
|
onPurchase,
|
|
1041
1048
|
onClose,
|
|
1042
1049
|
onRestore,
|
|
1043
|
-
onReady
|
|
1050
|
+
onReady,
|
|
1051
|
+
onError
|
|
1044
1052
|
}) {
|
|
1045
1053
|
const paywallDataSent = (0, import_react4.useRef)(false);
|
|
1046
1054
|
const loadEndReceived = (0, import_react4.useRef)(false);
|
|
1047
1055
|
const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
|
|
1056
|
+
const onErrorRef = (0, import_react4.useRef)(onError);
|
|
1057
|
+
onErrorRef.current = onError;
|
|
1048
1058
|
const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
|
|
1049
1059
|
const webUrl = (0, import_react4.useMemo)(() => PaywalloClient.getWebUrl() ?? "http://localhost:3000", []);
|
|
1050
1060
|
const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
|
|
@@ -1104,17 +1114,51 @@ function PaywallWebView({
|
|
|
1104
1114
|
const message = parseWebViewMessage(event.data);
|
|
1105
1115
|
if (message) handleParsedMessage(message);
|
|
1106
1116
|
});
|
|
1117
|
+
const errorSub = emitter.addListener(
|
|
1118
|
+
"PaywalloWebViewError",
|
|
1119
|
+
(event) => {
|
|
1120
|
+
onErrorRef.current?.(parseErrorEvent(event));
|
|
1121
|
+
}
|
|
1122
|
+
);
|
|
1107
1123
|
return () => {
|
|
1108
1124
|
loadEndSub.remove();
|
|
1109
1125
|
messageSub.remove();
|
|
1126
|
+
errorSub.remove();
|
|
1110
1127
|
};
|
|
1111
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
|
+
}, []);
|
|
1112
1140
|
const handleLoadEnd = (0, import_react4.useCallback)(() => {
|
|
1113
1141
|
if (!loadEndReceived.current) {
|
|
1114
1142
|
loadEndReceived.current = true;
|
|
1115
1143
|
buildAndSetInjectionScript();
|
|
1116
1144
|
}
|
|
1117
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
|
+
}, []);
|
|
1118
1162
|
const handleMessage = (0, import_react4.useCallback)(
|
|
1119
1163
|
(event) => {
|
|
1120
1164
|
const message = parseWebViewMessage(event.nativeEvent.data);
|
|
@@ -1134,6 +1178,7 @@ function PaywallWebView({
|
|
|
1134
1178
|
injectedJavaScript: scriptToInject,
|
|
1135
1179
|
onMessage: handleMessage,
|
|
1136
1180
|
onLoadEnd: handleLoadEnd,
|
|
1181
|
+
onError: handleNativeError,
|
|
1137
1182
|
style: styles.webview
|
|
1138
1183
|
}
|
|
1139
1184
|
) });
|
|
@@ -1164,6 +1209,25 @@ function PaywallModal({
|
|
|
1164
1209
|
campaignId
|
|
1165
1210
|
);
|
|
1166
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
|
+
}, []);
|
|
1167
1231
|
const openAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(SCREEN_HEIGHT2)).current;
|
|
1168
1232
|
(0, import_react5.useEffect)(() => {
|
|
1169
1233
|
if (visible) {
|
|
@@ -1188,7 +1252,12 @@ function PaywallModal({
|
|
|
1188
1252
|
statusBarTranslucent: true,
|
|
1189
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: [
|
|
1190
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 }) }),
|
|
1191
|
-
!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)(
|
|
1192
1261
|
PaywallWebView,
|
|
1193
1262
|
{
|
|
1194
1263
|
paywallId: paywallConfig.id,
|
|
@@ -1199,39 +1268,67 @@ function PaywallModal({
|
|
|
1199
1268
|
isPurchasing,
|
|
1200
1269
|
onClose: handleClose,
|
|
1201
1270
|
onPurchase: handlePurchase,
|
|
1202
|
-
onRestore: handleRestore
|
|
1203
|
-
|
|
1204
|
-
|
|
1271
|
+
onRestore: handleRestore,
|
|
1272
|
+
onError: handleWebViewError
|
|
1273
|
+
},
|
|
1274
|
+
retryCount
|
|
1275
|
+
) })
|
|
1205
1276
|
] }) }) })
|
|
1206
1277
|
}
|
|
1207
1278
|
);
|
|
1208
1279
|
}
|
|
1209
1280
|
var styles2 = import_react_native5.StyleSheet.create({
|
|
1210
|
-
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: {
|
|
1211
1287
|
flex: 1,
|
|
1212
|
-
|
|
1288
|
+
justifyContent: "center",
|
|
1289
|
+
alignItems: "center",
|
|
1290
|
+
backgroundColor: "#111010",
|
|
1291
|
+
padding: 24
|
|
1213
1292
|
},
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
|
1219
1300
|
},
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1301
|
+
fallbackTitle: {
|
|
1302
|
+
fontSize: 18,
|
|
1303
|
+
fontWeight: "600",
|
|
1304
|
+
color: "#f5f5f5",
|
|
1305
|
+
marginBottom: 10,
|
|
1306
|
+
textAlign: "center"
|
|
1223
1307
|
},
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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,
|
|
1227
1320
|
alignItems: "center",
|
|
1228
|
-
|
|
1321
|
+
marginBottom: 12
|
|
1229
1322
|
},
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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)" }
|
|
1235
1332
|
});
|
|
1236
1333
|
|
|
1237
1334
|
// src/domains/paywall/PaywallErrorBoundary.tsx
|
|
@@ -1546,6 +1643,92 @@ var PaywalloAffiliate = {
|
|
|
1546
1643
|
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1547
1644
|
};
|
|
1548
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
|
+
|
|
1549
1732
|
// src/core/ApiClient.ts
|
|
1550
1733
|
var import_react_native7 = require("react-native");
|
|
1551
1734
|
|
|
@@ -2408,7 +2591,7 @@ var ApiCache = class {
|
|
|
2408
2591
|
// src/core/ApiClient.ts
|
|
2409
2592
|
var SDK_VERSION = "1.0.0";
|
|
2410
2593
|
var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
|
|
2411
|
-
var
|
|
2594
|
+
var _ApiClient = class _ApiClient {
|
|
2412
2595
|
constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2413
2596
|
this.cache = new ApiCache();
|
|
2414
2597
|
this.appKey = appKey;
|
|
@@ -2498,6 +2681,11 @@ var ApiClient = class {
|
|
|
2498
2681
|
const key = `${flagKey}:${distinctId}`;
|
|
2499
2682
|
const hit = this.cache.getVariant(key);
|
|
2500
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
|
+
}
|
|
2501
2689
|
try {
|
|
2502
2690
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2503
2691
|
if (res.status === 404) {
|
|
@@ -2512,6 +2700,7 @@ var ApiClient = class {
|
|
|
2512
2700
|
return { variant: null };
|
|
2513
2701
|
}
|
|
2514
2702
|
this.cache.setVariant(key, res.data);
|
|
2703
|
+
await this.writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2515
2704
|
return res.data;
|
|
2516
2705
|
} catch (error) {
|
|
2517
2706
|
this.log("Failed to get variant:", flagKey, error);
|
|
@@ -2593,6 +2782,35 @@ var ApiClient = class {
|
|
|
2593
2782
|
this.log("Subscription status:", result.hasActiveSubscription);
|
|
2594
2783
|
return result;
|
|
2595
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
|
+
}
|
|
2596
2814
|
async getConditionalFlag(flagKey, context) {
|
|
2597
2815
|
try {
|
|
2598
2816
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
@@ -2609,7 +2827,43 @@ var ApiClient = class {
|
|
|
2609
2827
|
return { value: false, flagKey };
|
|
2610
2828
|
}
|
|
2611
2829
|
}
|
|
2612
|
-
// ───
|
|
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
|
+
}
|
|
2613
2867
|
/**
|
|
2614
2868
|
* POST with offline queue fallback. Enqueues when offline or on network failure.
|
|
2615
2869
|
* Throws only when offlineQueueEnabled is false and the request fails.
|
|
@@ -2636,6 +2890,9 @@ var ApiClient = class {
|
|
|
2636
2890
|
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
2637
2891
|
}
|
|
2638
2892
|
};
|
|
2893
|
+
// ─── Private helpers ─────────────────────────────────────────────────────────
|
|
2894
|
+
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2895
|
+
var ApiClient = _ApiClient;
|
|
2639
2896
|
|
|
2640
2897
|
// src/domains/campaign/CampaignError.ts
|
|
2641
2898
|
var CampaignError = class extends PaywalloError {
|
|
@@ -2663,18 +2920,21 @@ var CampaignGateService = class {
|
|
|
2663
2920
|
if (!distinctId) return null;
|
|
2664
2921
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2665
2922
|
}
|
|
2666
|
-
async presentCampaign(apiClient, placement, paywallPresenter, campaignPresenter, context) {
|
|
2923
|
+
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2667
2924
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2668
2925
|
if (!campaign) {
|
|
2669
2926
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2670
2927
|
}
|
|
2928
|
+
if (!context?.forceShow && await hasActive()) {
|
|
2929
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
2930
|
+
}
|
|
2671
2931
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2672
2932
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2673
2933
|
return { ...await paywallPresenter(campaign.paywall.placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2674
2934
|
}
|
|
2675
2935
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2676
2936
|
if (await hasActive()) return true;
|
|
2677
|
-
const r = await this.presentCampaign(apiClient, placement, paywallPresenter, campaignPresenter, context);
|
|
2937
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2678
2938
|
return r.purchased || r.restored;
|
|
2679
2939
|
}
|
|
2680
2940
|
async preloadCampaign(apiClient, placement, context) {
|
|
@@ -3211,6 +3471,20 @@ function isValidEventName(name) {
|
|
|
3211
3471
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3212
3472
|
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3213
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
|
+
}
|
|
3214
3488
|
var _PaywalloClientClass = class _PaywalloClientClass {
|
|
3215
3489
|
constructor() {
|
|
3216
3490
|
this.config = null;
|
|
@@ -3227,27 +3501,27 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3227
3501
|
this.initAttempts = 0;
|
|
3228
3502
|
}
|
|
3229
3503
|
async init(config) {
|
|
3230
|
-
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");
|
|
3231
3505
|
if (this.config && this.apiClient) {
|
|
3232
|
-
console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3506
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3233
3507
|
return;
|
|
3234
3508
|
}
|
|
3235
3509
|
if (this.initPromise) {
|
|
3236
|
-
console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3510
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3237
3511
|
await this.initPromise;
|
|
3238
|
-
console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3512
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3239
3513
|
return;
|
|
3240
3514
|
}
|
|
3241
3515
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3242
|
-
console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3516
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3243
3517
|
this.pendingConfig = config;
|
|
3244
3518
|
this.initAttempts = 0;
|
|
3245
3519
|
this.initPromise = this.initWithRetry(config);
|
|
3246
3520
|
try {
|
|
3247
3521
|
await this.initPromise;
|
|
3248
|
-
console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3522
|
+
if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3249
3523
|
} catch (error) {
|
|
3250
|
-
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));
|
|
3251
3525
|
this.initPromise = null;
|
|
3252
3526
|
this.config = null;
|
|
3253
3527
|
this.apiClient = null;
|
|
@@ -3317,26 +3591,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3317
3591
|
async doInit(config) {
|
|
3318
3592
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3319
3593
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3320
|
-
console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3594
|
+
if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3321
3595
|
await Promise.all([
|
|
3322
3596
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3323
3597
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3324
3598
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3325
3599
|
)
|
|
3326
3600
|
]);
|
|
3327
|
-
console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3328
|
-
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...");
|
|
3329
3603
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3330
3604
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3331
3605
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3332
|
-
console.warn("[Paywallo INIT]", "Step 2 DONE");
|
|
3333
|
-
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...");
|
|
3334
3608
|
await Promise.all([
|
|
3335
3609
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3336
3610
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3337
3611
|
]);
|
|
3338
3612
|
const distinctId = identityManager.getDistinctId();
|
|
3339
|
-
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");
|
|
3340
3614
|
if (config.autoStartSession !== false) {
|
|
3341
3615
|
sessionManager.startSession().catch(() => {
|
|
3342
3616
|
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
@@ -3345,7 +3619,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3345
3619
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3346
3620
|
});
|
|
3347
3621
|
this.config = config;
|
|
3348
|
-
console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
|
|
3622
|
+
if (config.debug) console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
|
|
3349
3623
|
}
|
|
3350
3624
|
async identify(options) {
|
|
3351
3625
|
this.ensureInitialized();
|
|
@@ -3355,7 +3629,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3355
3629
|
const apiClient = this.getApiClientOrThrow();
|
|
3356
3630
|
const distinctId = identityManager.getDistinctId();
|
|
3357
3631
|
if (!distinctId) {
|
|
3358
|
-
console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3632
|
+
if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3359
3633
|
return;
|
|
3360
3634
|
}
|
|
3361
3635
|
if (!isValidEventName(eventName)) {
|
|
@@ -3364,7 +3638,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3364
3638
|
}
|
|
3365
3639
|
const sessionId = sessionManager.getSessionId();
|
|
3366
3640
|
if (eventName === "$purchase_completed") {
|
|
3367
|
-
console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3641
|
+
if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3368
3642
|
distinctId: distinctId.substring(0, 8) + "...",
|
|
3369
3643
|
productId: options?.properties?.productId,
|
|
3370
3644
|
priceUsd: options?.properties?.priceUsd,
|
|
@@ -3373,14 +3647,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3373
3647
|
placement: options?.properties?.placement
|
|
3374
3648
|
}));
|
|
3375
3649
|
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3376
|
-
console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3650
|
+
if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3377
3651
|
distinctId: distinctId.substring(0, 8) + "...",
|
|
3378
3652
|
placement: options?.properties?.placement,
|
|
3379
3653
|
variantKey: options?.properties?.variantKey,
|
|
3380
3654
|
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3381
3655
|
}));
|
|
3382
3656
|
} else {
|
|
3383
|
-
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) + "...");
|
|
3384
3658
|
}
|
|
3385
3659
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3386
3660
|
...identityManager.getProperties(),
|
|
@@ -3431,15 +3705,25 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3431
3705
|
...sessionId && { sessionId }
|
|
3432
3706
|
});
|
|
3433
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
|
+
}
|
|
3434
3718
|
async getVariant(flagKey) {
|
|
3435
3719
|
try {
|
|
3436
3720
|
const distinctId = identityManager.getDistinctId();
|
|
3437
3721
|
if (!distinctId) {
|
|
3438
|
-
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);
|
|
3439
3723
|
return { variant: null };
|
|
3440
3724
|
}
|
|
3441
3725
|
const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3442
|
-
console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3726
|
+
if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3443
3727
|
return result;
|
|
3444
3728
|
} catch (error) {
|
|
3445
3729
|
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
@@ -3447,6 +3731,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3447
3731
|
return { variant: null };
|
|
3448
3732
|
}
|
|
3449
3733
|
}
|
|
3734
|
+
async evaluateFlags(keys) {
|
|
3735
|
+
this.ensureInitialized();
|
|
3736
|
+
return this.getApiClientOrThrow().evaluateFlags(keys, identityManager.getDistinctId());
|
|
3737
|
+
}
|
|
3450
3738
|
async getConditionalFlag(flagKey, context) {
|
|
3451
3739
|
try {
|
|
3452
3740
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3472,7 +3760,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3472
3760
|
}
|
|
3473
3761
|
async presentCampaign(placement, context) {
|
|
3474
3762
|
this.ensureInitialized();
|
|
3475
|
-
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);
|
|
3476
3764
|
}
|
|
3477
3765
|
async hasActiveSubscription() {
|
|
3478
3766
|
this.ensureInitialized();
|
|
@@ -3480,11 +3768,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3480
3768
|
if (this.activeChecker) return await this.activeChecker();
|
|
3481
3769
|
const distinctId = identityManager.getDistinctId();
|
|
3482
3770
|
if (!distinctId || !this.apiClient) return false;
|
|
3483
|
-
|
|
3771
|
+
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
3772
|
+
return status.hasActiveSubscription;
|
|
3484
3773
|
} catch {
|
|
3485
|
-
if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014
|
|
3774
|
+
if (this.config?.debug) console.warn("[Paywallo]", "hasActiveSubscription failed \u2014 using persistent cache");
|
|
3486
3775
|
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", "hasActiveSubscription failed");
|
|
3487
|
-
return
|
|
3776
|
+
return resolveClientOfflineFromCache();
|
|
3488
3777
|
}
|
|
3489
3778
|
}
|
|
3490
3779
|
async getSubscription() {
|
|
@@ -3513,6 +3802,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3513
3802
|
const wasDebug = this.config?.debug ?? false;
|
|
3514
3803
|
await sessionManager.endSession();
|
|
3515
3804
|
await identityManager.reset();
|
|
3805
|
+
await subscriptionCache.invalidate();
|
|
3516
3806
|
queueProcessor.dispose();
|
|
3517
3807
|
offlineQueue.dispose();
|
|
3518
3808
|
networkMonitor.dispose();
|
|
@@ -3869,86 +4159,6 @@ var import_react13 = require("react");
|
|
|
3869
4159
|
|
|
3870
4160
|
// src/domains/subscription/SubscriptionManager.ts
|
|
3871
4161
|
var import_react_native13 = require("react-native");
|
|
3872
|
-
|
|
3873
|
-
// src/domains/subscription/SubscriptionCache.ts
|
|
3874
|
-
var CACHE_KEY = "subscription_cache";
|
|
3875
|
-
var DEFAULT_TTL = 5 * 60 * 1e3;
|
|
3876
|
-
var SubscriptionCache = class {
|
|
3877
|
-
constructor(ttl = DEFAULT_TTL) {
|
|
3878
|
-
this.memoryCache = null;
|
|
3879
|
-
this.ttl = ttl;
|
|
3880
|
-
this.storage = new SecureStorage();
|
|
3881
|
-
}
|
|
3882
|
-
async get() {
|
|
3883
|
-
if (this.memoryCache && !this.isExpired(this.memoryCache)) {
|
|
3884
|
-
return this.memoryCache;
|
|
3885
|
-
}
|
|
3886
|
-
try {
|
|
3887
|
-
const stored = await this.storage.get(CACHE_KEY);
|
|
3888
|
-
if (!stored) {
|
|
3889
|
-
return null;
|
|
3890
|
-
}
|
|
3891
|
-
const cached = JSON.parse(stored);
|
|
3892
|
-
if (cached.data.subscription?.expiresAt) {
|
|
3893
|
-
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
3894
|
-
}
|
|
3895
|
-
if (cached.data.subscription?.originalPurchaseDate) {
|
|
3896
|
-
cached.data.subscription.originalPurchaseDate = new Date(
|
|
3897
|
-
cached.data.subscription.originalPurchaseDate
|
|
3898
|
-
);
|
|
3899
|
-
}
|
|
3900
|
-
if (cached.data.subscription?.latestPurchaseDate) {
|
|
3901
|
-
cached.data.subscription.latestPurchaseDate = new Date(
|
|
3902
|
-
cached.data.subscription.latestPurchaseDate
|
|
3903
|
-
);
|
|
3904
|
-
}
|
|
3905
|
-
if (cached.data.subscription?.cancellationDate) {
|
|
3906
|
-
cached.data.subscription.cancellationDate = new Date(
|
|
3907
|
-
cached.data.subscription.cancellationDate
|
|
3908
|
-
);
|
|
3909
|
-
}
|
|
3910
|
-
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
3911
|
-
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
3912
|
-
cached.data.subscription.gracePeriodExpiresAt
|
|
3913
|
-
);
|
|
3914
|
-
}
|
|
3915
|
-
cached.isStale = this.isExpired(cached);
|
|
3916
|
-
if (!cached.isStale) {
|
|
3917
|
-
this.memoryCache = cached;
|
|
3918
|
-
}
|
|
3919
|
-
return cached;
|
|
3920
|
-
} catch {
|
|
3921
|
-
return null;
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
async set(data) {
|
|
3925
|
-
const cached = {
|
|
3926
|
-
data,
|
|
3927
|
-
cachedAt: Date.now(),
|
|
3928
|
-
isStale: false
|
|
3929
|
-
};
|
|
3930
|
-
this.memoryCache = cached;
|
|
3931
|
-
try {
|
|
3932
|
-
await this.storage.set(CACHE_KEY, JSON.stringify(cached));
|
|
3933
|
-
} catch {
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
async invalidate() {
|
|
3937
|
-
this.memoryCache = null;
|
|
3938
|
-
try {
|
|
3939
|
-
await this.storage.remove(CACHE_KEY);
|
|
3940
|
-
} catch {
|
|
3941
|
-
}
|
|
3942
|
-
}
|
|
3943
|
-
isExpired(cached) {
|
|
3944
|
-
return Date.now() - cached.cachedAt > this.ttl;
|
|
3945
|
-
}
|
|
3946
|
-
setTTL(ttl) {
|
|
3947
|
-
this.ttl = ttl;
|
|
3948
|
-
}
|
|
3949
|
-
};
|
|
3950
|
-
|
|
3951
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3952
4162
|
var SubscriptionManagerClass = class {
|
|
3953
4163
|
constructor() {
|
|
3954
4164
|
this.config = null;
|
|
@@ -4364,12 +4574,54 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4364
4574
|
// src/hooks/useSubscriptionSync.ts
|
|
4365
4575
|
var import_react17 = require("react");
|
|
4366
4576
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4367
|
-
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
|
+
}
|
|
4368
4619
|
function useSubscriptionSync() {
|
|
4369
4620
|
const [subscription, setSubscription] = (0, import_react17.useState)(null);
|
|
4370
4621
|
const cacheRef = (0, import_react17.useRef)(null);
|
|
4371
4622
|
const invalidateCache = (0, import_react17.useCallback)(() => {
|
|
4372
4623
|
cacheRef.current = null;
|
|
4624
|
+
void subscriptionCache.invalidate();
|
|
4373
4625
|
}, []);
|
|
4374
4626
|
const fetchAndCache = (0, import_react17.useCallback)(async () => {
|
|
4375
4627
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4382,6 +4634,7 @@ function useSubscriptionSync() {
|
|
|
4382
4634
|
} : null;
|
|
4383
4635
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4384
4636
|
setSubscription(sub);
|
|
4637
|
+
void subscriptionCache.set(toDomainResponse(status));
|
|
4385
4638
|
return sub;
|
|
4386
4639
|
}, []);
|
|
4387
4640
|
const hasActiveSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4390,10 +4643,28 @@ function useSubscriptionSync() {
|
|
|
4390
4643
|
if (!apiClient || !distinctId) return false;
|
|
4391
4644
|
const cached = cacheRef.current;
|
|
4392
4645
|
if (cached && cached.expiresAt > Date.now()) {
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
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 {
|
|
4397
4668
|
}
|
|
4398
4669
|
try {
|
|
4399
4670
|
const status = await apiClient.getSubscriptionStatus(distinctId);
|
|
@@ -4403,11 +4674,12 @@ function useSubscriptionSync() {
|
|
|
4403
4674
|
} : null;
|
|
4404
4675
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4405
4676
|
setSubscription(sub);
|
|
4677
|
+
void subscriptionCache.set(toDomainResponse(status));
|
|
4406
4678
|
return status.hasActiveSubscription;
|
|
4407
4679
|
} catch {
|
|
4408
|
-
return
|
|
4680
|
+
return await resolveOfflineFromCache();
|
|
4409
4681
|
}
|
|
4410
|
-
}, []);
|
|
4682
|
+
}, [fetchAndCache]);
|
|
4411
4683
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
4412
4684
|
const apiClient = PaywalloClient.getApiClient();
|
|
4413
4685
|
const distinctId = PaywalloClient.getDistinctId();
|
|
@@ -4417,7 +4689,22 @@ function useSubscriptionSync() {
|
|
|
4417
4689
|
try {
|
|
4418
4690
|
return await fetchAndCache();
|
|
4419
4691
|
} catch {
|
|
4420
|
-
|
|
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
|
+
}
|
|
4421
4708
|
}
|
|
4422
4709
|
}, [fetchAndCache]);
|
|
4423
4710
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|