@virex-tech/paywallo-sdk 1.1.1 → 1.3.0
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 +83 -42
- package/dist/index.d.ts +83 -42
- package/dist/index.js +1109 -600
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1277 -759
- 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.mjs
CHANGED
|
@@ -38,8 +38,17 @@ var CLIENT_ERROR_CODES = {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// src/domains/paywall/PaywallModal.tsx
|
|
41
|
-
import { useEffect as useEffect3, useRef as useRef4 } from "react";
|
|
42
|
-
import {
|
|
41
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4, useState as useState4 } from "react";
|
|
42
|
+
import {
|
|
43
|
+
Animated as Animated2,
|
|
44
|
+
Dimensions as Dimensions2,
|
|
45
|
+
Easing,
|
|
46
|
+
Modal,
|
|
47
|
+
Pressable,
|
|
48
|
+
StyleSheet as StyleSheet2,
|
|
49
|
+
Text,
|
|
50
|
+
View as View2
|
|
51
|
+
} from "react-native";
|
|
43
52
|
|
|
44
53
|
// src/domains/paywall/usePaywallActions.ts
|
|
45
54
|
import { useCallback as useCallback2, useRef, useState } from "react";
|
|
@@ -136,9 +145,6 @@ var SecureStorage = class {
|
|
|
136
145
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
137
146
|
constructor(_debug = false) {
|
|
138
147
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
|
|
141
|
-
*/
|
|
142
148
|
async get(key) {
|
|
143
149
|
const keychainValue = await this.getFromKeychain(key);
|
|
144
150
|
if (keychainValue) {
|
|
@@ -154,9 +160,6 @@ var SecureStorage = class {
|
|
|
154
160
|
return null;
|
|
155
161
|
}
|
|
156
162
|
}
|
|
157
|
-
/**
|
|
158
|
-
* Set a value in both Keychain (if available) and AsyncStorage.
|
|
159
|
-
*/
|
|
160
163
|
async set(key, value) {
|
|
161
164
|
try {
|
|
162
165
|
await Promise.all([
|
|
@@ -220,11 +223,13 @@ var secureStorage = new SecureStorage();
|
|
|
220
223
|
|
|
221
224
|
// src/domains/identity/IdentityManager.ts
|
|
222
225
|
var DEVICE_ID_KEY = "device_id";
|
|
226
|
+
var ANON_ID_KEY = "anon_id";
|
|
223
227
|
var USER_EMAIL_KEY = "user_email";
|
|
224
228
|
var USER_PROPERTIES_KEY = "user_properties";
|
|
225
229
|
var IdentityManager = class {
|
|
226
230
|
constructor() {
|
|
227
231
|
this.deviceId = null;
|
|
232
|
+
this.anonId = null;
|
|
228
233
|
this.email = null;
|
|
229
234
|
this.properties = {};
|
|
230
235
|
this.apiClient = null;
|
|
@@ -251,23 +256,23 @@ var IdentityManager = class {
|
|
|
251
256
|
try {
|
|
252
257
|
if (DeviceInfo) {
|
|
253
258
|
this.deviceId = await DeviceInfo.getUniqueId();
|
|
254
|
-
this.log("Got device ID from DeviceInfo:", this.deviceId);
|
|
255
259
|
} else {
|
|
256
260
|
this.deviceId = generateUUID();
|
|
257
|
-
this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
|
|
258
261
|
}
|
|
259
262
|
} catch {
|
|
260
263
|
this.deviceId = generateUUID();
|
|
261
|
-
this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
|
|
262
264
|
}
|
|
263
265
|
const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
|
|
264
266
|
if (!stored) {
|
|
265
|
-
this.log("Failed to persist device ID to secure storage, using fallback");
|
|
266
267
|
await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
|
|
267
|
-
this.log("Fallback also failed \u2014 device ID only in memory");
|
|
268
268
|
});
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
+
if (!this.anonId) {
|
|
272
|
+
this.anonId = "$paywallo_anon:" + generateUUID();
|
|
273
|
+
await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
|
|
274
|
+
});
|
|
275
|
+
}
|
|
271
276
|
this.initialized = true;
|
|
272
277
|
return this.deviceId ?? "";
|
|
273
278
|
}
|
|
@@ -291,21 +296,18 @@ var IdentityManager = class {
|
|
|
291
296
|
this.properties = { ...this.properties, ...properties };
|
|
292
297
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
293
298
|
}
|
|
294
|
-
if (this.apiClient && this.
|
|
299
|
+
if (this.apiClient && this.anonId) {
|
|
295
300
|
try {
|
|
296
|
-
await this.apiClient.identify(this.
|
|
297
|
-
|
|
298
|
-
} catch (error) {
|
|
299
|
-
this.log("Failed to update identity on server:", error);
|
|
301
|
+
await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
|
|
302
|
+
} catch {
|
|
300
303
|
}
|
|
301
304
|
}
|
|
302
305
|
}
|
|
303
306
|
getDistinctId() {
|
|
304
|
-
if (!this.initialized || !this.
|
|
305
|
-
if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
|
|
307
|
+
if (!this.initialized || !this.anonId) {
|
|
306
308
|
return "";
|
|
307
309
|
}
|
|
308
|
-
return this.
|
|
310
|
+
return this.anonId;
|
|
309
311
|
}
|
|
310
312
|
getDeviceId() {
|
|
311
313
|
return this.deviceId;
|
|
@@ -320,24 +322,22 @@ var IdentityManager = class {
|
|
|
320
322
|
this.ensureInitialized();
|
|
321
323
|
this.properties = { ...this.properties, ...properties };
|
|
322
324
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
323
|
-
if (this.apiClient && this.
|
|
325
|
+
if (this.apiClient && this.anonId) {
|
|
324
326
|
try {
|
|
325
|
-
await this.apiClient.identify(this.
|
|
326
|
-
} catch
|
|
327
|
-
this.log("Failed to update properties on server:", error);
|
|
327
|
+
await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
|
|
328
|
+
} catch {
|
|
328
329
|
}
|
|
329
330
|
}
|
|
330
331
|
}
|
|
331
332
|
async reset() {
|
|
333
|
+
const newAnonId = "$paywallo_anon:" + generateUUID();
|
|
334
|
+
this.anonId = newAnonId;
|
|
332
335
|
this.email = null;
|
|
333
336
|
this.properties = {};
|
|
334
|
-
this.initialized = false;
|
|
335
|
-
this.apiClient = null;
|
|
336
|
-
this.deviceId = null;
|
|
337
337
|
await Promise.all([
|
|
338
|
+
this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
|
|
338
339
|
this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
|
|
339
|
-
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
340
|
-
this.secureStorage?.remove("device_id_fallback") ?? Promise.resolve(false)
|
|
340
|
+
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
341
341
|
]);
|
|
342
342
|
}
|
|
343
343
|
getState() {
|
|
@@ -354,21 +354,23 @@ var IdentityManager = class {
|
|
|
354
354
|
if (!this.secureStorage) {
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
|
-
const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
357
|
+
const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
358
358
|
this.secureStorage.get(DEVICE_ID_KEY),
|
|
359
|
+
this.secureStorage.get(ANON_ID_KEY),
|
|
359
360
|
this.secureStorage.get(USER_EMAIL_KEY),
|
|
360
361
|
this.secureStorage.get(USER_PROPERTIES_KEY)
|
|
361
362
|
]);
|
|
362
363
|
if (storedDeviceId) {
|
|
363
364
|
this.deviceId = storedDeviceId;
|
|
364
|
-
this.log("Loaded device ID from storage:", this.deviceId);
|
|
365
365
|
} else {
|
|
366
366
|
const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
|
|
367
367
|
if (fallbackDeviceId) {
|
|
368
368
|
this.deviceId = fallbackDeviceId;
|
|
369
|
-
this.log("Loaded device ID from fallback storage:", this.deviceId);
|
|
370
369
|
}
|
|
371
370
|
}
|
|
371
|
+
if (storedAnonId) {
|
|
372
|
+
this.anonId = storedAnonId;
|
|
373
|
+
}
|
|
372
374
|
if (storedEmail) {
|
|
373
375
|
this.email = storedEmail;
|
|
374
376
|
}
|
|
@@ -388,11 +390,6 @@ var IdentityManager = class {
|
|
|
388
390
|
);
|
|
389
391
|
}
|
|
390
392
|
}
|
|
391
|
-
log(...args) {
|
|
392
|
-
if (this.debug) {
|
|
393
|
-
console.warn("[Paywallo Identity]", ...args);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
393
|
};
|
|
397
394
|
var identityManager = new IdentityManager();
|
|
398
395
|
|
|
@@ -477,7 +474,6 @@ var nativeStoreKit = {
|
|
|
477
474
|
isAvailable,
|
|
478
475
|
async getProducts(productIds) {
|
|
479
476
|
if (!PaywalloStoreKitNative) {
|
|
480
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
|
|
481
477
|
return [];
|
|
482
478
|
}
|
|
483
479
|
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
@@ -548,7 +544,6 @@ var IAPService = class {
|
|
|
548
544
|
}
|
|
549
545
|
return products;
|
|
550
546
|
} catch (error) {
|
|
551
|
-
if (this.debug) console.warn("[Paywallo][Products] StoreKit error:", error);
|
|
552
547
|
return [];
|
|
553
548
|
}
|
|
554
549
|
}
|
|
@@ -558,35 +553,46 @@ var IAPService = class {
|
|
|
558
553
|
getCachedProducts() {
|
|
559
554
|
return new Map(this.productsCache);
|
|
560
555
|
}
|
|
556
|
+
async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
|
|
557
|
+
let lastError;
|
|
558
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
559
|
+
try {
|
|
560
|
+
return await attempt();
|
|
561
|
+
} catch (error) {
|
|
562
|
+
lastError = error;
|
|
563
|
+
if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
throw lastError;
|
|
567
|
+
}
|
|
561
568
|
async validateWithServer(purchase, productId, options) {
|
|
562
569
|
const apiClient = this.getApiClient();
|
|
563
570
|
const product = this.productsCache.get(productId);
|
|
564
571
|
const platform = Platform2.OS;
|
|
565
572
|
const distinctId = PaywalloClient.getDistinctId();
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
573
|
+
return this.validateWithRetry(
|
|
574
|
+
() => apiClient.validatePurchase(
|
|
575
|
+
platform,
|
|
576
|
+
purchase.receipt,
|
|
577
|
+
purchase.productId,
|
|
578
|
+
purchase.transactionId,
|
|
579
|
+
product?.priceValue ?? 0,
|
|
580
|
+
product?.currency ?? "USD",
|
|
581
|
+
this.getDeviceCountry(),
|
|
582
|
+
distinctId,
|
|
583
|
+
options?.paywallPlacement,
|
|
584
|
+
options?.variantKey
|
|
585
|
+
)
|
|
577
586
|
);
|
|
578
|
-
return validation;
|
|
579
587
|
}
|
|
580
588
|
async finishTransaction(transactionId) {
|
|
581
589
|
try {
|
|
582
590
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
583
591
|
} catch (finishError) {
|
|
584
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Failed to finish transaction:", finishError instanceof Error ? finishError.message : finishError);
|
|
585
592
|
}
|
|
586
593
|
}
|
|
587
594
|
async purchase(productId, options) {
|
|
588
595
|
if (!nativeStoreKit.isAvailable()) {
|
|
589
|
-
if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
|
|
590
596
|
return {
|
|
591
597
|
success: false,
|
|
592
598
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
@@ -597,11 +603,11 @@ var IAPService = class {
|
|
|
597
603
|
if (!purchase) {
|
|
598
604
|
return { success: false };
|
|
599
605
|
}
|
|
606
|
+
options?.onValidating?.();
|
|
600
607
|
let validation = null;
|
|
601
608
|
try {
|
|
602
609
|
validation = await this.validateWithServer(purchase, productId, options);
|
|
603
610
|
} catch (validationError) {
|
|
604
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
|
|
605
611
|
return {
|
|
606
612
|
success: false,
|
|
607
613
|
error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
|
|
@@ -617,7 +623,6 @@ var IAPService = class {
|
|
|
617
623
|
return { success: true, purchase };
|
|
618
624
|
} catch (error) {
|
|
619
625
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
620
|
-
if (this.debug) console.warn("[Paywallo][Purchase] FAILED:", e.message);
|
|
621
626
|
return { success: false, error: e };
|
|
622
627
|
}
|
|
623
628
|
}
|
|
@@ -653,14 +658,12 @@ var IAPService = class {
|
|
|
653
658
|
try {
|
|
654
659
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
655
660
|
} catch (finishError) {
|
|
656
|
-
if (this.debug) console.warn("[Paywallo][Restore] Failed to finish tx:", tx.transactionId, finishError instanceof Error ? finishError.message : finishError);
|
|
657
661
|
}
|
|
658
662
|
validated.push(tx);
|
|
659
663
|
}
|
|
660
664
|
}
|
|
661
665
|
return validated;
|
|
662
666
|
} catch (error) {
|
|
663
|
-
if (this.debug) console.warn("[Paywallo][Restore] \u274C FAILED:", error instanceof Error ? error.message : error);
|
|
664
667
|
return [];
|
|
665
668
|
}
|
|
666
669
|
}
|
|
@@ -728,7 +731,7 @@ function usePaywallTracking() {
|
|
|
728
731
|
|
|
729
732
|
// src/domains/paywall/usePaywallActions.ts
|
|
730
733
|
var SCREEN_HEIGHT = Dimensions.get("window").height;
|
|
731
|
-
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
|
|
734
|
+
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
732
735
|
const [isPurchasing, setIsPurchasing] = useState(false);
|
|
733
736
|
const [isClosing, setIsClosing] = useState(false);
|
|
734
737
|
const slideAnim = useRef(new Animated.Value(0)).current;
|
|
@@ -737,7 +740,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
737
740
|
const handleClose = useCallback2(() => {
|
|
738
741
|
if (isClosing) return;
|
|
739
742
|
setIsClosing(true);
|
|
740
|
-
|
|
743
|
+
const animTarget = openAnim ?? slideAnim;
|
|
744
|
+
Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
|
|
741
745
|
if (paywallConfig) {
|
|
742
746
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
743
747
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -746,7 +750,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
746
750
|
setIsClosing(false);
|
|
747
751
|
onResult({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
748
752
|
});
|
|
749
|
-
}, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
753
|
+
}, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
750
754
|
const handlePurchase = useCallback2(async (productId) => {
|
|
751
755
|
if (isPurchasing) return;
|
|
752
756
|
setIsPurchasing(true);
|
|
@@ -764,7 +768,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
764
768
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
765
769
|
}
|
|
766
770
|
} catch (error) {
|
|
767
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
|
|
768
771
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
769
772
|
} finally {
|
|
770
773
|
setIsPurchasing(false);
|
|
@@ -785,7 +788,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
785
788
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
786
789
|
}
|
|
787
790
|
} catch (error) {
|
|
788
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
|
|
789
791
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
790
792
|
} finally {
|
|
791
793
|
setIsPurchasing(false);
|
|
@@ -860,11 +862,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
860
862
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
861
863
|
const map = /* @__PURE__ */ new Map();
|
|
862
864
|
for (const p of storeProducts) map.set(p.productId, p);
|
|
863
|
-
if (PaywalloClient.getConfig()?.debug) {
|
|
864
|
-
for (const [id, p] of map) {
|
|
865
|
-
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"}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
865
|
setProducts(map);
|
|
869
866
|
} catch {
|
|
870
867
|
setProducts(/* @__PURE__ */ new Map());
|
|
@@ -885,7 +882,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
885
882
|
}
|
|
886
883
|
} catch (err) {
|
|
887
884
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
888
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
|
|
889
885
|
setError(e);
|
|
890
886
|
}
|
|
891
887
|
};
|
|
@@ -894,18 +890,103 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
894
890
|
return { paywallConfig, products, error };
|
|
895
891
|
}
|
|
896
892
|
|
|
893
|
+
// src/utils/localization.ts
|
|
894
|
+
import { NativeModules as NativeModules2, Platform as Platform3 } from "react-native";
|
|
895
|
+
var currentLanguage = "pt-BR";
|
|
896
|
+
var defaultLanguage = "pt-BR";
|
|
897
|
+
function setCurrentLanguage(language) {
|
|
898
|
+
currentLanguage = language;
|
|
899
|
+
}
|
|
900
|
+
function setDefaultLanguage(language) {
|
|
901
|
+
defaultLanguage = language;
|
|
902
|
+
}
|
|
903
|
+
function getCurrentLanguage() {
|
|
904
|
+
return currentLanguage;
|
|
905
|
+
}
|
|
906
|
+
function getDefaultLanguage() {
|
|
907
|
+
return defaultLanguage;
|
|
908
|
+
}
|
|
909
|
+
function getLocalizedString(text) {
|
|
910
|
+
if (!text) return "";
|
|
911
|
+
if (typeof text === "string") return text;
|
|
912
|
+
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
913
|
+
}
|
|
914
|
+
function detectDeviceLanguage() {
|
|
915
|
+
try {
|
|
916
|
+
if (Platform3.OS === "ios") {
|
|
917
|
+
const settings = NativeModules2.SettingsManager?.settings;
|
|
918
|
+
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
919
|
+
}
|
|
920
|
+
return NativeModules2.I18nManager?.localeIdentifier ?? "en-US";
|
|
921
|
+
} catch {
|
|
922
|
+
return "en-US";
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
var SDK_STRINGS = {
|
|
926
|
+
paywallErrorTitle: {
|
|
927
|
+
"pt-BR": "Algo deu errado",
|
|
928
|
+
"pt": "Algo deu errado",
|
|
929
|
+
"en": "Something went wrong",
|
|
930
|
+
"en-US": "Something went wrong",
|
|
931
|
+
"en-GB": "Something went wrong",
|
|
932
|
+
"es": "Algo sali\xF3 mal",
|
|
933
|
+
"es-419": "Algo sali\xF3 mal"
|
|
934
|
+
},
|
|
935
|
+
paywallRetryButton: {
|
|
936
|
+
"pt-BR": "Tentar novamente",
|
|
937
|
+
"pt": "Tentar novamente",
|
|
938
|
+
"en": "Try again",
|
|
939
|
+
"en-US": "Try again",
|
|
940
|
+
"en-GB": "Try again",
|
|
941
|
+
"es": "Reintentar",
|
|
942
|
+
"es-419": "Reintentar"
|
|
943
|
+
},
|
|
944
|
+
paywallCloseButton: {
|
|
945
|
+
"pt-BR": "Fechar",
|
|
946
|
+
"pt": "Fechar",
|
|
947
|
+
"en": "Close",
|
|
948
|
+
"en-US": "Close",
|
|
949
|
+
"en-GB": "Close",
|
|
950
|
+
"es": "Cerrar",
|
|
951
|
+
"es-419": "Cerrar"
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
function getSdkString(key) {
|
|
955
|
+
const map = SDK_STRINGS[key];
|
|
956
|
+
const lang = currentLanguage;
|
|
957
|
+
if (lang in map) return map[lang];
|
|
958
|
+
const base = lang.split("-")[0];
|
|
959
|
+
if (base && base in map) return map[base];
|
|
960
|
+
if (defaultLanguage in map) return map[defaultLanguage];
|
|
961
|
+
return Object.values(map)[0];
|
|
962
|
+
}
|
|
963
|
+
function initLocalization(options) {
|
|
964
|
+
if (options?.defaultLanguage) {
|
|
965
|
+
defaultLanguage = options.defaultLanguage;
|
|
966
|
+
}
|
|
967
|
+
if (options?.detectDevice !== false) {
|
|
968
|
+
const deviceLanguage = detectDeviceLanguage();
|
|
969
|
+
currentLanguage = deviceLanguage;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
897
973
|
// src/domains/paywall/PaywallWebView.tsx
|
|
898
974
|
import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState3 } from "react";
|
|
899
975
|
import {
|
|
900
976
|
Linking,
|
|
901
977
|
NativeEventEmitter,
|
|
902
|
-
NativeModules as
|
|
978
|
+
NativeModules as NativeModules3,
|
|
903
979
|
requireNativeComponent,
|
|
904
980
|
StyleSheet,
|
|
905
981
|
View
|
|
906
982
|
} from "react-native";
|
|
907
983
|
|
|
908
984
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
985
|
+
function deriveMessageId(message) {
|
|
986
|
+
if (message.id) return message.id;
|
|
987
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
988
|
+
return `${message.type}:${payloadKey}`;
|
|
989
|
+
}
|
|
909
990
|
function parseWebViewMessage(raw) {
|
|
910
991
|
try {
|
|
911
992
|
const parsed = JSON.parse(raw);
|
|
@@ -929,7 +1010,7 @@ function buildPaywallInjectionScript(data) {
|
|
|
929
1010
|
secondaryProductId: data.secondaryProductId
|
|
930
1011
|
}
|
|
931
1012
|
};
|
|
932
|
-
return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}
|
|
1013
|
+
return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}}t();})();true;`;
|
|
933
1014
|
}
|
|
934
1015
|
function buildPurchaseStateScript(isPurchasing) {
|
|
935
1016
|
return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
|
|
@@ -947,10 +1028,17 @@ function getNativeWebView() {
|
|
|
947
1028
|
var webViewEmitter = null;
|
|
948
1029
|
function getWebViewEmitter() {
|
|
949
1030
|
if (!webViewEmitter) {
|
|
950
|
-
webViewEmitter = new NativeEventEmitter(
|
|
1031
|
+
webViewEmitter = new NativeEventEmitter(NativeModules3.PaywalloWebViewModule);
|
|
951
1032
|
}
|
|
952
1033
|
return webViewEmitter;
|
|
953
1034
|
}
|
|
1035
|
+
function parseErrorEvent(raw) {
|
|
1036
|
+
return {
|
|
1037
|
+
code: typeof raw["code"] === "number" ? raw["code"] : -1,
|
|
1038
|
+
description: typeof raw["description"] === "string" ? raw["description"] : "WebView error",
|
|
1039
|
+
url: typeof raw["url"] === "string" ? raw["url"] : void 0
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
954
1042
|
function PaywallWebView({
|
|
955
1043
|
paywallId,
|
|
956
1044
|
craftData,
|
|
@@ -958,16 +1046,51 @@ function PaywallWebView({
|
|
|
958
1046
|
primaryProductId,
|
|
959
1047
|
secondaryProductId,
|
|
960
1048
|
isPurchasing,
|
|
1049
|
+
webUrl: webUrlProp,
|
|
961
1050
|
onPurchase,
|
|
962
1051
|
onClose,
|
|
963
1052
|
onRestore,
|
|
964
|
-
onReady
|
|
1053
|
+
onReady,
|
|
1054
|
+
onError
|
|
965
1055
|
}) {
|
|
966
1056
|
const paywallDataSent = useRef3(false);
|
|
967
1057
|
const loadEndReceived = useRef3(false);
|
|
968
1058
|
const [scriptToInject, setScriptToInject] = useState3(void 0);
|
|
1059
|
+
const onErrorRef = useRef3(onError);
|
|
1060
|
+
onErrorRef.current = onError;
|
|
1061
|
+
const onReadyRef = useRef3(onReady);
|
|
1062
|
+
onReadyRef.current = onReady;
|
|
969
1063
|
const NativeWebView = useMemo(() => getNativeWebView(), []);
|
|
970
|
-
const
|
|
1064
|
+
const [resolvedWebUrl, setResolvedWebUrl] = useState3(
|
|
1065
|
+
() => webUrlProp ?? PaywalloClient.getWebUrl()
|
|
1066
|
+
);
|
|
1067
|
+
useEffect2(() => {
|
|
1068
|
+
if (webUrlProp !== void 0) {
|
|
1069
|
+
setResolvedWebUrl(webUrlProp ?? null);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const url = PaywalloClient.getWebUrl();
|
|
1073
|
+
if (url) {
|
|
1074
|
+
setResolvedWebUrl(url);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const interval = setInterval(() => {
|
|
1078
|
+
const u = PaywalloClient.getWebUrl();
|
|
1079
|
+
if (u) {
|
|
1080
|
+
setResolvedWebUrl(u);
|
|
1081
|
+
clearInterval(interval);
|
|
1082
|
+
}
|
|
1083
|
+
}, 200);
|
|
1084
|
+
return () => clearInterval(interval);
|
|
1085
|
+
}, [webUrlProp]);
|
|
1086
|
+
useEffect2(() => {
|
|
1087
|
+
if (!resolvedWebUrl) {
|
|
1088
|
+
onErrorRef.current?.({
|
|
1089
|
+
code: -1,
|
|
1090
|
+
description: "Paywall URL not configured. SDK may not be initialized."
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}, []);
|
|
971
1094
|
const buildAndSetInjectionScript = useCallback3(() => {
|
|
972
1095
|
if (!craftData || paywallDataSent.current) return;
|
|
973
1096
|
paywallDataSent.current = true;
|
|
@@ -978,19 +1101,21 @@ function PaywallWebView({
|
|
|
978
1101
|
secondaryProductId
|
|
979
1102
|
});
|
|
980
1103
|
setScriptToInject(script);
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const lastProcessedRef = useRef3({ type: "", time: 0 });
|
|
1104
|
+
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1105
|
+
const processedIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
984
1106
|
const handleParsedMessage = useCallback3(
|
|
985
1107
|
(message) => {
|
|
986
|
-
const
|
|
987
|
-
if (
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
lastProcessedRef.current = { type: message.type, time: now };
|
|
1108
|
+
const msgId = deriveMessageId(message);
|
|
1109
|
+
if (processedIdsRef.current.has(msgId)) return;
|
|
1110
|
+
processedIdsRef.current.add(msgId);
|
|
1111
|
+
setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
|
|
991
1112
|
switch (message.type) {
|
|
992
1113
|
case "ready":
|
|
993
|
-
if (!paywallDataSent.current)
|
|
1114
|
+
if (!paywallDataSent.current) {
|
|
1115
|
+
buildAndSetInjectionScript();
|
|
1116
|
+
} else {
|
|
1117
|
+
onReadyRef.current?.();
|
|
1118
|
+
}
|
|
994
1119
|
break;
|
|
995
1120
|
case "purchase":
|
|
996
1121
|
if (message.payload?.productId) onPurchase(message.payload.productId);
|
|
@@ -1025,17 +1150,51 @@ function PaywallWebView({
|
|
|
1025
1150
|
const message = parseWebViewMessage(event.data);
|
|
1026
1151
|
if (message) handleParsedMessage(message);
|
|
1027
1152
|
});
|
|
1153
|
+
const errorSub = emitter.addListener(
|
|
1154
|
+
"PaywalloWebViewError",
|
|
1155
|
+
(event) => {
|
|
1156
|
+
onErrorRef.current?.(parseErrorEvent(event));
|
|
1157
|
+
}
|
|
1158
|
+
);
|
|
1028
1159
|
return () => {
|
|
1029
1160
|
loadEndSub.remove();
|
|
1030
1161
|
messageSub.remove();
|
|
1162
|
+
errorSub.remove();
|
|
1031
1163
|
};
|
|
1032
1164
|
}, [buildAndSetInjectionScript, handleParsedMessage]);
|
|
1165
|
+
useEffect2(() => {
|
|
1166
|
+
const timer = setTimeout(() => {
|
|
1167
|
+
if (!paywallDataSent.current) {
|
|
1168
|
+
onErrorRef.current?.({
|
|
1169
|
+
code: -1001,
|
|
1170
|
+
description: "Paywall load timed out after 10 seconds"
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
}, 1e4);
|
|
1174
|
+
return () => clearTimeout(timer);
|
|
1175
|
+
}, []);
|
|
1033
1176
|
const handleLoadEnd = useCallback3(() => {
|
|
1034
1177
|
if (!loadEndReceived.current) {
|
|
1035
1178
|
loadEndReceived.current = true;
|
|
1036
1179
|
buildAndSetInjectionScript();
|
|
1037
1180
|
}
|
|
1038
1181
|
}, [buildAndSetInjectionScript]);
|
|
1182
|
+
const handleNativeError = useCallback3((event) => {
|
|
1183
|
+
const ne = event.nativeEvent;
|
|
1184
|
+
let errorData;
|
|
1185
|
+
if (ne["payload"] && typeof ne["payload"] === "object") {
|
|
1186
|
+
errorData = ne["payload"];
|
|
1187
|
+
} else if (typeof ne["data"] === "string") {
|
|
1188
|
+
try {
|
|
1189
|
+
errorData = JSON.parse(ne["data"]);
|
|
1190
|
+
} catch {
|
|
1191
|
+
errorData = {};
|
|
1192
|
+
}
|
|
1193
|
+
} else {
|
|
1194
|
+
errorData = {};
|
|
1195
|
+
}
|
|
1196
|
+
onErrorRef.current?.(parseErrorEvent(errorData));
|
|
1197
|
+
}, []);
|
|
1039
1198
|
const handleMessage = useCallback3(
|
|
1040
1199
|
(event) => {
|
|
1041
1200
|
const message = parseWebViewMessage(event.nativeEvent.data);
|
|
@@ -1047,7 +1206,8 @@ function PaywallWebView({
|
|
|
1047
1206
|
if (!paywallDataSent.current) return;
|
|
1048
1207
|
setScriptToInject(buildPurchaseStateScript(isPurchasing));
|
|
1049
1208
|
}, [isPurchasing]);
|
|
1050
|
-
|
|
1209
|
+
if (!resolvedWebUrl) return null;
|
|
1210
|
+
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1051
1211
|
return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsx(
|
|
1052
1212
|
NativeWebView,
|
|
1053
1213
|
{
|
|
@@ -1055,6 +1215,7 @@ function PaywallWebView({
|
|
|
1055
1215
|
injectedJavaScript: scriptToInject,
|
|
1056
1216
|
onMessage: handleMessage,
|
|
1057
1217
|
onLoadEnd: handleLoadEnd,
|
|
1218
|
+
onError: handleNativeError,
|
|
1058
1219
|
style: styles.webview
|
|
1059
1220
|
}
|
|
1060
1221
|
) });
|
|
@@ -1065,8 +1226,20 @@ var styles = StyleSheet.create({
|
|
|
1065
1226
|
});
|
|
1066
1227
|
|
|
1067
1228
|
// src/domains/paywall/PaywallModal.tsx
|
|
1068
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1229
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1069
1230
|
var SCREEN_HEIGHT2 = Dimensions2.get("window").height;
|
|
1231
|
+
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1232
|
+
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1233
|
+
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1234
|
+
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1235
|
+
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1236
|
+
return /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
|
|
1237
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: title }),
|
|
1238
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: description }),
|
|
1239
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: retryLabel }) }),
|
|
1240
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: closeLabel }) })
|
|
1241
|
+
] }) });
|
|
1242
|
+
}
|
|
1070
1243
|
function PaywallModal({
|
|
1071
1244
|
placement,
|
|
1072
1245
|
visible,
|
|
@@ -1084,10 +1257,35 @@ function PaywallModal({
|
|
|
1084
1257
|
variantKey,
|
|
1085
1258
|
campaignId
|
|
1086
1259
|
);
|
|
1087
|
-
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId);
|
|
1088
1260
|
const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
|
|
1261
|
+
const [modalVisible, setModalVisible] = useState4(false);
|
|
1262
|
+
const handleResult = useCallback4((result) => {
|
|
1263
|
+
setModalVisible(false);
|
|
1264
|
+
onResult(result);
|
|
1265
|
+
}, [onResult]);
|
|
1266
|
+
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
|
|
1267
|
+
const [webViewError, setWebViewError] = useState4(null);
|
|
1268
|
+
const [retryCount, setRetryCount] = useState4(0);
|
|
1269
|
+
const retryCountRef = useRef4(0);
|
|
1270
|
+
const handleWebViewError = useCallback4(
|
|
1271
|
+
(err) => {
|
|
1272
|
+
if (retryCountRef.current >= 1) {
|
|
1273
|
+
setWebViewError({ code: err.code, description: err.description });
|
|
1274
|
+
} else {
|
|
1275
|
+
retryCountRef.current += 1;
|
|
1276
|
+
setRetryCount(retryCountRef.current);
|
|
1277
|
+
}
|
|
1278
|
+
},
|
|
1279
|
+
[]
|
|
1280
|
+
);
|
|
1281
|
+
const handleRetry = useCallback4(() => {
|
|
1282
|
+
retryCountRef.current = 0;
|
|
1283
|
+
setWebViewError(null);
|
|
1284
|
+
setRetryCount(0);
|
|
1285
|
+
}, []);
|
|
1089
1286
|
useEffect3(() => {
|
|
1090
1287
|
if (visible) {
|
|
1288
|
+
setModalVisible(true);
|
|
1091
1289
|
openAnim.setValue(SCREEN_HEIGHT2);
|
|
1092
1290
|
Animated2.timing(openAnim, {
|
|
1093
1291
|
toValue: 0,
|
|
@@ -1097,11 +1295,11 @@ function PaywallModal({
|
|
|
1097
1295
|
}).start();
|
|
1098
1296
|
}
|
|
1099
1297
|
}, [visible, openAnim]);
|
|
1100
|
-
if (!visible) return null;
|
|
1298
|
+
if (!modalVisible && !visible) return null;
|
|
1101
1299
|
return /* @__PURE__ */ jsx2(
|
|
1102
1300
|
Modal,
|
|
1103
1301
|
{
|
|
1104
|
-
visible,
|
|
1302
|
+
visible: modalVisible,
|
|
1105
1303
|
animationType: "none",
|
|
1106
1304
|
presentationStyle: "overFullScreen",
|
|
1107
1305
|
onRequestClose: handleClose,
|
|
@@ -1109,7 +1307,14 @@ function PaywallModal({
|
|
|
1109
1307
|
statusBarTranslucent: true,
|
|
1110
1308
|
children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsx2(Animated2.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.container, children: [
|
|
1111
1309
|
error && /* @__PURE__ */ jsx2(View2, { style: styles2.errorContainer, children: /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: error.message }) }),
|
|
1112
|
-
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(
|
|
1310
|
+
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
|
|
1311
|
+
ErrorFallback,
|
|
1312
|
+
{
|
|
1313
|
+
description: webViewError.description,
|
|
1314
|
+
onRetry: handleRetry,
|
|
1315
|
+
onClose: handleClose
|
|
1316
|
+
}
|
|
1317
|
+
) : /* @__PURE__ */ jsx2(
|
|
1113
1318
|
PaywallWebView,
|
|
1114
1319
|
{
|
|
1115
1320
|
paywallId: paywallConfig.id,
|
|
@@ -1120,39 +1325,67 @@ function PaywallModal({
|
|
|
1120
1325
|
isPurchasing,
|
|
1121
1326
|
onClose: handleClose,
|
|
1122
1327
|
onPurchase: handlePurchase,
|
|
1123
|
-
onRestore: handleRestore
|
|
1124
|
-
|
|
1125
|
-
|
|
1328
|
+
onRestore: handleRestore,
|
|
1329
|
+
onError: handleWebViewError
|
|
1330
|
+
},
|
|
1331
|
+
retryCount
|
|
1332
|
+
) })
|
|
1126
1333
|
] }) }) })
|
|
1127
1334
|
}
|
|
1128
1335
|
);
|
|
1129
1336
|
}
|
|
1130
1337
|
var styles2 = StyleSheet2.create({
|
|
1131
|
-
overlay: {
|
|
1338
|
+
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1339
|
+
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1340
|
+
container: { flex: 1, backgroundColor: "#fff" },
|
|
1341
|
+
errorContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20 },
|
|
1342
|
+
errorText: { fontSize: 16, color: "#ef4444", textAlign: "center" },
|
|
1343
|
+
fallbackContainer: {
|
|
1132
1344
|
flex: 1,
|
|
1133
|
-
|
|
1345
|
+
justifyContent: "center",
|
|
1346
|
+
alignItems: "center",
|
|
1347
|
+
backgroundColor: "#111010",
|
|
1348
|
+
padding: 24
|
|
1134
1349
|
},
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1350
|
+
fallbackCard: {
|
|
1351
|
+
width: "100%",
|
|
1352
|
+
maxWidth: 360,
|
|
1353
|
+
alignItems: "center",
|
|
1354
|
+
backgroundColor: "rgba(255,255,255,0.07)",
|
|
1355
|
+
borderRadius: 16,
|
|
1356
|
+
padding: 28
|
|
1140
1357
|
},
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1358
|
+
fallbackTitle: {
|
|
1359
|
+
fontSize: 18,
|
|
1360
|
+
fontWeight: "600",
|
|
1361
|
+
color: "#f5f5f5",
|
|
1362
|
+
marginBottom: 10,
|
|
1363
|
+
textAlign: "center"
|
|
1144
1364
|
},
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1365
|
+
fallbackDescription: {
|
|
1366
|
+
fontSize: 14,
|
|
1367
|
+
color: "rgba(255,255,255,0.55)",
|
|
1368
|
+
textAlign: "center",
|
|
1369
|
+
marginBottom: 28,
|
|
1370
|
+
lineHeight: 20
|
|
1371
|
+
},
|
|
1372
|
+
retryButton: {
|
|
1373
|
+
width: "100%",
|
|
1374
|
+
backgroundColor: "#ffffff",
|
|
1375
|
+
borderRadius: 10,
|
|
1376
|
+
paddingVertical: 14,
|
|
1148
1377
|
alignItems: "center",
|
|
1149
|
-
|
|
1378
|
+
marginBottom: 12
|
|
1150
1379
|
},
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1380
|
+
retryButtonText: { fontSize: 15, fontWeight: "600", color: "#111010" },
|
|
1381
|
+
closeButton: {
|
|
1382
|
+
width: "100%",
|
|
1383
|
+
borderRadius: 10,
|
|
1384
|
+
paddingVertical: 14,
|
|
1385
|
+
alignItems: "center",
|
|
1386
|
+
backgroundColor: "rgba(255,255,255,0.1)"
|
|
1387
|
+
},
|
|
1388
|
+
closeButtonText: { fontSize: 15, fontWeight: "500", color: "rgba(255,255,255,0.7)" }
|
|
1156
1389
|
});
|
|
1157
1390
|
|
|
1158
1391
|
// src/domains/paywall/PaywallErrorBoundary.tsx
|
|
@@ -1165,8 +1398,7 @@ var PaywallErrorBoundary = class extends Component {
|
|
|
1165
1398
|
static getDerivedStateFromError() {
|
|
1166
1399
|
return { hasError: true };
|
|
1167
1400
|
}
|
|
1168
|
-
componentDidCatch(
|
|
1169
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
|
|
1401
|
+
componentDidCatch(_error) {
|
|
1170
1402
|
}
|
|
1171
1403
|
render() {
|
|
1172
1404
|
return this.state.hasError ? null : this.props.children;
|
|
@@ -1188,9 +1420,9 @@ function usePaywallContext() {
|
|
|
1188
1420
|
}
|
|
1189
1421
|
|
|
1190
1422
|
// src/domains/paywall/usePaywallPresenter.ts
|
|
1191
|
-
import { useCallback as
|
|
1192
|
-
function usePaywallPresenter(preloadedWebView,
|
|
1193
|
-
const [showingPreloadedWebView, setShowingPreloadedWebView] =
|
|
1423
|
+
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
|
|
1424
|
+
function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
1425
|
+
const [showingPreloadedWebView, setShowingPreloadedWebView] = useState5(false);
|
|
1194
1426
|
const resolverRef = useRef5(null);
|
|
1195
1427
|
const presentedAtRef = useRef5(Date.now());
|
|
1196
1428
|
useEffect4(() => {
|
|
@@ -1201,23 +1433,23 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1201
1433
|
}
|
|
1202
1434
|
};
|
|
1203
1435
|
}, []);
|
|
1204
|
-
const presentPreloadedWebView =
|
|
1436
|
+
const presentPreloadedWebView = useCallback5(() => {
|
|
1205
1437
|
return new Promise((resolve) => {
|
|
1206
1438
|
resolverRef.current = resolve;
|
|
1207
1439
|
presentedAtRef.current = Date.now();
|
|
1208
1440
|
setShowingPreloadedWebView(true);
|
|
1209
1441
|
});
|
|
1210
1442
|
}, []);
|
|
1211
|
-
const resolvePreloadedPaywall =
|
|
1443
|
+
const resolvePreloadedPaywall = useCallback5((result) => {
|
|
1212
1444
|
if (resolverRef.current) {
|
|
1213
1445
|
resolverRef.current(result);
|
|
1214
1446
|
resolverRef.current = null;
|
|
1215
1447
|
}
|
|
1216
1448
|
}, []);
|
|
1217
|
-
const handlePreloadedWebViewReady =
|
|
1449
|
+
const handlePreloadedWebViewReady = useCallback5(() => {
|
|
1218
1450
|
setPreloadedWebView((prev) => prev ? { ...prev, loaded: true } : null);
|
|
1219
1451
|
}, [setPreloadedWebView]);
|
|
1220
|
-
const handlePreloadedClose =
|
|
1452
|
+
const handlePreloadedClose = useCallback5(() => {
|
|
1221
1453
|
const track = async () => {
|
|
1222
1454
|
try {
|
|
1223
1455
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -1238,9 +1470,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1238
1470
|
};
|
|
1239
1471
|
void track();
|
|
1240
1472
|
setShowingPreloadedWebView(false);
|
|
1241
|
-
clearPreloadedWebView();
|
|
1242
1473
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
1243
|
-
}, [preloadedWebView,
|
|
1474
|
+
}, [preloadedWebView, resolvePreloadedPaywall]);
|
|
1244
1475
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1245
1476
|
}
|
|
1246
1477
|
|
|
@@ -1447,10 +1678,7 @@ var AffiliateManager = class {
|
|
|
1447
1678
|
);
|
|
1448
1679
|
}
|
|
1449
1680
|
}
|
|
1450
|
-
log(...
|
|
1451
|
-
if (this.debug) {
|
|
1452
|
-
console.warn("[AffiliateManager]", ...args);
|
|
1453
|
-
}
|
|
1681
|
+
log(..._args) {
|
|
1454
1682
|
}
|
|
1455
1683
|
};
|
|
1456
1684
|
var affiliateManager = new AffiliateManager();
|
|
@@ -1467,138 +1695,394 @@ var PaywalloAffiliate = {
|
|
|
1467
1695
|
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1468
1696
|
};
|
|
1469
1697
|
|
|
1470
|
-
// src/
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
try {
|
|
1477
|
-
const url = new URL(baseUrl);
|
|
1478
|
-
if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
|
|
1479
|
-
url.port = "3000";
|
|
1480
|
-
return url.toString().replace(/\/$/, "");
|
|
1481
|
-
}
|
|
1482
|
-
if (url.hostname.startsWith("api.")) {
|
|
1483
|
-
url.hostname = url.hostname.replace("api.", "app.");
|
|
1484
|
-
return url.toString().replace(/\/$/, "");
|
|
1485
|
-
}
|
|
1486
|
-
return DEFAULT_WEB_URL;
|
|
1487
|
-
} catch {
|
|
1488
|
-
return DEFAULT_WEB_URL;
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
function buildConditionalFlagUrl(baseUrl, flagKey, context) {
|
|
1492
|
-
const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
|
|
1493
|
-
if (context.platform) url.searchParams.set("platform", context.platform);
|
|
1494
|
-
if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
|
|
1495
|
-
if (context.country) url.searchParams.set("country", context.country);
|
|
1496
|
-
if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
|
|
1497
|
-
return url.toString();
|
|
1498
|
-
}
|
|
1499
|
-
|
|
1500
|
-
// src/domains/iap/apiPurchaseMethods.ts
|
|
1501
|
-
async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
1502
|
-
const response = await client.post(
|
|
1503
|
-
`/purchases/validate/${appKey}`,
|
|
1504
|
-
{
|
|
1505
|
-
platform,
|
|
1506
|
-
receipt,
|
|
1507
|
-
productId,
|
|
1508
|
-
transactionId,
|
|
1509
|
-
priceLocal,
|
|
1510
|
-
currency,
|
|
1511
|
-
country,
|
|
1512
|
-
distinctId,
|
|
1513
|
-
...paywallPlacement !== void 0 && { paywallPlacement },
|
|
1514
|
-
...variantKey !== void 0 && { variantKey }
|
|
1515
|
-
},
|
|
1516
|
-
false
|
|
1517
|
-
);
|
|
1518
|
-
return response.data;
|
|
1519
|
-
}
|
|
1520
|
-
async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
1521
|
-
const url = new URL(`${baseUrl}/purchases/status/${appKey}`);
|
|
1522
|
-
url.searchParams.set("distinctId", distinctId);
|
|
1523
|
-
const response = await client.get(url.toString());
|
|
1524
|
-
return response.data;
|
|
1525
|
-
}
|
|
1526
|
-
async function getEmergencyPaywall(client) {
|
|
1527
|
-
try {
|
|
1528
|
-
const response = await client.get("/api/v1/emergency-paywall");
|
|
1529
|
-
if (response.status === 404) return { enabled: false };
|
|
1530
|
-
return response.data;
|
|
1531
|
-
} catch {
|
|
1532
|
-
return { enabled: false };
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
async function getCampaignData(client, baseUrl, placement, distinctId, context) {
|
|
1536
|
-
const url = new URL(`${baseUrl}/api/v1/campaigns/${placement}`);
|
|
1537
|
-
url.searchParams.set("distinctId", distinctId);
|
|
1538
|
-
if (context) url.searchParams.set("context", JSON.stringify(context));
|
|
1539
|
-
const response = await client.get(url.toString());
|
|
1540
|
-
if (response.status === 404) return null;
|
|
1541
|
-
const data = response.data;
|
|
1542
|
-
if ("paywall" in data && data.paywall === null && !("campaignId" in data)) return null;
|
|
1543
|
-
return data;
|
|
1698
|
+
// src/domains/subscription/SubscriptionCache.ts
|
|
1699
|
+
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1700
|
+
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1701
|
+
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1702
|
+
function keyFor(distinctId) {
|
|
1703
|
+
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
1544
1704
|
}
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
1552
|
-
};
|
|
1553
|
-
var DEFAULT_HTTP_CONFIG = {
|
|
1554
|
-
timeout: 1e4,
|
|
1555
|
-
retry: DEFAULT_RETRY_CONFIG,
|
|
1556
|
-
debug: false
|
|
1557
|
-
};
|
|
1558
|
-
|
|
1559
|
-
// src/core/http/HttpClient.ts
|
|
1560
|
-
var HttpClient = class {
|
|
1561
|
-
constructor(baseUrl, config) {
|
|
1562
|
-
this.config = {
|
|
1563
|
-
baseUrl,
|
|
1564
|
-
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
|
|
1565
|
-
retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
|
|
1566
|
-
debug: config?.debug ?? false
|
|
1567
|
-
};
|
|
1568
|
-
}
|
|
1569
|
-
async get(path, options) {
|
|
1570
|
-
return this.request(path, { ...options, method: "GET" });
|
|
1571
|
-
}
|
|
1572
|
-
async post(path, body, options) {
|
|
1573
|
-
return this.request(path, { ...options, method: "POST", body });
|
|
1705
|
+
var SubscriptionCache = class {
|
|
1706
|
+
constructor(ttl = DEFAULT_TTL) {
|
|
1707
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
1708
|
+
this.legacyCleanupDone = false;
|
|
1709
|
+
this.ttl = ttl;
|
|
1710
|
+
this.storage = new SecureStorage();
|
|
1574
1711
|
}
|
|
1575
|
-
async
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1712
|
+
async cleanupLegacyCache() {
|
|
1713
|
+
if (this.legacyCleanupDone) return;
|
|
1714
|
+
this.legacyCleanupDone = true;
|
|
1715
|
+
try {
|
|
1716
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1717
|
+
} catch {
|
|
1581
1718
|
}
|
|
1582
|
-
return this.executeWithRetry(url, fetchOptions, options);
|
|
1583
1719
|
}
|
|
1584
|
-
async
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
const
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
return
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1720
|
+
async get(distinctId) {
|
|
1721
|
+
await this.cleanupLegacyCache();
|
|
1722
|
+
if (!distinctId) return null;
|
|
1723
|
+
const memEntry = this.memoryCache.get(distinctId);
|
|
1724
|
+
if (memEntry && !this.isExpired(memEntry)) {
|
|
1725
|
+
return memEntry;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
const stored = await this.storage.get(keyFor(distinctId));
|
|
1729
|
+
if (!stored) {
|
|
1730
|
+
return null;
|
|
1731
|
+
}
|
|
1732
|
+
const parsed = JSON.parse(stored);
|
|
1733
|
+
if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
|
|
1734
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1735
|
+
return null;
|
|
1736
|
+
}
|
|
1737
|
+
const cached = parsed;
|
|
1738
|
+
if (cached.data.subscription?.expiresAt) {
|
|
1739
|
+
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
1740
|
+
}
|
|
1741
|
+
if (cached.data.subscription?.originalPurchaseDate) {
|
|
1742
|
+
cached.data.subscription.originalPurchaseDate = new Date(
|
|
1743
|
+
cached.data.subscription.originalPurchaseDate
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
if (cached.data.subscription?.latestPurchaseDate) {
|
|
1747
|
+
cached.data.subscription.latestPurchaseDate = new Date(
|
|
1748
|
+
cached.data.subscription.latestPurchaseDate
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
if (cached.data.subscription?.cancellationDate) {
|
|
1752
|
+
cached.data.subscription.cancellationDate = new Date(
|
|
1753
|
+
cached.data.subscription.cancellationDate
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
1757
|
+
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
1758
|
+
cached.data.subscription.gracePeriodExpiresAt
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
cached.isStale = this.isExpired(cached);
|
|
1762
|
+
if (!cached.isStale) {
|
|
1763
|
+
this.memoryCache.set(distinctId, cached);
|
|
1764
|
+
}
|
|
1765
|
+
return cached;
|
|
1766
|
+
} catch {
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async set(distinctId, data) {
|
|
1771
|
+
if (!distinctId) return;
|
|
1772
|
+
const cached = {
|
|
1773
|
+
data,
|
|
1774
|
+
cachedAt: Date.now(),
|
|
1775
|
+
isStale: false
|
|
1776
|
+
};
|
|
1777
|
+
this.memoryCache.set(distinctId, cached);
|
|
1778
|
+
try {
|
|
1779
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1780
|
+
} catch {
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
async invalidate(distinctId) {
|
|
1784
|
+
if (!distinctId) return;
|
|
1785
|
+
this.memoryCache.delete(distinctId);
|
|
1786
|
+
try {
|
|
1787
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
async invalidateAll() {
|
|
1792
|
+
this.memoryCache.clear();
|
|
1793
|
+
try {
|
|
1794
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1795
|
+
} catch {
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
isExpired(cached) {
|
|
1799
|
+
return Date.now() - cached.cachedAt > this.ttl;
|
|
1800
|
+
}
|
|
1801
|
+
setTTL(ttl) {
|
|
1802
|
+
this.ttl = ttl;
|
|
1803
|
+
}
|
|
1804
|
+
};
|
|
1805
|
+
var subscriptionCache = new SubscriptionCache();
|
|
1806
|
+
|
|
1807
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1808
|
+
import { Platform as Platform4 } from "react-native";
|
|
1809
|
+
|
|
1810
|
+
// src/domains/session/SessionError.ts
|
|
1811
|
+
var SessionError = class extends PaywalloError {
|
|
1812
|
+
constructor(code, message) {
|
|
1813
|
+
super("session", code, message);
|
|
1814
|
+
this.name = "SessionError";
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
var SESSION_ERROR_CODES = {
|
|
1818
|
+
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
1819
|
+
START_FAILED: "SESSION_START_FAILED",
|
|
1820
|
+
END_FAILED: "SESSION_END_FAILED",
|
|
1821
|
+
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1825
|
+
var SubscriptionManagerClass = class {
|
|
1826
|
+
constructor() {
|
|
1827
|
+
this.config = null;
|
|
1828
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1829
|
+
this.userId = null;
|
|
1830
|
+
this.cache = new SubscriptionCache();
|
|
1831
|
+
}
|
|
1832
|
+
cacheKey() {
|
|
1833
|
+
return this.userId ?? "__anonymous__";
|
|
1834
|
+
}
|
|
1835
|
+
init(config) {
|
|
1836
|
+
this.config = config;
|
|
1837
|
+
if (config.cacheTTL) {
|
|
1838
|
+
this.cache.setTTL(config.cacheTTL);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
setUserId(userId) {
|
|
1842
|
+
if (this.userId !== userId) {
|
|
1843
|
+
this.userId = userId;
|
|
1844
|
+
void this.cache.invalidateAll();
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
async hasActiveSubscription(forceRefresh = false) {
|
|
1848
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1849
|
+
return status.hasActiveSubscription;
|
|
1850
|
+
}
|
|
1851
|
+
async getSubscription(forceRefresh = false) {
|
|
1852
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1853
|
+
return status.subscription;
|
|
1854
|
+
}
|
|
1855
|
+
async getEntitlements(forceRefresh = false) {
|
|
1856
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1857
|
+
return status.entitlements;
|
|
1858
|
+
}
|
|
1859
|
+
async getSubscriptionStatus(forceRefresh = false) {
|
|
1860
|
+
if (!this.config) {
|
|
1861
|
+
return this.getEmptyStatus();
|
|
1862
|
+
}
|
|
1863
|
+
if (!forceRefresh) {
|
|
1864
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1865
|
+
if (cached && !cached.isStale) {
|
|
1866
|
+
return cached.data;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
try {
|
|
1870
|
+
const status = await this.fetchSubscriptionStatus();
|
|
1871
|
+
await this.cache.set(this.cacheKey(), status);
|
|
1872
|
+
this.notifyListeners(status);
|
|
1873
|
+
return status;
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
this.log("Failed to fetch subscription status", error);
|
|
1876
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1877
|
+
if (cached) {
|
|
1878
|
+
return cached.data;
|
|
1879
|
+
}
|
|
1880
|
+
return this.getEmptyStatus();
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
async restorePurchases() {
|
|
1884
|
+
if (!this.config) {
|
|
1885
|
+
throw new SessionError(
|
|
1886
|
+
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
1887
|
+
"SubscriptionManager not initialized"
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1891
|
+
return this.getSubscriptionStatus(true);
|
|
1892
|
+
}
|
|
1893
|
+
async fetchSubscriptionStatus() {
|
|
1894
|
+
if (!this.config) {
|
|
1895
|
+
return this.getEmptyStatus();
|
|
1896
|
+
}
|
|
1897
|
+
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
1898
|
+
if (this.userId) {
|
|
1899
|
+
url.searchParams.set("userId", this.userId);
|
|
1900
|
+
}
|
|
1901
|
+
url.searchParams.set("platform", Platform4.OS);
|
|
1902
|
+
const response = await fetch(url.toString(), {
|
|
1903
|
+
method: "GET",
|
|
1904
|
+
headers: {
|
|
1905
|
+
"Content-Type": "application/json",
|
|
1906
|
+
"X-App-Key": this.config.appKey
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
if (!response.ok) {
|
|
1910
|
+
throw new SessionError(
|
|
1911
|
+
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
1912
|
+
`Failed to fetch subscription status: ${response.status}`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
const data = await response.json();
|
|
1916
|
+
if (data.subscription) {
|
|
1917
|
+
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
1918
|
+
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
1919
|
+
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
1920
|
+
if (data.subscription.cancellationDate) {
|
|
1921
|
+
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
1922
|
+
}
|
|
1923
|
+
if (data.subscription.gracePeriodExpiresAt) {
|
|
1924
|
+
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return data;
|
|
1928
|
+
}
|
|
1929
|
+
getEmptyStatus() {
|
|
1930
|
+
return {
|
|
1931
|
+
hasActiveSubscription: false,
|
|
1932
|
+
subscription: null,
|
|
1933
|
+
entitlements: []
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
addListener(listener) {
|
|
1937
|
+
this.listeners.add(listener);
|
|
1938
|
+
return () => this.listeners.delete(listener);
|
|
1939
|
+
}
|
|
1940
|
+
notifyListeners(status) {
|
|
1941
|
+
for (const listener of this.listeners) {
|
|
1942
|
+
listener(status);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
async onPurchaseComplete() {
|
|
1946
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1947
|
+
await this.getSubscriptionStatus(true);
|
|
1948
|
+
}
|
|
1949
|
+
log(..._args) {
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
var subscriptionManager = new SubscriptionManagerClass();
|
|
1953
|
+
|
|
1954
|
+
// src/core/ApiClient.ts
|
|
1955
|
+
import { Platform as Platform5 } from "react-native";
|
|
1956
|
+
|
|
1957
|
+
// src/core/apiUrlUtils.ts
|
|
1958
|
+
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
1959
|
+
function deriveWebUrl(baseUrl) {
|
|
1960
|
+
try {
|
|
1961
|
+
const url = new URL(baseUrl);
|
|
1962
|
+
if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
|
|
1963
|
+
url.port = "3000";
|
|
1964
|
+
return url.toString().replace(/\/$/, "");
|
|
1965
|
+
}
|
|
1966
|
+
if (url.hostname.startsWith("api.")) {
|
|
1967
|
+
url.hostname = url.hostname.replace("api.", "app.");
|
|
1968
|
+
return url.toString().replace(/\/$/, "");
|
|
1969
|
+
}
|
|
1970
|
+
return DEFAULT_WEB_URL;
|
|
1971
|
+
} catch {
|
|
1972
|
+
return DEFAULT_WEB_URL;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
function buildConditionalFlagUrl(baseUrl, flagKey, context) {
|
|
1976
|
+
const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
|
|
1977
|
+
if (context.platform) url.searchParams.set("platform", context.platform);
|
|
1978
|
+
if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
|
|
1979
|
+
if (context.country) url.searchParams.set("country", context.country);
|
|
1980
|
+
if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
|
|
1981
|
+
return url.toString();
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
// src/domains/iap/apiPurchaseMethods.ts
|
|
1985
|
+
async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
1986
|
+
const response = await client.post(
|
|
1987
|
+
`/purchases/validate/${appKey}`,
|
|
1988
|
+
{
|
|
1989
|
+
platform,
|
|
1990
|
+
receipt,
|
|
1991
|
+
productId,
|
|
1992
|
+
transactionId,
|
|
1993
|
+
priceLocal,
|
|
1994
|
+
currency,
|
|
1995
|
+
country,
|
|
1996
|
+
distinctId,
|
|
1997
|
+
...paywallPlacement !== void 0 && { paywallPlacement },
|
|
1998
|
+
...variantKey !== void 0 && { variantKey }
|
|
1999
|
+
},
|
|
2000
|
+
false
|
|
2001
|
+
);
|
|
2002
|
+
return response.data;
|
|
2003
|
+
}
|
|
2004
|
+
async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
2005
|
+
const url = new URL(`${baseUrl}/purchases/status/${appKey}`);
|
|
2006
|
+
url.searchParams.set("distinctId", distinctId);
|
|
2007
|
+
const response = await client.get(url.toString());
|
|
2008
|
+
return response.data;
|
|
2009
|
+
}
|
|
2010
|
+
async function getEmergencyPaywall(client) {
|
|
2011
|
+
try {
|
|
2012
|
+
const response = await client.get("/api/v1/emergency-paywall");
|
|
2013
|
+
if (response.status === 404) return { enabled: false };
|
|
2014
|
+
return response.data;
|
|
2015
|
+
} catch {
|
|
2016
|
+
return { enabled: false };
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
async function getCampaignData(client, baseUrl, placement, distinctId, context) {
|
|
2020
|
+
const url = new URL(`${baseUrl}/api/v1/campaigns/${placement}`);
|
|
2021
|
+
url.searchParams.set("distinctId", distinctId);
|
|
2022
|
+
if (context) url.searchParams.set("context", JSON.stringify(context));
|
|
2023
|
+
const response = await client.get(url.toString());
|
|
2024
|
+
if (response.status === 404) return null;
|
|
2025
|
+
const data = response.data;
|
|
2026
|
+
if ("paywall" in data && data.paywall === null && !("campaignId" in data)) return null;
|
|
2027
|
+
return data;
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
// src/core/http/types.ts
|
|
2031
|
+
var DEFAULT_RETRY_CONFIG = {
|
|
2032
|
+
maxRetries: 2,
|
|
2033
|
+
baseDelay: 500,
|
|
2034
|
+
maxDelay: 4e3,
|
|
2035
|
+
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
|
|
2036
|
+
};
|
|
2037
|
+
var DEFAULT_HTTP_CONFIG = {
|
|
2038
|
+
timeout: 1e4,
|
|
2039
|
+
retry: DEFAULT_RETRY_CONFIG,
|
|
2040
|
+
debug: false
|
|
2041
|
+
};
|
|
2042
|
+
|
|
2043
|
+
// src/core/http/HttpClient.ts
|
|
2044
|
+
var HttpClient = class {
|
|
2045
|
+
constructor(baseUrl, config) {
|
|
2046
|
+
this.config = {
|
|
2047
|
+
baseUrl,
|
|
2048
|
+
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
|
|
2049
|
+
retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
|
|
2050
|
+
debug: config?.debug ?? false
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
async get(path, options) {
|
|
2054
|
+
return this.request(path, { ...options, method: "GET" });
|
|
2055
|
+
}
|
|
2056
|
+
async post(path, body, options) {
|
|
2057
|
+
return this.request(path, { ...options, method: "POST", body });
|
|
2058
|
+
}
|
|
2059
|
+
async request(path, options) {
|
|
2060
|
+
const url = this.buildUrl(path);
|
|
2061
|
+
const fetchOptions = this.buildFetchOptions(options);
|
|
2062
|
+
const timeout = options.timeout ?? this.config.timeout;
|
|
2063
|
+
if (options.skipRetry) {
|
|
2064
|
+
return this.executeRequest(url, fetchOptions, timeout);
|
|
2065
|
+
}
|
|
2066
|
+
return this.executeWithRetry(url, fetchOptions, options);
|
|
2067
|
+
}
|
|
2068
|
+
async executeWithRetry(url, fetchOptions, options) {
|
|
2069
|
+
const retryConfig = this.config.retry;
|
|
2070
|
+
const timeout = options.timeout ?? this.config.timeout;
|
|
2071
|
+
const state = { attempt: 0, lastError: null };
|
|
2072
|
+
while (state.attempt <= retryConfig.maxRetries) {
|
|
2073
|
+
try {
|
|
2074
|
+
const response = await this.executeRequest(url, fetchOptions, timeout);
|
|
2075
|
+
if (this.shouldRetry(response.status, retryConfig)) {
|
|
2076
|
+
if (state.attempt < retryConfig.maxRetries) {
|
|
2077
|
+
state.attempt++;
|
|
2078
|
+
const delay = this.calculateDelay(state.attempt, retryConfig);
|
|
2079
|
+
await this.sleep(delay);
|
|
2080
|
+
continue;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return response;
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
state.lastError = error instanceof Error ? error : new ClientError(CLIENT_ERROR_CODES.UNKNOWN, String(error));
|
|
1602
2086
|
if (this.isNetworkError(state.lastError) && state.attempt < retryConfig.maxRetries) {
|
|
1603
2087
|
state.attempt++;
|
|
1604
2088
|
const delay = this.calculateDelay(state.attempt, retryConfig);
|
|
@@ -1700,10 +2184,7 @@ var HttpClient = class {
|
|
|
1700
2184
|
sleep(ms) {
|
|
1701
2185
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1702
2186
|
}
|
|
1703
|
-
log(
|
|
1704
|
-
if (this.config.debug) {
|
|
1705
|
-
console.warn("[HttpClient]", message, data ?? "");
|
|
1706
|
-
}
|
|
2187
|
+
log(..._args) {
|
|
1707
2188
|
}
|
|
1708
2189
|
setDebug(debug) {
|
|
1709
2190
|
this.config.debug = debug;
|
|
@@ -1847,10 +2328,7 @@ var NetworkMonitorClass = class {
|
|
|
1847
2328
|
this.initialized = false;
|
|
1848
2329
|
this.currentState = "unknown";
|
|
1849
2330
|
}
|
|
1850
|
-
log(
|
|
1851
|
-
if (this.config.debug) {
|
|
1852
|
-
console.warn("[NetworkMonitor]", message, data ?? "");
|
|
1853
|
-
}
|
|
2331
|
+
log(..._args) {
|
|
1854
2332
|
}
|
|
1855
2333
|
setDebug(debug) {
|
|
1856
2334
|
this.config.debug = debug;
|
|
@@ -1880,6 +2358,8 @@ function normalizeForComparison(obj) {
|
|
|
1880
2358
|
return "{" + pairs.join(",") + "}";
|
|
1881
2359
|
}
|
|
1882
2360
|
function findDuplicateIndex(queue, item) {
|
|
2361
|
+
const byId = queue.findIndex((existing) => existing.id === item.id);
|
|
2362
|
+
if (byId !== -1) return byId;
|
|
1883
2363
|
const normalizedBody = normalizeForComparison(item.body);
|
|
1884
2364
|
return queue.findIndex(
|
|
1885
2365
|
(existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
|
|
@@ -1948,9 +2428,9 @@ var OfflineQueueClass = class {
|
|
|
1948
2428
|
void this.saveToStorage();
|
|
1949
2429
|
}
|
|
1950
2430
|
}
|
|
1951
|
-
async enqueue(method, url, body, headers) {
|
|
2431
|
+
async enqueue(method, url, body, headers, id) {
|
|
1952
2432
|
const item = {
|
|
1953
|
-
id: generateUUID(),
|
|
2433
|
+
id: id ?? generateUUID(),
|
|
1954
2434
|
method,
|
|
1955
2435
|
url,
|
|
1956
2436
|
body,
|
|
@@ -2034,7 +2514,6 @@ var OfflineQueueClass = class {
|
|
|
2034
2514
|
this.emit({ type: "processing:end", queueSize: this.queue.length });
|
|
2035
2515
|
return { processed, failed };
|
|
2036
2516
|
}
|
|
2037
|
-
/** Returns true if the item was permanently removed (counted as failed). */
|
|
2038
2517
|
async handleItemFailure(item, error, permanent = false) {
|
|
2039
2518
|
if (permanent || item.attempts >= item.maxAttempts) {
|
|
2040
2519
|
await this.removeItem(item.id);
|
|
@@ -2096,10 +2575,7 @@ var OfflineQueueClass = class {
|
|
|
2096
2575
|
}
|
|
2097
2576
|
}
|
|
2098
2577
|
}
|
|
2099
|
-
log(
|
|
2100
|
-
if (this.config.debug) {
|
|
2101
|
-
console.warn("[OfflineQueue]", message, data ?? "");
|
|
2102
|
-
}
|
|
2578
|
+
log(..._args) {
|
|
2103
2579
|
}
|
|
2104
2580
|
setDebug(debug) {
|
|
2105
2581
|
this.config.debug = debug;
|
|
@@ -2241,10 +2717,7 @@ var QueueProcessorClass = class {
|
|
|
2241
2717
|
this.httpClient = null;
|
|
2242
2718
|
this.initialized = false;
|
|
2243
2719
|
}
|
|
2244
|
-
log(
|
|
2245
|
-
if (this.config.debug) {
|
|
2246
|
-
console.warn("[QueueProcessor]", message, data ?? "");
|
|
2247
|
-
}
|
|
2720
|
+
log(..._args) {
|
|
2248
2721
|
}
|
|
2249
2722
|
setDebug(debug) {
|
|
2250
2723
|
this.config.debug = debug;
|
|
@@ -2256,10 +2729,9 @@ var queueProcessor = new QueueProcessorClass();
|
|
|
2256
2729
|
var ApiCache = class {
|
|
2257
2730
|
constructor() {
|
|
2258
2731
|
this.CACHE_TTL = 3e5;
|
|
2259
|
-
// 5 minutes
|
|
2260
2732
|
this.CACHE_NULL_TTL = 3e4;
|
|
2261
|
-
// 30 seconds — for null/404 variants
|
|
2262
2733
|
this.MAX_SIZE = 200;
|
|
2734
|
+
this.STALE_WINDOW_MS = 864e5;
|
|
2263
2735
|
this.variantCache = /* @__PURE__ */ new Map();
|
|
2264
2736
|
this.paywallCache = /* @__PURE__ */ new Map();
|
|
2265
2737
|
this.campaignCache = /* @__PURE__ */ new Map();
|
|
@@ -2297,7 +2769,7 @@ var ApiCache = class {
|
|
|
2297
2769
|
}
|
|
2298
2770
|
getStaleVariant(key) {
|
|
2299
2771
|
const entry = this.variantCache.get(key);
|
|
2300
|
-
if (entry && Date.now() - entry.ts <
|
|
2772
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2301
2773
|
return void 0;
|
|
2302
2774
|
}
|
|
2303
2775
|
getPaywall(key) {
|
|
@@ -2309,19 +2781,19 @@ var ApiCache = class {
|
|
|
2309
2781
|
}
|
|
2310
2782
|
getStalePaywall(key) {
|
|
2311
2783
|
const entry = this.paywallCache.get(key);
|
|
2312
|
-
if (entry && Date.now() - entry.ts <
|
|
2784
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2313
2785
|
return void 0;
|
|
2314
2786
|
}
|
|
2315
2787
|
getCampaign(key) {
|
|
2316
2788
|
return this.getCached(this.campaignCache, key);
|
|
2317
2789
|
}
|
|
2318
|
-
setCampaign(key, value) {
|
|
2319
|
-
this.setCached(this.campaignCache, key, value);
|
|
2790
|
+
setCampaign(key, value, ttlOverride) {
|
|
2791
|
+
this.setCached(this.campaignCache, key, value, ttlOverride);
|
|
2320
2792
|
this.evictExpiredEntries(this.campaignCache);
|
|
2321
2793
|
}
|
|
2322
2794
|
getStaleCampaign(key) {
|
|
2323
2795
|
const entry = this.campaignCache.get(key);
|
|
2324
|
-
if (entry && Date.now() - entry.ts <
|
|
2796
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2325
2797
|
return void 0;
|
|
2326
2798
|
}
|
|
2327
2799
|
};
|
|
@@ -2329,7 +2801,7 @@ var ApiCache = class {
|
|
|
2329
2801
|
// src/core/ApiClient.ts
|
|
2330
2802
|
var SDK_VERSION = "1.0.0";
|
|
2331
2803
|
var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
|
|
2332
|
-
var
|
|
2804
|
+
var _ApiClient = class _ApiClient {
|
|
2333
2805
|
constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2334
2806
|
this.cache = new ApiCache();
|
|
2335
2807
|
this.appKey = appKey;
|
|
@@ -2340,11 +2812,12 @@ var ApiClient = class {
|
|
|
2340
2812
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2341
2813
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
2342
2814
|
}
|
|
2343
|
-
// ─── Infrastructure ──────────────────────────────────────────────────────────
|
|
2344
|
-
/** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
|
|
2345
2815
|
getHttpClient() {
|
|
2346
2816
|
return this.httpClient;
|
|
2347
2817
|
}
|
|
2818
|
+
getBaseUrl() {
|
|
2819
|
+
return this.baseUrl;
|
|
2820
|
+
}
|
|
2348
2821
|
getWebUrl() {
|
|
2349
2822
|
return this.webUrl;
|
|
2350
2823
|
}
|
|
@@ -2370,7 +2843,6 @@ var ApiClient = class {
|
|
|
2370
2843
|
async clearQueue() {
|
|
2371
2844
|
await offlineQueue.clear();
|
|
2372
2845
|
}
|
|
2373
|
-
// ─── Public HTTP facade (headers injected automatically) ─────────────────────
|
|
2374
2846
|
async get(path) {
|
|
2375
2847
|
return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
|
|
2376
2848
|
}
|
|
@@ -2384,29 +2856,28 @@ var ApiClient = class {
|
|
|
2384
2856
|
message,
|
|
2385
2857
|
context,
|
|
2386
2858
|
sdkVersion: SDK_VERSION,
|
|
2387
|
-
platform:
|
|
2859
|
+
platform: Platform5.OS
|
|
2388
2860
|
}, true);
|
|
2389
2861
|
} catch {
|
|
2390
2862
|
}
|
|
2391
2863
|
}
|
|
2392
|
-
// ─── Business methods ────────────────────────────────────────────────────────
|
|
2393
2864
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2394
2865
|
await this.postWithQueue("/api/v1/events", {
|
|
2395
2866
|
eventName,
|
|
2396
2867
|
distinctId,
|
|
2397
|
-
properties: { ...properties, platform:
|
|
2868
|
+
properties: { ...properties, platform: Platform5.OS },
|
|
2398
2869
|
timestamp: timestamp ?? Date.now(),
|
|
2399
2870
|
environment: this.environment.toLowerCase()
|
|
2400
2871
|
}, `event:${eventName}`);
|
|
2401
2872
|
}
|
|
2402
|
-
async identify(distinctId, properties, email) {
|
|
2403
|
-
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform:
|
|
2873
|
+
async identify(distinctId, properties, email, deviceId) {
|
|
2874
|
+
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
|
|
2404
2875
|
}
|
|
2405
2876
|
async startSession(distinctId, sessionId, platform) {
|
|
2406
2877
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2407
2878
|
distinctId,
|
|
2408
2879
|
sessionId,
|
|
2409
|
-
platform: platform ??
|
|
2880
|
+
platform: platform ?? Platform5.OS,
|
|
2410
2881
|
environment: this.environment.toLowerCase()
|
|
2411
2882
|
}, `session.start:${sessionId}`);
|
|
2412
2883
|
return { success: true };
|
|
@@ -2419,6 +2890,11 @@ var ApiClient = class {
|
|
|
2419
2890
|
const key = `${flagKey}:${distinctId}`;
|
|
2420
2891
|
const hit = this.cache.getVariant(key);
|
|
2421
2892
|
if (hit !== void 0) return hit;
|
|
2893
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
2894
|
+
if (persisted !== null) {
|
|
2895
|
+
this.cache.setVariant(key, persisted);
|
|
2896
|
+
return persisted;
|
|
2897
|
+
}
|
|
2422
2898
|
try {
|
|
2423
2899
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2424
2900
|
if (res.status === 404) {
|
|
@@ -2433,6 +2909,7 @@ var ApiClient = class {
|
|
|
2433
2909
|
return { variant: null };
|
|
2434
2910
|
}
|
|
2435
2911
|
this.cache.setVariant(key, res.data);
|
|
2912
|
+
await this.writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2436
2913
|
return res.data;
|
|
2437
2914
|
} catch (error) {
|
|
2438
2915
|
this.log("Failed to get variant:", flagKey, error);
|
|
@@ -2478,7 +2955,7 @@ var ApiClient = class {
|
|
|
2478
2955
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2479
2956
|
if (!result) {
|
|
2480
2957
|
this.log("No campaign found for placement:", placement);
|
|
2481
|
-
this.cache.setCampaign(key, null);
|
|
2958
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2482
2959
|
return null;
|
|
2483
2960
|
}
|
|
2484
2961
|
this.log("Got campaign:", placement, result.variantKey);
|
|
@@ -2491,6 +2968,32 @@ var ApiClient = class {
|
|
|
2491
2968
|
return null;
|
|
2492
2969
|
}
|
|
2493
2970
|
}
|
|
2971
|
+
async getPrimaryCampaign(distinctId) {
|
|
2972
|
+
const key = `primary:${distinctId}`;
|
|
2973
|
+
const hit = this.cache.getCampaign(key);
|
|
2974
|
+
if (hit !== void 0) return hit;
|
|
2975
|
+
try {
|
|
2976
|
+
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2977
|
+
if (res.status === 404) {
|
|
2978
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
if (!res.ok) {
|
|
2982
|
+
this.log("Non-OK response for primary campaign:", res.status);
|
|
2983
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
2984
|
+
if (stale !== void 0) return stale;
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
this.log("Got primary campaign:", res.data?.campaignId);
|
|
2988
|
+
this.cache.setCampaign(key, res.data);
|
|
2989
|
+
return res.data;
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
this.log("Failed to get primary campaign:", error);
|
|
2992
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
2993
|
+
if (stale !== void 0) return stale;
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2494
2997
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2495
2998
|
const result = await validatePurchase(
|
|
2496
2999
|
this,
|
|
@@ -2514,6 +3017,35 @@ var ApiClient = class {
|
|
|
2514
3017
|
this.log("Subscription status:", result.hasActiveSubscription);
|
|
2515
3018
|
return result;
|
|
2516
3019
|
}
|
|
3020
|
+
async evaluateFlags(keys, distinctId) {
|
|
3021
|
+
const query = keys.map(encodeURIComponent).join(",");
|
|
3022
|
+
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
3023
|
+
try {
|
|
3024
|
+
const res = await this.httpClient.get(path, {
|
|
3025
|
+
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
3026
|
+
});
|
|
3027
|
+
if (!res.ok) {
|
|
3028
|
+
this.log("Non-OK response for evaluateFlags:", res.status);
|
|
3029
|
+
return {};
|
|
3030
|
+
}
|
|
3031
|
+
const raw = res.data;
|
|
3032
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
3033
|
+
const record = raw;
|
|
3034
|
+
const result = {};
|
|
3035
|
+
for (const key of Object.keys(record)) {
|
|
3036
|
+
const value = record[key];
|
|
3037
|
+
const variant = typeof value === "boolean" ? String(value) : typeof value === "string" ? value : null;
|
|
3038
|
+
result[key] = variant;
|
|
3039
|
+
const flagVariant = { variant };
|
|
3040
|
+
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
3041
|
+
await this.writeFlagToStorage(key, distinctId, flagVariant);
|
|
3042
|
+
}
|
|
3043
|
+
return result;
|
|
3044
|
+
} catch (error) {
|
|
3045
|
+
this.log("Failed to evaluateFlags:", error);
|
|
3046
|
+
return {};
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
2517
3049
|
async getConditionalFlag(flagKey, context) {
|
|
2518
3050
|
try {
|
|
2519
3051
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
@@ -2530,11 +3062,37 @@ var ApiClient = class {
|
|
|
2530
3062
|
return { value: false, flagKey };
|
|
2531
3063
|
}
|
|
2532
3064
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
3065
|
+
async getVariantFromCache(flagKey, distinctId) {
|
|
3066
|
+
const key = `${flagKey}:${distinctId}`;
|
|
3067
|
+
const hit = this.cache.getVariant(key);
|
|
3068
|
+
if (hit !== void 0) return hit;
|
|
3069
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
3070
|
+
if (persisted !== null) {
|
|
3071
|
+
this.cache.setVariant(key, persisted);
|
|
3072
|
+
return persisted;
|
|
3073
|
+
}
|
|
3074
|
+
return null;
|
|
3075
|
+
}
|
|
3076
|
+
async readFlagFromStorage(flagKey, distinctId) {
|
|
3077
|
+
try {
|
|
3078
|
+
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3079
|
+
if (!raw) return null;
|
|
3080
|
+
const parsed = JSON.parse(raw);
|
|
3081
|
+
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3082
|
+
const { variant, payload, cachedAt } = parsed;
|
|
3083
|
+
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
3084
|
+
return { variant, payload };
|
|
3085
|
+
} catch {
|
|
3086
|
+
return null;
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3090
|
+
try {
|
|
3091
|
+
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3092
|
+
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3093
|
+
} catch {
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
2538
3096
|
async postWithQueue(url, payload, label) {
|
|
2539
3097
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
2540
3098
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -2553,10 +3111,11 @@ var ApiClient = class {
|
|
|
2553
3111
|
throw error;
|
|
2554
3112
|
}
|
|
2555
3113
|
}
|
|
2556
|
-
log(...
|
|
2557
|
-
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
3114
|
+
log(..._args) {
|
|
2558
3115
|
}
|
|
2559
3116
|
};
|
|
3117
|
+
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3118
|
+
var ApiClient = _ApiClient;
|
|
2560
3119
|
|
|
2561
3120
|
// src/domains/campaign/CampaignError.ts
|
|
2562
3121
|
var CampaignError = class extends PaywalloError {
|
|
@@ -2575,39 +3134,72 @@ var CAMPAIGN_ERROR_CODES = {
|
|
|
2575
3134
|
|
|
2576
3135
|
// src/domains/campaign/CampaignGateService.ts
|
|
2577
3136
|
var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3137
|
+
var PRELOAD_WAIT_MAX_MS = 2e3;
|
|
3138
|
+
var PRELOAD_WAIT_INTERVAL_MS = 100;
|
|
2578
3139
|
var CampaignGateService = class {
|
|
2579
3140
|
constructor() {
|
|
2580
3141
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3142
|
+
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
2581
3143
|
}
|
|
2582
3144
|
async getCampaign(apiClient, placement, context) {
|
|
2583
3145
|
const distinctId = identityManager.getDistinctId();
|
|
2584
3146
|
if (!distinctId) return null;
|
|
2585
3147
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2586
3148
|
}
|
|
2587
|
-
async
|
|
2588
|
-
|
|
3149
|
+
async waitForPreload(placement) {
|
|
3150
|
+
if (!this.pendingPreloads.has(placement)) return;
|
|
3151
|
+
const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
|
|
3152
|
+
await new Promise((resolve) => {
|
|
3153
|
+
const check = () => {
|
|
3154
|
+
if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
|
|
3155
|
+
resolve();
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
|
|
3159
|
+
};
|
|
3160
|
+
check();
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
3163
|
+
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
3164
|
+
await this.waitForPreload(placement);
|
|
3165
|
+
const preloaded = this.getPreloadedCampaign(placement);
|
|
3166
|
+
const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
|
|
2589
3167
|
if (!campaign) {
|
|
2590
3168
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2591
3169
|
}
|
|
3170
|
+
if (!context?.forceShow) {
|
|
3171
|
+
const isActive = await hasActive();
|
|
3172
|
+
if (isActive) {
|
|
3173
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
3174
|
+
}
|
|
3175
|
+
}
|
|
2592
3176
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2593
3177
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2594
|
-
return { ...await paywallPresenter(
|
|
3178
|
+
return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2595
3179
|
}
|
|
2596
3180
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2597
|
-
|
|
2598
|
-
|
|
3181
|
+
const isActive = await hasActive();
|
|
3182
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3183
|
+
if (isActive) return true;
|
|
3184
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2599
3185
|
return r.purchased || r.restored;
|
|
2600
3186
|
}
|
|
2601
3187
|
async preloadCampaign(apiClient, placement, context) {
|
|
3188
|
+
this.pendingPreloads.add(placement);
|
|
2602
3189
|
try {
|
|
2603
3190
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2604
3191
|
if (!campaign?.paywall) {
|
|
2605
3192
|
return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
|
|
2606
3193
|
}
|
|
2607
3194
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3195
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
3196
|
+
console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3197
|
+
}
|
|
2608
3198
|
return { success: true };
|
|
2609
3199
|
} catch (error) {
|
|
2610
3200
|
return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
|
|
3201
|
+
} finally {
|
|
3202
|
+
this.pendingPreloads.delete(placement);
|
|
2611
3203
|
}
|
|
2612
3204
|
}
|
|
2613
3205
|
getPreloadedCampaign(placement) {
|
|
@@ -2621,12 +3213,13 @@ var CampaignGateService = class {
|
|
|
2621
3213
|
}
|
|
2622
3214
|
clearPreloaded() {
|
|
2623
3215
|
this.preloadedCampaigns.clear();
|
|
3216
|
+
this.pendingPreloads.clear();
|
|
2624
3217
|
}
|
|
2625
3218
|
};
|
|
2626
3219
|
var campaignGateService = new CampaignGateService();
|
|
2627
3220
|
|
|
2628
3221
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
2629
|
-
import { Platform as
|
|
3222
|
+
import { Platform as Platform6 } from "react-native";
|
|
2630
3223
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
2631
3224
|
var AdvertisingIdManager = class {
|
|
2632
3225
|
constructor() {
|
|
@@ -2637,7 +3230,7 @@ var AdvertisingIdManager = class {
|
|
|
2637
3230
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
2638
3231
|
try {
|
|
2639
3232
|
const tracking = await import("expo-tracking-transparency");
|
|
2640
|
-
if (
|
|
3233
|
+
if (Platform6.OS === "ios") {
|
|
2641
3234
|
if (!tracking.isAvailable()) {
|
|
2642
3235
|
const idfv2 = await this.collectIdfv();
|
|
2643
3236
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -2656,7 +3249,7 @@ var AdvertisingIdManager = class {
|
|
|
2656
3249
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
2657
3250
|
return this.cached;
|
|
2658
3251
|
}
|
|
2659
|
-
if (
|
|
3252
|
+
if (Platform6.OS === "android") {
|
|
2660
3253
|
const androidStatus = await tracking.getTrackingPermissionsAsync();
|
|
2661
3254
|
const optedIn = androidStatus.granted;
|
|
2662
3255
|
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
@@ -2715,14 +3308,14 @@ var FacebookIdManager = class {
|
|
|
2715
3308
|
var facebookIdManager = new FacebookIdManager();
|
|
2716
3309
|
|
|
2717
3310
|
// src/domains/identity/InstallTracker.ts
|
|
2718
|
-
import { Dimensions as Dimensions3, Platform as
|
|
3311
|
+
import { Dimensions as Dimensions3, Platform as Platform8 } from "react-native";
|
|
2719
3312
|
|
|
2720
3313
|
// src/domains/identity/InstallReferrerManager.ts
|
|
2721
|
-
import { Platform as
|
|
3314
|
+
import { Platform as Platform7 } from "react-native";
|
|
2722
3315
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
2723
3316
|
var InstallReferrerManager = class {
|
|
2724
3317
|
async getReferrer() {
|
|
2725
|
-
if (
|
|
3318
|
+
if (Platform7.OS !== "android") {
|
|
2726
3319
|
return this.emptyResult();
|
|
2727
3320
|
}
|
|
2728
3321
|
let timerId;
|
|
@@ -2784,20 +3377,6 @@ var installReferrerManager = new InstallReferrerManager();
|
|
|
2784
3377
|
// src/domains/session/SessionManager.ts
|
|
2785
3378
|
import { AppState as AppState2 } from "react-native";
|
|
2786
3379
|
|
|
2787
|
-
// src/domains/session/SessionError.ts
|
|
2788
|
-
var SessionError = class extends PaywalloError {
|
|
2789
|
-
constructor(code, message) {
|
|
2790
|
-
super("session", code, message);
|
|
2791
|
-
this.name = "SessionError";
|
|
2792
|
-
}
|
|
2793
|
-
};
|
|
2794
|
-
var SESSION_ERROR_CODES = {
|
|
2795
|
-
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
2796
|
-
START_FAILED: "SESSION_START_FAILED",
|
|
2797
|
-
END_FAILED: "SESSION_END_FAILED",
|
|
2798
|
-
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
2799
|
-
};
|
|
2800
|
-
|
|
2801
3380
|
// src/domains/session/sessionTracking.ts
|
|
2802
3381
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
2803
3382
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3025,10 +3604,7 @@ var SessionManager = class {
|
|
|
3025
3604
|
);
|
|
3026
3605
|
}
|
|
3027
3606
|
}
|
|
3028
|
-
log(...
|
|
3029
|
-
if (this.debug) {
|
|
3030
|
-
console.warn("[Paywallo Session]", ...args);
|
|
3031
|
-
}
|
|
3607
|
+
log(..._args) {
|
|
3032
3608
|
}
|
|
3033
3609
|
};
|
|
3034
3610
|
var sessionManager = new SessionManager();
|
|
@@ -3083,9 +3659,6 @@ var InstallTracker = class {
|
|
|
3083
3659
|
locale
|
|
3084
3660
|
};
|
|
3085
3661
|
} catch {
|
|
3086
|
-
if (this.debug) {
|
|
3087
|
-
console.warn("[Paywallo] Failed to collect device data for install event");
|
|
3088
|
-
}
|
|
3089
3662
|
}
|
|
3090
3663
|
const [fbAnonId, referrer, adIds] = await Promise.all([
|
|
3091
3664
|
facebookIdManager.getAnonId(),
|
|
@@ -3095,7 +3668,7 @@ var InstallTracker = class {
|
|
|
3095
3668
|
try {
|
|
3096
3669
|
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3097
3670
|
installedAt,
|
|
3098
|
-
platform:
|
|
3671
|
+
platform: Platform8.OS,
|
|
3099
3672
|
...sessionId && { sessionId },
|
|
3100
3673
|
...deviceData,
|
|
3101
3674
|
...fbAnonId && { fbAnonId },
|
|
@@ -3116,13 +3689,7 @@ var InstallTracker = class {
|
|
|
3116
3689
|
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3117
3690
|
} catch {
|
|
3118
3691
|
}
|
|
3119
|
-
|
|
3120
|
-
console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
|
|
3121
|
-
}
|
|
3122
|
-
} catch (error) {
|
|
3123
|
-
if (this.debug) {
|
|
3124
|
-
console.warn("[Paywallo] Failed to track install:", error);
|
|
3125
|
-
}
|
|
3692
|
+
} catch {
|
|
3126
3693
|
}
|
|
3127
3694
|
}
|
|
3128
3695
|
};
|
|
@@ -3137,6 +3704,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3137
3704
|
this.config = null;
|
|
3138
3705
|
this.apiClient = null;
|
|
3139
3706
|
this.initPromise = null;
|
|
3707
|
+
this.readyPromise = null;
|
|
3708
|
+
this.resolveReady = null;
|
|
3140
3709
|
this.paywallPresenter = null;
|
|
3141
3710
|
this.campaignPresenter = null;
|
|
3142
3711
|
this.subscriptionGetter = null;
|
|
@@ -3146,29 +3715,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3146
3715
|
this.pendingConfig = null;
|
|
3147
3716
|
this.networkCleanup = null;
|
|
3148
3717
|
this.initAttempts = 0;
|
|
3718
|
+
this.autoPreloadedPlacement = null;
|
|
3719
|
+
this.autoPreloadPlacementPromise = null;
|
|
3720
|
+
this.sessionFlagsMap = /* @__PURE__ */ new Map();
|
|
3149
3721
|
}
|
|
3150
3722
|
async init(config) {
|
|
3151
|
-
|
|
3723
|
+
if (!this.readyPromise) {
|
|
3724
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3725
|
+
this.resolveReady = resolve;
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3152
3728
|
if (this.config && this.apiClient) {
|
|
3153
|
-
console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3154
3729
|
return;
|
|
3155
3730
|
}
|
|
3156
3731
|
if (this.initPromise) {
|
|
3157
|
-
console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3158
3732
|
await this.initPromise;
|
|
3159
|
-
console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3160
3733
|
return;
|
|
3161
3734
|
}
|
|
3162
3735
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3163
|
-
console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3164
3736
|
this.pendingConfig = config;
|
|
3165
3737
|
this.initAttempts = 0;
|
|
3166
3738
|
this.initPromise = this.initWithRetry(config);
|
|
3167
3739
|
try {
|
|
3168
3740
|
await this.initPromise;
|
|
3169
|
-
console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3170
3741
|
} catch (error) {
|
|
3171
|
-
console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3172
3742
|
this.initPromise = null;
|
|
3173
3743
|
this.config = null;
|
|
3174
3744
|
this.apiClient = null;
|
|
@@ -3177,6 +3747,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3177
3747
|
throw error;
|
|
3178
3748
|
}
|
|
3179
3749
|
}
|
|
3750
|
+
isReady() {
|
|
3751
|
+
return this.config !== null && this.apiClient !== null;
|
|
3752
|
+
}
|
|
3753
|
+
waitUntilReady() {
|
|
3754
|
+
if (this.isReady()) return Promise.resolve();
|
|
3755
|
+
if (!this.readyPromise) {
|
|
3756
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3757
|
+
this.resolveReady = resolve;
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
return this.readyPromise;
|
|
3761
|
+
}
|
|
3180
3762
|
async initWithRetry(config) {
|
|
3181
3763
|
while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
|
|
3182
3764
|
try {
|
|
@@ -3191,7 +3773,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3191
3773
|
this.initAttempts++;
|
|
3192
3774
|
if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
|
|
3193
3775
|
const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
|
|
3194
|
-
if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
|
|
3195
3776
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3196
3777
|
}
|
|
3197
3778
|
}
|
|
@@ -3202,11 +3783,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3202
3783
|
this.networkCleanup = networkMonitor.addListener((state) => {
|
|
3203
3784
|
if (state !== "online") return;
|
|
3204
3785
|
if (this.config && this.apiClient) return;
|
|
3205
|
-
if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
|
|
3206
3786
|
this.initPromise = this.initWithRetry(config);
|
|
3207
|
-
this.initPromise.
|
|
3208
|
-
if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
|
|
3209
|
-
}).catch(() => {
|
|
3787
|
+
this.initPromise.catch(() => {
|
|
3210
3788
|
this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
|
|
3211
3789
|
});
|
|
3212
3790
|
});
|
|
@@ -3235,38 +3813,79 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3235
3813
|
} catch {
|
|
3236
3814
|
}
|
|
3237
3815
|
}
|
|
3816
|
+
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
3817
|
+
try {
|
|
3818
|
+
const results = await apiClient.evaluateFlags(keys, distinctId);
|
|
3819
|
+
this.sessionFlagsMap.clear();
|
|
3820
|
+
for (const key of keys) {
|
|
3821
|
+
this.sessionFlagsMap.set(key, key in results ? results[key] : null);
|
|
3822
|
+
}
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
for (const key of keys) {
|
|
3825
|
+
this.sessionFlagsMap.set(key, null);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3238
3829
|
async doInit(config) {
|
|
3239
3830
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3240
3831
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3241
|
-
console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3242
3832
|
await Promise.all([
|
|
3243
3833
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3244
3834
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3245
3835
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3246
3836
|
)
|
|
3247
3837
|
]);
|
|
3248
|
-
console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3249
|
-
console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3250
3838
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3251
3839
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3252
3840
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3253
|
-
|
|
3254
|
-
|
|
3841
|
+
subscriptionManager.init({
|
|
3842
|
+
serverUrl: this.apiClient.getBaseUrl(),
|
|
3843
|
+
appKey: config.appKey,
|
|
3844
|
+
cacheTTL: config.subscriptionCacheTTL,
|
|
3845
|
+
debug: config.debug
|
|
3846
|
+
});
|
|
3255
3847
|
await Promise.all([
|
|
3256
3848
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3257
3849
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3258
3850
|
]);
|
|
3259
3851
|
const distinctId = identityManager.getDistinctId();
|
|
3260
|
-
console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3261
3852
|
if (config.autoStartSession !== false) {
|
|
3262
3853
|
sessionManager.startSession().catch(() => {
|
|
3263
|
-
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
3264
3854
|
});
|
|
3265
3855
|
}
|
|
3266
3856
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3267
3857
|
});
|
|
3858
|
+
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3859
|
+
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3860
|
+
}
|
|
3268
3861
|
this.config = config;
|
|
3269
|
-
|
|
3862
|
+
if (this.resolveReady) {
|
|
3863
|
+
this.resolveReady();
|
|
3864
|
+
this.resolveReady = null;
|
|
3865
|
+
}
|
|
3866
|
+
if (config.debug) console.info("[Paywallo INIT] complete");
|
|
3867
|
+
if (this.apiClient) {
|
|
3868
|
+
const apiClientRef = this.apiClient;
|
|
3869
|
+
if (config.autoPreloadCampaign) {
|
|
3870
|
+
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
3871
|
+
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
3872
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
3873
|
+
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
3874
|
+
});
|
|
3875
|
+
} else {
|
|
3876
|
+
this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
|
|
3877
|
+
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
3878
|
+
if (placement) {
|
|
3879
|
+
this.autoPreloadedPlacement = placement;
|
|
3880
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
3881
|
+
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
3882
|
+
});
|
|
3883
|
+
return placement;
|
|
3884
|
+
}
|
|
3885
|
+
return null;
|
|
3886
|
+
}).catch(() => null);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3270
3889
|
}
|
|
3271
3890
|
async identify(options) {
|
|
3272
3891
|
this.ensureInitialized();
|
|
@@ -3276,7 +3895,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3276
3895
|
const apiClient = this.getApiClientOrThrow();
|
|
3277
3896
|
const distinctId = identityManager.getDistinctId();
|
|
3278
3897
|
if (!distinctId) {
|
|
3279
|
-
console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3280
3898
|
return;
|
|
3281
3899
|
}
|
|
3282
3900
|
if (!isValidEventName(eventName)) {
|
|
@@ -3284,25 +3902,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3284
3902
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
3285
3903
|
}
|
|
3286
3904
|
const sessionId = sessionManager.getSessionId();
|
|
3287
|
-
if (eventName === "$purchase_completed") {
|
|
3288
|
-
console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3289
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3290
|
-
productId: options?.properties?.productId,
|
|
3291
|
-
priceUsd: options?.properties?.priceUsd,
|
|
3292
|
-
price: options?.properties?.price,
|
|
3293
|
-
currency: options?.properties?.currency,
|
|
3294
|
-
placement: options?.properties?.placement
|
|
3295
|
-
}));
|
|
3296
|
-
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3297
|
-
console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3298
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3299
|
-
placement: options?.properties?.placement,
|
|
3300
|
-
variantKey: options?.properties?.variantKey,
|
|
3301
|
-
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3302
|
-
}));
|
|
3303
|
-
} else {
|
|
3304
|
-
console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3305
|
-
}
|
|
3306
3905
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3307
3906
|
...identityManager.getProperties(),
|
|
3308
3907
|
...options?.properties,
|
|
@@ -3313,7 +3912,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3313
3912
|
const apiClient = this.getApiClientOrThrow();
|
|
3314
3913
|
const distinctId = identityManager.getDistinctId();
|
|
3315
3914
|
if (!distinctId) {
|
|
3316
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3317
3915
|
return;
|
|
3318
3916
|
}
|
|
3319
3917
|
if (!Number.isInteger(index) || index < 0) {
|
|
@@ -3337,7 +3935,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3337
3935
|
const apiClient = this.getApiClientOrThrow();
|
|
3338
3936
|
const distinctId = identityManager.getDistinctId();
|
|
3339
3937
|
if (!distinctId) {
|
|
3340
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3341
3938
|
return;
|
|
3342
3939
|
}
|
|
3343
3940
|
if (!actionName || typeof actionName !== "string") {
|
|
@@ -3352,22 +3949,32 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3352
3949
|
...sessionId && { sessionId }
|
|
3353
3950
|
});
|
|
3354
3951
|
}
|
|
3952
|
+
async getVariantCached(flagKey, defaultValue) {
|
|
3953
|
+
this.ensureInitialized();
|
|
3954
|
+
const apiClient = this.getApiClientOrThrow();
|
|
3955
|
+
const distinctId = identityManager.getDistinctId();
|
|
3956
|
+
const result = await apiClient.getVariantFromCache(flagKey, distinctId);
|
|
3957
|
+
if (result) return result;
|
|
3958
|
+
void apiClient.getVariant(flagKey, distinctId).catch(() => {
|
|
3959
|
+
});
|
|
3960
|
+
return { variant: defaultValue ?? null, payload: void 0 };
|
|
3961
|
+
}
|
|
3355
3962
|
async getVariant(flagKey) {
|
|
3356
3963
|
try {
|
|
3357
3964
|
const distinctId = identityManager.getDistinctId();
|
|
3358
3965
|
if (!distinctId) {
|
|
3359
|
-
console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3360
3966
|
return { variant: null };
|
|
3361
3967
|
}
|
|
3362
|
-
|
|
3363
|
-
console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3364
|
-
return result;
|
|
3968
|
+
return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3365
3969
|
} catch (error) {
|
|
3366
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
3367
3970
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
3368
3971
|
return { variant: null };
|
|
3369
3972
|
}
|
|
3370
3973
|
}
|
|
3974
|
+
async evaluateFlags(keys) {
|
|
3975
|
+
this.ensureInitialized();
|
|
3976
|
+
return this.getApiClientOrThrow().evaluateFlags(keys, identityManager.getDistinctId());
|
|
3977
|
+
}
|
|
3371
3978
|
async getConditionalFlag(flagKey, context) {
|
|
3372
3979
|
try {
|
|
3373
3980
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3375,7 +3982,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3375
3982
|
const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
|
|
3376
3983
|
return result.value;
|
|
3377
3984
|
} catch (error) {
|
|
3378
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
|
|
3379
3985
|
if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
|
|
3380
3986
|
return false;
|
|
3381
3987
|
}
|
|
@@ -3393,18 +3999,22 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3393
3999
|
}
|
|
3394
4000
|
async presentCampaign(placement, context) {
|
|
3395
4001
|
this.ensureInitialized();
|
|
3396
|
-
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, this.paywallPresenter, this.campaignPresenter, context);
|
|
4002
|
+
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
3397
4003
|
}
|
|
3398
4004
|
async hasActiveSubscription() {
|
|
3399
4005
|
this.ensureInitialized();
|
|
3400
4006
|
try {
|
|
3401
|
-
if (this.activeChecker)
|
|
4007
|
+
if (this.activeChecker) {
|
|
4008
|
+
return await this.activeChecker();
|
|
4009
|
+
}
|
|
3402
4010
|
const distinctId = identityManager.getDistinctId();
|
|
3403
|
-
if (!distinctId || !this.apiClient)
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
4011
|
+
if (!distinctId || !this.apiClient) {
|
|
4012
|
+
return false;
|
|
4013
|
+
}
|
|
4014
|
+
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
4015
|
+
return status.hasActiveSubscription;
|
|
4016
|
+
} catch (error) {
|
|
4017
|
+
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
3408
4018
|
return false;
|
|
3409
4019
|
}
|
|
3410
4020
|
}
|
|
@@ -3418,7 +4028,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3418
4028
|
if (!status.subscription) return null;
|
|
3419
4029
|
return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
|
|
3420
4030
|
} catch {
|
|
3421
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
|
|
3422
4031
|
return null;
|
|
3423
4032
|
}
|
|
3424
4033
|
}
|
|
@@ -3429,19 +4038,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3429
4038
|
}
|
|
3430
4039
|
async reset() {
|
|
3431
4040
|
await identityManager.reset();
|
|
4041
|
+
await subscriptionCache.invalidateAll();
|
|
3432
4042
|
}
|
|
3433
4043
|
async fullReset() {
|
|
3434
4044
|
const wasDebug = this.config?.debug ?? false;
|
|
3435
4045
|
await sessionManager.endSession();
|
|
3436
4046
|
await identityManager.reset();
|
|
4047
|
+
await subscriptionCache.invalidateAll();
|
|
3437
4048
|
queueProcessor.dispose();
|
|
3438
4049
|
offlineQueue.dispose();
|
|
3439
4050
|
networkMonitor.dispose();
|
|
3440
4051
|
campaignGateService.clearPreloaded();
|
|
3441
4052
|
this.cleanupNetworkRecovery();
|
|
4053
|
+
this.sessionFlagsMap.clear();
|
|
3442
4054
|
this.apiClient = null;
|
|
3443
4055
|
this.config = null;
|
|
3444
4056
|
this.initPromise = null;
|
|
4057
|
+
this.readyPromise = null;
|
|
4058
|
+
this.resolveReady = null;
|
|
3445
4059
|
this.pendingConfig = null;
|
|
3446
4060
|
this.initAttempts = 0;
|
|
3447
4061
|
this.paywallPresenter = null;
|
|
@@ -3450,7 +4064,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3450
4064
|
this.activeChecker = null;
|
|
3451
4065
|
this.restoreHandler = null;
|
|
3452
4066
|
this.lastOnboardingStepTimestamp = null;
|
|
3453
|
-
|
|
4067
|
+
this.autoPreloadedPlacement = null;
|
|
4068
|
+
this.autoPreloadPlacementPromise = null;
|
|
4069
|
+
}
|
|
4070
|
+
getSessionFlag(key) {
|
|
4071
|
+
if (!this.config || !this.apiClient) return null;
|
|
4072
|
+
const value = this.sessionFlagsMap.get(key);
|
|
4073
|
+
return value !== void 0 ? value : null;
|
|
3454
4074
|
}
|
|
3455
4075
|
getConfig() {
|
|
3456
4076
|
return this.config;
|
|
@@ -3539,6 +4159,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3539
4159
|
getPreloadedCampaign(placement) {
|
|
3540
4160
|
return campaignGateService.getPreloadedCampaign(placement);
|
|
3541
4161
|
}
|
|
4162
|
+
isPreloaded(placement) {
|
|
4163
|
+
return campaignGateService.getPreloadedCampaign(placement) !== null;
|
|
4164
|
+
}
|
|
4165
|
+
getAutoPreloadedPlacement() {
|
|
4166
|
+
return this.autoPreloadedPlacement;
|
|
4167
|
+
}
|
|
4168
|
+
waitForAutoPreloadedPlacement() {
|
|
4169
|
+
return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
|
|
4170
|
+
}
|
|
3542
4171
|
isOnline() {
|
|
3543
4172
|
return networkMonitor.isOnline();
|
|
3544
4173
|
}
|
|
@@ -3558,57 +4187,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3558
4187
|
ensureInitialized() {
|
|
3559
4188
|
if (!this.config || !this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized. Call Paywallo.init() first");
|
|
3560
4189
|
}
|
|
3561
|
-
};
|
|
3562
|
-
_PaywalloClientClass.MAX_INIT_RETRIES = 3;
|
|
3563
|
-
_PaywalloClientClass.RETRY_DELAYS = [2e3, 5e3, 1e4];
|
|
3564
|
-
var PaywalloClientClass = _PaywalloClientClass;
|
|
3565
|
-
var PaywalloClient = new PaywalloClientClass();
|
|
3566
|
-
|
|
3567
|
-
// src/context/PaywalloContext.ts
|
|
3568
|
-
import { createContext as createContext2 } from "react";
|
|
3569
|
-
var PaywalloContext = createContext2(null);
|
|
3570
|
-
|
|
3571
|
-
// src/utils/localization.ts
|
|
3572
|
-
import { NativeModules as NativeModules3, Platform as Platform7 } from "react-native";
|
|
3573
|
-
var currentLanguage = "pt-BR";
|
|
3574
|
-
var defaultLanguage = "pt-BR";
|
|
3575
|
-
function setCurrentLanguage(language) {
|
|
3576
|
-
currentLanguage = language;
|
|
3577
|
-
}
|
|
3578
|
-
function setDefaultLanguage(language) {
|
|
3579
|
-
defaultLanguage = language;
|
|
3580
|
-
}
|
|
3581
|
-
function getCurrentLanguage() {
|
|
3582
|
-
return currentLanguage;
|
|
3583
|
-
}
|
|
3584
|
-
function getDefaultLanguage() {
|
|
3585
|
-
return defaultLanguage;
|
|
3586
|
-
}
|
|
3587
|
-
function getLocalizedString(text) {
|
|
3588
|
-
if (!text) return "";
|
|
3589
|
-
if (typeof text === "string") return text;
|
|
3590
|
-
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
3591
|
-
}
|
|
3592
|
-
function detectDeviceLanguage() {
|
|
3593
|
-
try {
|
|
3594
|
-
if (Platform7.OS === "ios") {
|
|
3595
|
-
const settings = NativeModules3.SettingsManager?.settings;
|
|
3596
|
-
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
3597
|
-
}
|
|
3598
|
-
return NativeModules3.I18nManager?.localeIdentifier ?? "en-US";
|
|
3599
|
-
} catch {
|
|
3600
|
-
return "en-US";
|
|
3601
|
-
}
|
|
3602
|
-
}
|
|
3603
|
-
function initLocalization(options) {
|
|
3604
|
-
if (options?.defaultLanguage) {
|
|
3605
|
-
defaultLanguage = options.defaultLanguage;
|
|
3606
|
-
}
|
|
3607
|
-
if (options?.detectDevice !== false) {
|
|
3608
|
-
const deviceLanguage = detectDeviceLanguage();
|
|
3609
|
-
currentLanguage = deviceLanguage;
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
4190
|
+
};
|
|
4191
|
+
_PaywalloClientClass.MAX_INIT_RETRIES = 3;
|
|
4192
|
+
_PaywalloClientClass.RETRY_DELAYS = [2e3, 5e3, 1e4];
|
|
4193
|
+
var PaywalloClientClass = _PaywalloClientClass;
|
|
4194
|
+
var PaywalloClient = new PaywalloClientClass();
|
|
4195
|
+
|
|
4196
|
+
// src/context/PaywalloContext.ts
|
|
4197
|
+
import { createContext as createContext2 } from "react";
|
|
4198
|
+
var PaywalloContext = createContext2(null);
|
|
3612
4199
|
|
|
3613
4200
|
// src/hooks/usePaywallo.ts
|
|
3614
4201
|
import { useContext as useContext2 } from "react";
|
|
@@ -3621,7 +4208,7 @@ function usePaywallo() {
|
|
|
3621
4208
|
}
|
|
3622
4209
|
|
|
3623
4210
|
// src/hooks/useProducts.ts
|
|
3624
|
-
import { useCallback as
|
|
4211
|
+
import { useCallback as useCallback6, useEffect as useEffect5, useState as useState6 } from "react";
|
|
3625
4212
|
|
|
3626
4213
|
// src/utils/productMappers.ts
|
|
3627
4214
|
function mapToIAPProduct(product) {
|
|
@@ -3635,7 +4222,8 @@ function mapToIAPProduct(product) {
|
|
|
3635
4222
|
type: product.type === "subscription" ? "subs" : "inapp",
|
|
3636
4223
|
subscriptionPeriodAndroid: product.subscriptionPeriod,
|
|
3637
4224
|
introductoryPrice: product.introductoryPrice,
|
|
3638
|
-
freeTrialPeriodAndroid: product.freeTrialPeriod
|
|
4225
|
+
freeTrialPeriodAndroid: product.freeTrialPeriod,
|
|
4226
|
+
trialDays: product.trialDays ?? 0
|
|
3639
4227
|
};
|
|
3640
4228
|
}
|
|
3641
4229
|
function mapToFormattedProduct(product) {
|
|
@@ -3650,6 +4238,7 @@ function mapToFormattedProduct(product) {
|
|
|
3650
4238
|
subscriptionPeriod: product.subscriptionPeriod,
|
|
3651
4239
|
introductoryPrice: product.introductoryPrice,
|
|
3652
4240
|
freeTrialPeriod: product.freeTrialPeriod,
|
|
4241
|
+
trialDays: product.trialDays ?? 0,
|
|
3653
4242
|
pricePerMonth: product.pricePerMonth,
|
|
3654
4243
|
pricePerWeek: product.pricePerWeek,
|
|
3655
4244
|
pricePerDay: product.pricePerDay
|
|
@@ -3658,11 +4247,11 @@ function mapToFormattedProduct(product) {
|
|
|
3658
4247
|
|
|
3659
4248
|
// src/hooks/useProducts.ts
|
|
3660
4249
|
function useProducts(initialProductIds) {
|
|
3661
|
-
const [products, setProducts] =
|
|
3662
|
-
const [formattedProducts, setFormattedProducts] =
|
|
3663
|
-
const [isLoading, setIsLoading] =
|
|
3664
|
-
const [error, setError] =
|
|
3665
|
-
const loadProducts =
|
|
4250
|
+
const [products, setProducts] = useState6([]);
|
|
4251
|
+
const [formattedProducts, setFormattedProducts] = useState6([]);
|
|
4252
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
4253
|
+
const [error, setError] = useState6(null);
|
|
4254
|
+
const loadProducts = useCallback6(async (productIds) => {
|
|
3666
4255
|
setIsLoading(true);
|
|
3667
4256
|
setError(null);
|
|
3668
4257
|
try {
|
|
@@ -3681,19 +4270,19 @@ function useProducts(initialProductIds) {
|
|
|
3681
4270
|
void loadProducts(initialProductIds);
|
|
3682
4271
|
}
|
|
3683
4272
|
}, []);
|
|
3684
|
-
const getProduct =
|
|
4273
|
+
const getProduct = useCallback6(
|
|
3685
4274
|
(productId) => {
|
|
3686
4275
|
return products.find((p) => p.productId === productId);
|
|
3687
4276
|
},
|
|
3688
4277
|
[products]
|
|
3689
4278
|
);
|
|
3690
|
-
const getFormattedProduct =
|
|
4279
|
+
const getFormattedProduct = useCallback6(
|
|
3691
4280
|
(productId) => {
|
|
3692
4281
|
return formattedProducts.find((p) => p.productId === productId);
|
|
3693
4282
|
},
|
|
3694
4283
|
[formattedProducts]
|
|
3695
4284
|
);
|
|
3696
|
-
const getPriceInfo =
|
|
4285
|
+
const getPriceInfo = useCallback6(
|
|
3697
4286
|
(productId) => {
|
|
3698
4287
|
const p = formattedProducts.find((fp) => fp.productId === productId);
|
|
3699
4288
|
if (!p) return void 0;
|
|
@@ -3722,39 +4311,49 @@ function useProducts(initialProductIds) {
|
|
|
3722
4311
|
}
|
|
3723
4312
|
|
|
3724
4313
|
// src/hooks/usePurchase.ts
|
|
3725
|
-
import { useCallback as
|
|
4314
|
+
import { useCallback as useCallback7, useState as useState7 } from "react";
|
|
3726
4315
|
function usePurchase() {
|
|
3727
|
-
const [state, setState] =
|
|
3728
|
-
const [error, setError] =
|
|
3729
|
-
const purchase =
|
|
4316
|
+
const [state, setState] = useState7("idle");
|
|
4317
|
+
const [error, setError] = useState7(null);
|
|
4318
|
+
const purchase = useCallback7(async (productId) => {
|
|
3730
4319
|
setState("purchasing");
|
|
3731
4320
|
setError(null);
|
|
3732
4321
|
try {
|
|
3733
4322
|
const iapService = getIAPService();
|
|
3734
|
-
const result = await iapService.purchase(productId
|
|
4323
|
+
const result = await iapService.purchase(productId, {
|
|
4324
|
+
onValidating: () => setState("validating")
|
|
4325
|
+
});
|
|
3735
4326
|
if (result.success && result.purchase) {
|
|
3736
4327
|
setState("idle");
|
|
3737
4328
|
return {
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
4329
|
+
status: "success",
|
|
4330
|
+
purchase: {
|
|
4331
|
+
productId: result.purchase.productId,
|
|
4332
|
+
transactionId: result.purchase.transactionId,
|
|
4333
|
+
transactionDate: result.purchase.transactionDate,
|
|
4334
|
+
transactionReceipt: result.purchase.receipt
|
|
4335
|
+
}
|
|
3742
4336
|
};
|
|
3743
4337
|
}
|
|
3744
4338
|
if (result.error) {
|
|
3745
|
-
|
|
3746
|
-
setError(purchaseError);
|
|
4339
|
+
setError(result.error);
|
|
3747
4340
|
setState("error");
|
|
3748
|
-
|
|
4341
|
+
return { status: "failed", error: result.error };
|
|
3749
4342
|
}
|
|
3750
4343
|
setState("idle");
|
|
3751
|
-
|
|
4344
|
+
return { status: "cancelled" };
|
|
3752
4345
|
} catch (err) {
|
|
4346
|
+
const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
|
|
4347
|
+
if (purchaseError.userCancelled) {
|
|
4348
|
+
setState("idle");
|
|
4349
|
+
return { status: "cancelled" };
|
|
4350
|
+
}
|
|
4351
|
+
setError(purchaseError);
|
|
3753
4352
|
setState("error");
|
|
3754
|
-
|
|
4353
|
+
return { status: "failed", error: purchaseError };
|
|
3755
4354
|
}
|
|
3756
4355
|
}, []);
|
|
3757
|
-
const restore =
|
|
4356
|
+
const restore = useCallback7(async () => {
|
|
3758
4357
|
setState("restoring");
|
|
3759
4358
|
setError(null);
|
|
3760
4359
|
try {
|
|
@@ -3780,231 +4379,19 @@ function usePurchase() {
|
|
|
3780
4379
|
restore,
|
|
3781
4380
|
error,
|
|
3782
4381
|
isLoading: state === "loading",
|
|
3783
|
-
isPurchasing: state === "purchasing",
|
|
4382
|
+
isPurchasing: state === "purchasing" || state === "validating",
|
|
4383
|
+
isValidating: state === "validating",
|
|
3784
4384
|
isRestoring: state === "restoring"
|
|
3785
4385
|
};
|
|
3786
4386
|
}
|
|
3787
4387
|
|
|
3788
4388
|
// src/hooks/useSubscription.ts
|
|
3789
|
-
import { useCallback as
|
|
3790
|
-
|
|
3791
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3792
|
-
import { Platform as Platform8 } from "react-native";
|
|
3793
|
-
|
|
3794
|
-
// src/domains/subscription/SubscriptionCache.ts
|
|
3795
|
-
var CACHE_KEY = "subscription_cache";
|
|
3796
|
-
var DEFAULT_TTL = 5 * 60 * 1e3;
|
|
3797
|
-
var SubscriptionCache = class {
|
|
3798
|
-
constructor(ttl = DEFAULT_TTL) {
|
|
3799
|
-
this.memoryCache = null;
|
|
3800
|
-
this.ttl = ttl;
|
|
3801
|
-
this.storage = new SecureStorage();
|
|
3802
|
-
}
|
|
3803
|
-
async get() {
|
|
3804
|
-
if (this.memoryCache && !this.isExpired(this.memoryCache)) {
|
|
3805
|
-
return this.memoryCache;
|
|
3806
|
-
}
|
|
3807
|
-
try {
|
|
3808
|
-
const stored = await this.storage.get(CACHE_KEY);
|
|
3809
|
-
if (!stored) {
|
|
3810
|
-
return null;
|
|
3811
|
-
}
|
|
3812
|
-
const cached = JSON.parse(stored);
|
|
3813
|
-
if (cached.data.subscription?.expiresAt) {
|
|
3814
|
-
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
3815
|
-
}
|
|
3816
|
-
if (cached.data.subscription?.originalPurchaseDate) {
|
|
3817
|
-
cached.data.subscription.originalPurchaseDate = new Date(
|
|
3818
|
-
cached.data.subscription.originalPurchaseDate
|
|
3819
|
-
);
|
|
3820
|
-
}
|
|
3821
|
-
if (cached.data.subscription?.latestPurchaseDate) {
|
|
3822
|
-
cached.data.subscription.latestPurchaseDate = new Date(
|
|
3823
|
-
cached.data.subscription.latestPurchaseDate
|
|
3824
|
-
);
|
|
3825
|
-
}
|
|
3826
|
-
if (cached.data.subscription?.cancellationDate) {
|
|
3827
|
-
cached.data.subscription.cancellationDate = new Date(
|
|
3828
|
-
cached.data.subscription.cancellationDate
|
|
3829
|
-
);
|
|
3830
|
-
}
|
|
3831
|
-
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
3832
|
-
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
3833
|
-
cached.data.subscription.gracePeriodExpiresAt
|
|
3834
|
-
);
|
|
3835
|
-
}
|
|
3836
|
-
cached.isStale = this.isExpired(cached);
|
|
3837
|
-
if (!cached.isStale) {
|
|
3838
|
-
this.memoryCache = cached;
|
|
3839
|
-
}
|
|
3840
|
-
return cached;
|
|
3841
|
-
} catch {
|
|
3842
|
-
return null;
|
|
3843
|
-
}
|
|
3844
|
-
}
|
|
3845
|
-
async set(data) {
|
|
3846
|
-
const cached = {
|
|
3847
|
-
data,
|
|
3848
|
-
cachedAt: Date.now(),
|
|
3849
|
-
isStale: false
|
|
3850
|
-
};
|
|
3851
|
-
this.memoryCache = cached;
|
|
3852
|
-
try {
|
|
3853
|
-
await this.storage.set(CACHE_KEY, JSON.stringify(cached));
|
|
3854
|
-
} catch {
|
|
3855
|
-
}
|
|
3856
|
-
}
|
|
3857
|
-
async invalidate() {
|
|
3858
|
-
this.memoryCache = null;
|
|
3859
|
-
try {
|
|
3860
|
-
await this.storage.remove(CACHE_KEY);
|
|
3861
|
-
} catch {
|
|
3862
|
-
}
|
|
3863
|
-
}
|
|
3864
|
-
isExpired(cached) {
|
|
3865
|
-
return Date.now() - cached.cachedAt > this.ttl;
|
|
3866
|
-
}
|
|
3867
|
-
setTTL(ttl) {
|
|
3868
|
-
this.ttl = ttl;
|
|
3869
|
-
}
|
|
3870
|
-
};
|
|
3871
|
-
|
|
3872
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3873
|
-
var SubscriptionManagerClass = class {
|
|
3874
|
-
constructor() {
|
|
3875
|
-
this.config = null;
|
|
3876
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
3877
|
-
this.userId = null;
|
|
3878
|
-
this.cache = new SubscriptionCache();
|
|
3879
|
-
}
|
|
3880
|
-
init(config) {
|
|
3881
|
-
this.config = config;
|
|
3882
|
-
if (config.cacheTTL) {
|
|
3883
|
-
this.cache.setTTL(config.cacheTTL);
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
setUserId(userId) {
|
|
3887
|
-
if (this.userId !== userId) {
|
|
3888
|
-
this.userId = userId;
|
|
3889
|
-
void this.cache.invalidate();
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
3892
|
-
async hasActiveSubscription(forceRefresh = false) {
|
|
3893
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3894
|
-
return status.hasActiveSubscription;
|
|
3895
|
-
}
|
|
3896
|
-
async getSubscription(forceRefresh = false) {
|
|
3897
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3898
|
-
return status.subscription;
|
|
3899
|
-
}
|
|
3900
|
-
async getEntitlements(forceRefresh = false) {
|
|
3901
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3902
|
-
return status.entitlements;
|
|
3903
|
-
}
|
|
3904
|
-
async getSubscriptionStatus(forceRefresh = false) {
|
|
3905
|
-
if (!this.config) {
|
|
3906
|
-
return this.getEmptyStatus();
|
|
3907
|
-
}
|
|
3908
|
-
if (!forceRefresh) {
|
|
3909
|
-
const cached = await this.cache.get();
|
|
3910
|
-
if (cached && !cached.isStale) {
|
|
3911
|
-
return cached.data;
|
|
3912
|
-
}
|
|
3913
|
-
}
|
|
3914
|
-
try {
|
|
3915
|
-
const status = await this.fetchSubscriptionStatus();
|
|
3916
|
-
await this.cache.set(status);
|
|
3917
|
-
this.notifyListeners(status);
|
|
3918
|
-
return status;
|
|
3919
|
-
} catch (error) {
|
|
3920
|
-
this.log("Failed to fetch subscription status", error);
|
|
3921
|
-
const cached = await this.cache.get();
|
|
3922
|
-
if (cached) {
|
|
3923
|
-
return cached.data;
|
|
3924
|
-
}
|
|
3925
|
-
return this.getEmptyStatus();
|
|
3926
|
-
}
|
|
3927
|
-
}
|
|
3928
|
-
async restorePurchases() {
|
|
3929
|
-
if (!this.config) {
|
|
3930
|
-
throw new SessionError(
|
|
3931
|
-
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
3932
|
-
"SubscriptionManager not initialized"
|
|
3933
|
-
);
|
|
3934
|
-
}
|
|
3935
|
-
await this.cache.invalidate();
|
|
3936
|
-
return this.getSubscriptionStatus(true);
|
|
3937
|
-
}
|
|
3938
|
-
async fetchSubscriptionStatus() {
|
|
3939
|
-
if (!this.config) {
|
|
3940
|
-
return this.getEmptyStatus();
|
|
3941
|
-
}
|
|
3942
|
-
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
3943
|
-
if (this.userId) {
|
|
3944
|
-
url.searchParams.set("userId", this.userId);
|
|
3945
|
-
}
|
|
3946
|
-
url.searchParams.set("platform", Platform8.OS);
|
|
3947
|
-
const response = await fetch(url.toString(), {
|
|
3948
|
-
method: "GET",
|
|
3949
|
-
headers: {
|
|
3950
|
-
"Content-Type": "application/json",
|
|
3951
|
-
"X-App-Key": this.config.appKey
|
|
3952
|
-
}
|
|
3953
|
-
});
|
|
3954
|
-
if (!response.ok) {
|
|
3955
|
-
throw new SessionError(
|
|
3956
|
-
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
3957
|
-
`Failed to fetch subscription status: ${response.status}`
|
|
3958
|
-
);
|
|
3959
|
-
}
|
|
3960
|
-
const data = await response.json();
|
|
3961
|
-
if (data.subscription) {
|
|
3962
|
-
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
3963
|
-
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
3964
|
-
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
3965
|
-
if (data.subscription.cancellationDate) {
|
|
3966
|
-
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
3967
|
-
}
|
|
3968
|
-
if (data.subscription.gracePeriodExpiresAt) {
|
|
3969
|
-
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
return data;
|
|
3973
|
-
}
|
|
3974
|
-
getEmptyStatus() {
|
|
3975
|
-
return {
|
|
3976
|
-
hasActiveSubscription: false,
|
|
3977
|
-
subscription: null,
|
|
3978
|
-
entitlements: []
|
|
3979
|
-
};
|
|
3980
|
-
}
|
|
3981
|
-
addListener(listener) {
|
|
3982
|
-
this.listeners.add(listener);
|
|
3983
|
-
return () => this.listeners.delete(listener);
|
|
3984
|
-
}
|
|
3985
|
-
notifyListeners(status) {
|
|
3986
|
-
for (const listener of this.listeners) {
|
|
3987
|
-
listener(status);
|
|
3988
|
-
}
|
|
3989
|
-
}
|
|
3990
|
-
async onPurchaseComplete() {
|
|
3991
|
-
await this.cache.invalidate();
|
|
3992
|
-
await this.getSubscriptionStatus(true);
|
|
3993
|
-
}
|
|
3994
|
-
log(message, data) {
|
|
3995
|
-
if (this.config?.debug) {
|
|
3996
|
-
console.warn(`[SubscriptionManager] ${message}`, data ?? "");
|
|
3997
|
-
}
|
|
3998
|
-
}
|
|
3999
|
-
};
|
|
4000
|
-
var subscriptionManager = new SubscriptionManagerClass();
|
|
4001
|
-
|
|
4002
|
-
// src/hooks/useSubscription.ts
|
|
4389
|
+
import { useCallback as useCallback8, useEffect as useEffect6, useState as useState8 } from "react";
|
|
4003
4390
|
function useSubscription() {
|
|
4004
|
-
const [status, setStatus] =
|
|
4005
|
-
const [isLoading, setIsLoading] =
|
|
4006
|
-
const [error, setError] =
|
|
4007
|
-
const refresh =
|
|
4391
|
+
const [status, setStatus] = useState8(null);
|
|
4392
|
+
const [isLoading, setIsLoading] = useState8(true);
|
|
4393
|
+
const [error, setError] = useState8(null);
|
|
4394
|
+
const refresh = useCallback8(async () => {
|
|
4008
4395
|
setIsLoading(true);
|
|
4009
4396
|
setError(null);
|
|
4010
4397
|
try {
|
|
@@ -4017,7 +4404,7 @@ function useSubscription() {
|
|
|
4017
4404
|
setIsLoading(false);
|
|
4018
4405
|
}
|
|
4019
4406
|
}, []);
|
|
4020
|
-
const restore =
|
|
4407
|
+
const restore = useCallback8(async () => {
|
|
4021
4408
|
setIsLoading(true);
|
|
4022
4409
|
setError(null);
|
|
4023
4410
|
try {
|
|
@@ -4054,11 +4441,11 @@ function useSubscription() {
|
|
|
4054
4441
|
}
|
|
4055
4442
|
|
|
4056
4443
|
// src/PaywalloProvider.tsx
|
|
4057
|
-
import { useCallback as
|
|
4444
|
+
import { useCallback as useCallback13, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
|
|
4058
4445
|
import { Animated as Animated3, Dimensions as Dimensions4, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
|
|
4059
4446
|
|
|
4060
4447
|
// src/hooks/useCampaignPreload.ts
|
|
4061
|
-
import { useCallback as
|
|
4448
|
+
import { useCallback as useCallback9, useRef as useRef6, useState as useState9 } from "react";
|
|
4062
4449
|
|
|
4063
4450
|
// src/utils/serverProduct.ts
|
|
4064
4451
|
var PERIOD_MAP = {
|
|
@@ -4116,26 +4503,25 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
4116
4503
|
function isCacheEntryValid(entry) {
|
|
4117
4504
|
return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
|
|
4118
4505
|
}
|
|
4119
|
-
function
|
|
4506
|
+
function getCacheEntry(cache, placement) {
|
|
4120
4507
|
const entry = cache.get(placement);
|
|
4121
4508
|
if (!isCacheEntryValid(entry)) return null;
|
|
4122
|
-
cache.delete(placement);
|
|
4123
4509
|
return entry ?? null;
|
|
4124
4510
|
}
|
|
4125
4511
|
|
|
4126
4512
|
// src/hooks/useCampaignPreload.ts
|
|
4127
4513
|
function useCampaignPreload() {
|
|
4128
|
-
const [preloadedWebView, setPreloadedWebView] =
|
|
4514
|
+
const [preloadedWebView, setPreloadedWebView] = useState9(null);
|
|
4129
4515
|
const preloadedCampaignsRef = useRef6(/* @__PURE__ */ new Map());
|
|
4130
|
-
const isPreloadValid =
|
|
4516
|
+
const isPreloadValid = useCallback9(
|
|
4131
4517
|
(placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
|
|
4132
4518
|
[]
|
|
4133
4519
|
);
|
|
4134
|
-
const consumePreloadedCampaign =
|
|
4135
|
-
(placement) =>
|
|
4520
|
+
const consumePreloadedCampaign = useCallback9(
|
|
4521
|
+
(placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
|
|
4136
4522
|
[]
|
|
4137
4523
|
);
|
|
4138
|
-
const preloadCampaign =
|
|
4524
|
+
const preloadCampaign = useCallback9(
|
|
4139
4525
|
async (placement, context) => {
|
|
4140
4526
|
try {
|
|
4141
4527
|
const campaign = await PaywalloClient.getCampaign(placement, context);
|
|
@@ -4180,6 +4566,7 @@ function useCampaignPreload() {
|
|
|
4180
4566
|
campaignId: campaign.campaignId,
|
|
4181
4567
|
variantKey: campaign.variantKey
|
|
4182
4568
|
});
|
|
4569
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4183
4570
|
return { success: true };
|
|
4184
4571
|
} catch (error) {
|
|
4185
4572
|
return {
|
|
@@ -4201,11 +4588,11 @@ function useCampaignPreload() {
|
|
|
4201
4588
|
}
|
|
4202
4589
|
|
|
4203
4590
|
// src/hooks/useProductLoader.ts
|
|
4204
|
-
import { useCallback as
|
|
4591
|
+
import { useCallback as useCallback10, useState as useState10 } from "react";
|
|
4205
4592
|
function useProductLoader() {
|
|
4206
|
-
const [products, setProducts] =
|
|
4207
|
-
const [isLoadingProducts, setIsLoadingProducts] =
|
|
4208
|
-
const refreshProducts =
|
|
4593
|
+
const [products, setProducts] = useState10(/* @__PURE__ */ new Map());
|
|
4594
|
+
const [isLoadingProducts, setIsLoadingProducts] = useState10(false);
|
|
4595
|
+
const refreshProducts = useCallback10(async (productIds) => {
|
|
4209
4596
|
setIsLoadingProducts(true);
|
|
4210
4597
|
try {
|
|
4211
4598
|
const iapService = getIAPService();
|
|
@@ -4214,7 +4601,6 @@ function useProductLoader() {
|
|
|
4214
4601
|
for (const p of loaded) map.set(p.productId, p);
|
|
4215
4602
|
setProducts(map);
|
|
4216
4603
|
} catch (error) {
|
|
4217
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
|
|
4218
4604
|
} finally {
|
|
4219
4605
|
setIsLoadingProducts(false);
|
|
4220
4606
|
}
|
|
@@ -4223,10 +4609,10 @@ function useProductLoader() {
|
|
|
4223
4609
|
}
|
|
4224
4610
|
|
|
4225
4611
|
// src/hooks/usePurchaseOrchestrator.ts
|
|
4226
|
-
import { useCallback as
|
|
4227
|
-
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
|
|
4612
|
+
import { useCallback as useCallback11 } from "react";
|
|
4613
|
+
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
|
|
4228
4614
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
4229
|
-
const handlePreloadedPurchase =
|
|
4615
|
+
const handlePreloadedPurchase = useCallback11(
|
|
4230
4616
|
async (productId) => {
|
|
4231
4617
|
try {
|
|
4232
4618
|
if (preloadedWebView) {
|
|
@@ -4249,19 +4635,19 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4249
4635
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4250
4636
|
await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4251
4637
|
}
|
|
4638
|
+
invalidateSubscriptionCache();
|
|
4252
4639
|
setShowingPreloadedWebView(false);
|
|
4253
4640
|
clearPreloadedWebView();
|
|
4254
4641
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4255
4642
|
} catch (error) {
|
|
4256
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
|
|
4257
4643
|
setShowingPreloadedWebView(false);
|
|
4258
4644
|
clearPreloadedWebView();
|
|
4259
4645
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4260
4646
|
}
|
|
4261
4647
|
},
|
|
4262
|
-
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
|
|
4648
|
+
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
|
|
4263
4649
|
);
|
|
4264
|
-
const handlePreloadedRestore =
|
|
4650
|
+
const handlePreloadedRestore = useCallback11(async () => {
|
|
4265
4651
|
try {
|
|
4266
4652
|
const transactions = await getIAPService().restore();
|
|
4267
4653
|
if (transactions.length === 0) return;
|
|
@@ -4269,30 +4655,72 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4269
4655
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
4270
4656
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4271
4657
|
}
|
|
4658
|
+
invalidateSubscriptionCache();
|
|
4272
4659
|
setShowingPreloadedWebView(false);
|
|
4273
4660
|
clearPreloadedWebView();
|
|
4274
4661
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4275
4662
|
} catch (error) {
|
|
4276
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
|
|
4277
4663
|
setShowingPreloadedWebView(false);
|
|
4278
4664
|
clearPreloadedWebView();
|
|
4279
4665
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4280
4666
|
}
|
|
4281
|
-
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
|
|
4667
|
+
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
|
|
4282
4668
|
return { handlePreloadedPurchase, handlePreloadedRestore };
|
|
4283
4669
|
}
|
|
4284
4670
|
|
|
4285
4671
|
// src/hooks/useSubscriptionSync.ts
|
|
4286
|
-
import { useCallback as
|
|
4672
|
+
import { useCallback as useCallback12, useRef as useRef7, useState as useState11 } from "react";
|
|
4287
4673
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4288
|
-
var ACTIVE_STATUSES = ["active", "
|
|
4674
|
+
var ACTIVE_STATUSES = ["active", "grace_period"];
|
|
4675
|
+
function toDomainSubscriptionInfo(sub) {
|
|
4676
|
+
return {
|
|
4677
|
+
id: "",
|
|
4678
|
+
productId: sub.productId,
|
|
4679
|
+
status: sub.status,
|
|
4680
|
+
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4681
|
+
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4682
|
+
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4683
|
+
willRenew: sub.autoRenewEnabled,
|
|
4684
|
+
isTrial: false,
|
|
4685
|
+
isIntroductoryPeriod: false,
|
|
4686
|
+
platform: sub.platform
|
|
4687
|
+
};
|
|
4688
|
+
}
|
|
4689
|
+
function toDomainResponse(status) {
|
|
4690
|
+
return {
|
|
4691
|
+
hasActiveSubscription: status.hasActiveSubscription,
|
|
4692
|
+
subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
|
|
4693
|
+
entitlements: []
|
|
4694
|
+
};
|
|
4695
|
+
}
|
|
4696
|
+
function isSubscriptionStillActive(sub) {
|
|
4697
|
+
if (!sub) return false;
|
|
4698
|
+
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4699
|
+
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4700
|
+
return true;
|
|
4701
|
+
}
|
|
4702
|
+
async function resolveOfflineFromCache(distinctId) {
|
|
4703
|
+
try {
|
|
4704
|
+
const cached = await subscriptionCache.get(distinctId);
|
|
4705
|
+
if (!cached) return false;
|
|
4706
|
+
const sub = cached.data.subscription;
|
|
4707
|
+
return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
|
|
4708
|
+
} catch {
|
|
4709
|
+
return false;
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
function isActiveCacheEntry(entry) {
|
|
4713
|
+
return isSubscriptionStillActive(entry.data);
|
|
4714
|
+
}
|
|
4289
4715
|
function useSubscriptionSync() {
|
|
4290
|
-
const [subscription, setSubscription] =
|
|
4716
|
+
const [subscription, setSubscription] = useState11(null);
|
|
4291
4717
|
const cacheRef = useRef7(null);
|
|
4292
|
-
const invalidateCache =
|
|
4718
|
+
const invalidateCache = useCallback12(() => {
|
|
4293
4719
|
cacheRef.current = null;
|
|
4720
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4721
|
+
if (distinctId) void subscriptionCache.invalidate(distinctId);
|
|
4294
4722
|
}, []);
|
|
4295
|
-
const fetchAndCache =
|
|
4723
|
+
const fetchAndCache = useCallback12(async () => {
|
|
4296
4724
|
const apiClient = PaywalloClient.getApiClient();
|
|
4297
4725
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4298
4726
|
if (!apiClient || !distinctId) return null;
|
|
@@ -4303,18 +4731,37 @@ function useSubscriptionSync() {
|
|
|
4303
4731
|
} : null;
|
|
4304
4732
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4305
4733
|
setSubscription(sub);
|
|
4734
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4306
4735
|
return sub;
|
|
4307
4736
|
}, []);
|
|
4308
|
-
const hasActiveSubscription =
|
|
4737
|
+
const hasActiveSubscription = useCallback12(async () => {
|
|
4309
4738
|
const apiClient = PaywalloClient.getApiClient();
|
|
4310
4739
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4311
4740
|
if (!apiClient || !distinctId) return false;
|
|
4312
4741
|
const cached = cacheRef.current;
|
|
4313
4742
|
if (cached && cached.expiresAt > Date.now()) {
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4743
|
+
return isActiveCacheEntry(cached);
|
|
4744
|
+
}
|
|
4745
|
+
try {
|
|
4746
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4747
|
+
if (persisted && !persisted.isStale) {
|
|
4748
|
+
const persistedSub = persisted.data.subscription;
|
|
4749
|
+
const sub = persistedSub ? {
|
|
4750
|
+
productId: persistedSub.productId,
|
|
4751
|
+
status: persistedSub.status,
|
|
4752
|
+
expiresAt: persistedSub.expiresAt ?? null,
|
|
4753
|
+
platform: persistedSub.platform,
|
|
4754
|
+
autoRenewEnabled: persistedSub.willRenew,
|
|
4755
|
+
inGracePeriod: persistedSub.status === "grace_period"
|
|
4756
|
+
} : null;
|
|
4757
|
+
const stillActive = isSubscriptionStillActive(sub);
|
|
4758
|
+
if (stillActive) {
|
|
4759
|
+
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4760
|
+
setSubscription(sub);
|
|
4761
|
+
return true;
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
} catch {
|
|
4318
4765
|
}
|
|
4319
4766
|
try {
|
|
4320
4767
|
const status = await apiClient.getSubscriptionStatus(distinctId);
|
|
@@ -4324,12 +4771,13 @@ function useSubscriptionSync() {
|
|
|
4324
4771
|
} : null;
|
|
4325
4772
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4326
4773
|
setSubscription(sub);
|
|
4774
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4327
4775
|
return status.hasActiveSubscription;
|
|
4328
4776
|
} catch {
|
|
4329
|
-
return
|
|
4777
|
+
return await resolveOfflineFromCache(distinctId);
|
|
4330
4778
|
}
|
|
4331
4779
|
}, []);
|
|
4332
|
-
const getSubscription =
|
|
4780
|
+
const getSubscription = useCallback12(async () => {
|
|
4333
4781
|
const apiClient = PaywalloClient.getApiClient();
|
|
4334
4782
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4335
4783
|
if (!apiClient || !distinctId) return null;
|
|
@@ -4338,7 +4786,22 @@ function useSubscriptionSync() {
|
|
|
4338
4786
|
try {
|
|
4339
4787
|
return await fetchAndCache();
|
|
4340
4788
|
} catch {
|
|
4341
|
-
|
|
4789
|
+
try {
|
|
4790
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4791
|
+
if (!persisted?.data.subscription) return null;
|
|
4792
|
+
const s = persisted.data.subscription;
|
|
4793
|
+
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
4794
|
+
return {
|
|
4795
|
+
productId: s.productId,
|
|
4796
|
+
status: normalisedStatus,
|
|
4797
|
+
expiresAt: s.expiresAt ?? null,
|
|
4798
|
+
platform: s.platform,
|
|
4799
|
+
autoRenewEnabled: s.willRenew,
|
|
4800
|
+
inGracePeriod: normalisedStatus === "in_grace_period"
|
|
4801
|
+
};
|
|
4802
|
+
} catch {
|
|
4803
|
+
return null;
|
|
4804
|
+
}
|
|
4342
4805
|
}
|
|
4343
4806
|
}, [fetchAndCache]);
|
|
4344
4807
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|
|
@@ -4347,24 +4810,49 @@ function useSubscriptionSync() {
|
|
|
4347
4810
|
// src/PaywalloProvider.tsx
|
|
4348
4811
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4349
4812
|
function PaywalloProvider({ children, config }) {
|
|
4350
|
-
const [isInitialized, setIsInitialized] =
|
|
4351
|
-
const [initError, setInitError] =
|
|
4352
|
-
const [distinctId, setDistinctId] =
|
|
4353
|
-
const [activePaywall, setActivePaywall] =
|
|
4813
|
+
const [isInitialized, setIsInitialized] = useState12(false);
|
|
4814
|
+
const [initError, setInitError] = useState12(null);
|
|
4815
|
+
const [distinctId, setDistinctId] = useState12(null);
|
|
4816
|
+
const [activePaywall, setActivePaywall] = useState12(null);
|
|
4354
4817
|
const { products, isLoadingProducts, refreshProducts } = useProductLoader();
|
|
4355
4818
|
const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
|
|
4356
4819
|
const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
|
|
4357
|
-
const clearPreloadedWebView =
|
|
4358
|
-
const
|
|
4820
|
+
const clearPreloadedWebView = useCallback13(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4821
|
+
const autoPreloadTriggeredRef = useRef8(false);
|
|
4822
|
+
const preloadedWebViewRef = useRef8(preloadedWebView);
|
|
4823
|
+
useEffect7(() => {
|
|
4824
|
+
preloadedWebViewRef.current = preloadedWebView;
|
|
4825
|
+
}, [preloadedWebView]);
|
|
4826
|
+
const triggerRepreload = useCallback13((placement) => {
|
|
4827
|
+
void PaywalloClient.preloadCampaign(placement).catch(() => {
|
|
4828
|
+
});
|
|
4829
|
+
}, []);
|
|
4830
|
+
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
4831
|
+
const SCREEN_HEIGHT3 = useMemo2(() => Dimensions4.get("window").height, []);
|
|
4832
|
+
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4833
|
+
const handlePreloadedWebViewReady = useCallback13(() => {
|
|
4834
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4835
|
+
console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
4836
|
+
}
|
|
4837
|
+
baseHandlePreloadedWebViewReady();
|
|
4838
|
+
}, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
4839
|
+
const handlePreloadedClose = useCallback13(() => {
|
|
4840
|
+
const placement = preloadedWebView?.placement;
|
|
4841
|
+
Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
|
|
4842
|
+
baseHandlePreloadedClose();
|
|
4843
|
+
if (placement) triggerRepreload(placement);
|
|
4844
|
+
});
|
|
4845
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
|
|
4359
4846
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4360
4847
|
preloadedWebView,
|
|
4361
4848
|
resolvePreloadedPaywall,
|
|
4362
4849
|
clearPreloadedWebView,
|
|
4363
4850
|
setShowingPreloadedWebView,
|
|
4364
|
-
presentedAtRef
|
|
4851
|
+
presentedAtRef,
|
|
4852
|
+
invalidateCache
|
|
4365
4853
|
);
|
|
4366
|
-
const [isPreloadPurchasing, setIsPreloadPurchasing] =
|
|
4367
|
-
const handlePreloadedPurchase =
|
|
4854
|
+
const [isPreloadPurchasing, setIsPreloadPurchasing] = useState12(false);
|
|
4855
|
+
const handlePreloadedPurchase = useCallback13(async (productId) => {
|
|
4368
4856
|
setIsPreloadPurchasing(true);
|
|
4369
4857
|
try {
|
|
4370
4858
|
await rawPreloadedPurchase(productId);
|
|
@@ -4374,28 +4862,34 @@ function PaywalloProvider({ children, config }) {
|
|
|
4374
4862
|
}, [rawPreloadedPurchase]);
|
|
4375
4863
|
useEffect7(() => {
|
|
4376
4864
|
initLocalization({ detectDevice: true });
|
|
4377
|
-
PaywalloClient.init(config).then(() => {
|
|
4865
|
+
PaywalloClient.init(config).then(async () => {
|
|
4378
4866
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
4379
4867
|
setIsInitialized(true);
|
|
4868
|
+
if (!autoPreloadTriggeredRef.current) {
|
|
4869
|
+
autoPreloadTriggeredRef.current = true;
|
|
4870
|
+
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
4871
|
+
if (placement) void preloadCampaign(placement).catch(() => {
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4380
4874
|
}).catch((err) => {
|
|
4381
4875
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
4382
4876
|
});
|
|
4383
|
-
}, [config]);
|
|
4384
|
-
const getPaywallConfig =
|
|
4877
|
+
}, [config, preloadCampaign]);
|
|
4878
|
+
const getPaywallConfig = useCallback13(async (p) => {
|
|
4385
4879
|
const api = PaywalloClient.getApiClient();
|
|
4386
4880
|
return api ? api.getPaywall(p) : null;
|
|
4387
4881
|
}, []);
|
|
4388
|
-
const presentPaywall =
|
|
4882
|
+
const presentPaywall = useCallback13((placement) => {
|
|
4389
4883
|
return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
|
|
4390
4884
|
}, []);
|
|
4391
|
-
const trackCampaignImpression =
|
|
4885
|
+
const trackCampaignImpression = useCallback13((placement, variantKey, campaignId) => {
|
|
4392
4886
|
const api = PaywalloClient.getApiClient();
|
|
4393
4887
|
if (!api) return;
|
|
4394
4888
|
const sessionId = PaywalloClient.getSessionId();
|
|
4395
4889
|
void api.trackEvent("$campaign_impression", PaywalloClient.getDistinctId(), { placement, variantKey, campaignId, ...sessionId && { sessionId } }).catch(() => {
|
|
4396
4890
|
});
|
|
4397
4891
|
}, []);
|
|
4398
|
-
const presentCampaign =
|
|
4892
|
+
const presentCampaign = useCallback13(
|
|
4399
4893
|
async (placement, context) => {
|
|
4400
4894
|
try {
|
|
4401
4895
|
if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
|
|
@@ -4464,7 +4958,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4464
4958
|
},
|
|
4465
4959
|
[preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
|
|
4466
4960
|
);
|
|
4467
|
-
const restorePurchases =
|
|
4961
|
+
const restorePurchases = useCallback13(async () => {
|
|
4468
4962
|
invalidateCache();
|
|
4469
4963
|
try {
|
|
4470
4964
|
const txs = await getIAPService().restore();
|
|
@@ -4473,34 +4967,60 @@ function PaywalloProvider({ children, config }) {
|
|
|
4473
4967
|
return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
|
|
4474
4968
|
}
|
|
4475
4969
|
}, [invalidateCache]);
|
|
4476
|
-
const handlePaywallResult =
|
|
4970
|
+
const handlePaywallResult = useCallback13((result) => {
|
|
4477
4971
|
if (activePaywall) {
|
|
4478
4972
|
activePaywall.resolver(result);
|
|
4479
4973
|
setActivePaywall(null);
|
|
4480
4974
|
}
|
|
4481
4975
|
}, [activePaywall]);
|
|
4976
|
+
const bridgePaywallPresenter = useCallback13((placement) => {
|
|
4977
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4978
|
+
console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
4979
|
+
}
|
|
4980
|
+
if (preloadedWebViewRef.current?.placement === placement) {
|
|
4981
|
+
if (preloadedWebViewRef.current.loaded) {
|
|
4982
|
+
return presentPreloadedWebView();
|
|
4983
|
+
}
|
|
4984
|
+
return new Promise((resolve) => {
|
|
4985
|
+
let elapsed = 0;
|
|
4986
|
+
const poll = () => {
|
|
4987
|
+
if (preloadedWebViewRef.current?.loaded) {
|
|
4988
|
+
resolve(presentPreloadedWebView());
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
elapsed += 100;
|
|
4992
|
+
if (elapsed >= 500) {
|
|
4993
|
+
resolve(presentCampaign(placement));
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
setTimeout(poll, 100);
|
|
4997
|
+
};
|
|
4998
|
+
setTimeout(poll, 100);
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
return presentCampaign(placement);
|
|
5002
|
+
}, [presentPreloadedWebView, presentCampaign]);
|
|
4482
5003
|
useEffect7(() => {
|
|
4483
5004
|
if (!isInitialized) return;
|
|
4484
5005
|
const onEmergency = async (id) => {
|
|
4485
5006
|
try {
|
|
4486
|
-
await
|
|
5007
|
+
await bridgePaywallPresenter(id);
|
|
4487
5008
|
} catch {
|
|
4488
5009
|
}
|
|
4489
5010
|
};
|
|
4490
|
-
PaywalloClient.registerPaywallPresenter(
|
|
5011
|
+
PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
|
|
4491
5012
|
PaywalloClient.registerSubscriptionGetter(getSubscription);
|
|
4492
5013
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
4493
5014
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
4494
5015
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
4495
|
-
}, [isInitialized,
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
}, [activePaywall, setPreloadedWebView]);
|
|
4499
|
-
const noOp = useCallback12(() => {
|
|
5016
|
+
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5017
|
+
const isReady = isInitialized && !isLoadingProducts;
|
|
5018
|
+
const noOp = useCallback13(() => {
|
|
4500
5019
|
}, []);
|
|
4501
5020
|
const contextValue = useMemo2(() => ({
|
|
4502
5021
|
config: PaywalloClient.getConfig(),
|
|
4503
5022
|
isInitialized,
|
|
5023
|
+
isReady,
|
|
4504
5024
|
initError,
|
|
4505
5025
|
distinctId,
|
|
4506
5026
|
subscription,
|
|
@@ -4516,6 +5036,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4516
5036
|
refreshProducts
|
|
4517
5037
|
}), [
|
|
4518
5038
|
isInitialized,
|
|
5039
|
+
isReady,
|
|
4519
5040
|
initError,
|
|
4520
5041
|
distinctId,
|
|
4521
5042
|
subscription,
|
|
@@ -4530,20 +5051,16 @@ function PaywalloProvider({ children, config }) {
|
|
|
4530
5051
|
getPaywallConfig,
|
|
4531
5052
|
refreshProducts
|
|
4532
5053
|
]);
|
|
4533
|
-
|
|
4534
|
-
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4535
|
-
useEffect7(() => {
|
|
5054
|
+
useLayoutEffect(() => {
|
|
4536
5055
|
if (showingPreloadedWebView) {
|
|
4537
5056
|
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
4538
|
-
Animated3.timing(slideAnim, { toValue: 0, duration:
|
|
4539
|
-
} else {
|
|
4540
|
-
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
5057
|
+
Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
|
|
4541
5058
|
}
|
|
4542
5059
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
|
|
4543
5060
|
const preloadStyle = [
|
|
4544
5061
|
styles3.preloadOverlay,
|
|
4545
5062
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
4546
|
-
{ transform: [{ translateY:
|
|
5063
|
+
showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
|
|
4547
5064
|
];
|
|
4548
5065
|
return /* @__PURE__ */ jsxs2(PaywalloContext.Provider, { value: contextValue, children: [
|
|
4549
5066
|
children,
|
|
@@ -4556,6 +5073,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4556
5073
|
primaryProductId: preloadedWebView.primaryProductId,
|
|
4557
5074
|
secondaryProductId: preloadedWebView.secondaryProductId,
|
|
4558
5075
|
isPurchasing: isPreloadPurchasing,
|
|
5076
|
+
webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
|
|
4559
5077
|
onReady: handlePreloadedWebViewReady,
|
|
4560
5078
|
onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
|
|
4561
5079
|
onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
|
|
@@ -4578,8 +5096,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4578
5096
|
}
|
|
4579
5097
|
var styles3 = StyleSheet3.create({
|
|
4580
5098
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
4581
|
-
hidden: { zIndex: -1 },
|
|
4582
|
-
visible: { zIndex: 9999 }
|
|
5099
|
+
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5100
|
+
visible: { zIndex: 9999, opacity: 1 }
|
|
4583
5101
|
});
|
|
4584
5102
|
|
|
4585
5103
|
// src/utils/productVariableResolver.ts
|