@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.js
CHANGED
|
@@ -130,7 +130,7 @@ var CLIENT_ERROR_CODES = {
|
|
|
130
130
|
|
|
131
131
|
// src/domains/paywall/PaywallModal.tsx
|
|
132
132
|
var import_react5 = require("react");
|
|
133
|
-
var
|
|
133
|
+
var import_react_native6 = require("react-native");
|
|
134
134
|
|
|
135
135
|
// src/domains/paywall/usePaywallActions.ts
|
|
136
136
|
var import_react2 = require("react");
|
|
@@ -227,9 +227,6 @@ var SecureStorage = class {
|
|
|
227
227
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
228
228
|
constructor(_debug = false) {
|
|
229
229
|
}
|
|
230
|
-
/**
|
|
231
|
-
* Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
|
|
232
|
-
*/
|
|
233
230
|
async get(key) {
|
|
234
231
|
const keychainValue = await this.getFromKeychain(key);
|
|
235
232
|
if (keychainValue) {
|
|
@@ -245,9 +242,6 @@ var SecureStorage = class {
|
|
|
245
242
|
return null;
|
|
246
243
|
}
|
|
247
244
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Set a value in both Keychain (if available) and AsyncStorage.
|
|
250
|
-
*/
|
|
251
245
|
async set(key, value) {
|
|
252
246
|
try {
|
|
253
247
|
await Promise.all([
|
|
@@ -311,11 +305,13 @@ var secureStorage = new SecureStorage();
|
|
|
311
305
|
|
|
312
306
|
// src/domains/identity/IdentityManager.ts
|
|
313
307
|
var DEVICE_ID_KEY = "device_id";
|
|
308
|
+
var ANON_ID_KEY = "anon_id";
|
|
314
309
|
var USER_EMAIL_KEY = "user_email";
|
|
315
310
|
var USER_PROPERTIES_KEY = "user_properties";
|
|
316
311
|
var IdentityManager = class {
|
|
317
312
|
constructor() {
|
|
318
313
|
this.deviceId = null;
|
|
314
|
+
this.anonId = null;
|
|
319
315
|
this.email = null;
|
|
320
316
|
this.properties = {};
|
|
321
317
|
this.apiClient = null;
|
|
@@ -342,23 +338,23 @@ var IdentityManager = class {
|
|
|
342
338
|
try {
|
|
343
339
|
if (DeviceInfo) {
|
|
344
340
|
this.deviceId = await DeviceInfo.getUniqueId();
|
|
345
|
-
this.log("Got device ID from DeviceInfo:", this.deviceId);
|
|
346
341
|
} else {
|
|
347
342
|
this.deviceId = generateUUID();
|
|
348
|
-
this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
|
|
349
343
|
}
|
|
350
344
|
} catch {
|
|
351
345
|
this.deviceId = generateUUID();
|
|
352
|
-
this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
|
|
353
346
|
}
|
|
354
347
|
const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
|
|
355
348
|
if (!stored) {
|
|
356
|
-
this.log("Failed to persist device ID to secure storage, using fallback");
|
|
357
349
|
await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
|
|
358
|
-
this.log("Fallback also failed \u2014 device ID only in memory");
|
|
359
350
|
});
|
|
360
351
|
}
|
|
361
352
|
}
|
|
353
|
+
if (!this.anonId) {
|
|
354
|
+
this.anonId = "$paywallo_anon:" + generateUUID();
|
|
355
|
+
await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
|
|
356
|
+
});
|
|
357
|
+
}
|
|
362
358
|
this.initialized = true;
|
|
363
359
|
return this.deviceId ?? "";
|
|
364
360
|
}
|
|
@@ -382,21 +378,18 @@ var IdentityManager = class {
|
|
|
382
378
|
this.properties = { ...this.properties, ...properties };
|
|
383
379
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
384
380
|
}
|
|
385
|
-
if (this.apiClient && this.
|
|
381
|
+
if (this.apiClient && this.anonId) {
|
|
386
382
|
try {
|
|
387
|
-
await this.apiClient.identify(this.
|
|
388
|
-
|
|
389
|
-
} catch (error) {
|
|
390
|
-
this.log("Failed to update identity on server:", error);
|
|
383
|
+
await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
|
|
384
|
+
} catch {
|
|
391
385
|
}
|
|
392
386
|
}
|
|
393
387
|
}
|
|
394
388
|
getDistinctId() {
|
|
395
|
-
if (!this.initialized || !this.
|
|
396
|
-
if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
|
|
389
|
+
if (!this.initialized || !this.anonId) {
|
|
397
390
|
return "";
|
|
398
391
|
}
|
|
399
|
-
return this.
|
|
392
|
+
return this.anonId;
|
|
400
393
|
}
|
|
401
394
|
getDeviceId() {
|
|
402
395
|
return this.deviceId;
|
|
@@ -411,24 +404,22 @@ var IdentityManager = class {
|
|
|
411
404
|
this.ensureInitialized();
|
|
412
405
|
this.properties = { ...this.properties, ...properties };
|
|
413
406
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
414
|
-
if (this.apiClient && this.
|
|
407
|
+
if (this.apiClient && this.anonId) {
|
|
415
408
|
try {
|
|
416
|
-
await this.apiClient.identify(this.
|
|
417
|
-
} catch
|
|
418
|
-
this.log("Failed to update properties on server:", error);
|
|
409
|
+
await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
|
|
410
|
+
} catch {
|
|
419
411
|
}
|
|
420
412
|
}
|
|
421
413
|
}
|
|
422
414
|
async reset() {
|
|
415
|
+
const newAnonId = "$paywallo_anon:" + generateUUID();
|
|
416
|
+
this.anonId = newAnonId;
|
|
423
417
|
this.email = null;
|
|
424
418
|
this.properties = {};
|
|
425
|
-
this.initialized = false;
|
|
426
|
-
this.apiClient = null;
|
|
427
|
-
this.deviceId = null;
|
|
428
419
|
await Promise.all([
|
|
420
|
+
this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
|
|
429
421
|
this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
|
|
430
|
-
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
431
|
-
this.secureStorage?.remove("device_id_fallback") ?? Promise.resolve(false)
|
|
422
|
+
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
432
423
|
]);
|
|
433
424
|
}
|
|
434
425
|
getState() {
|
|
@@ -445,21 +436,23 @@ var IdentityManager = class {
|
|
|
445
436
|
if (!this.secureStorage) {
|
|
446
437
|
return;
|
|
447
438
|
}
|
|
448
|
-
const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
439
|
+
const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
449
440
|
this.secureStorage.get(DEVICE_ID_KEY),
|
|
441
|
+
this.secureStorage.get(ANON_ID_KEY),
|
|
450
442
|
this.secureStorage.get(USER_EMAIL_KEY),
|
|
451
443
|
this.secureStorage.get(USER_PROPERTIES_KEY)
|
|
452
444
|
]);
|
|
453
445
|
if (storedDeviceId) {
|
|
454
446
|
this.deviceId = storedDeviceId;
|
|
455
|
-
this.log("Loaded device ID from storage:", this.deviceId);
|
|
456
447
|
} else {
|
|
457
448
|
const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
|
|
458
449
|
if (fallbackDeviceId) {
|
|
459
450
|
this.deviceId = fallbackDeviceId;
|
|
460
|
-
this.log("Loaded device ID from fallback storage:", this.deviceId);
|
|
461
451
|
}
|
|
462
452
|
}
|
|
453
|
+
if (storedAnonId) {
|
|
454
|
+
this.anonId = storedAnonId;
|
|
455
|
+
}
|
|
463
456
|
if (storedEmail) {
|
|
464
457
|
this.email = storedEmail;
|
|
465
458
|
}
|
|
@@ -479,11 +472,6 @@ var IdentityManager = class {
|
|
|
479
472
|
);
|
|
480
473
|
}
|
|
481
474
|
}
|
|
482
|
-
log(...args) {
|
|
483
|
-
if (this.debug) {
|
|
484
|
-
console.warn("[Paywallo Identity]", ...args);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
475
|
};
|
|
488
476
|
var identityManager = new IdentityManager();
|
|
489
477
|
|
|
@@ -568,7 +556,6 @@ var nativeStoreKit = {
|
|
|
568
556
|
isAvailable,
|
|
569
557
|
async getProducts(productIds) {
|
|
570
558
|
if (!PaywalloStoreKitNative) {
|
|
571
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
|
|
572
559
|
return [];
|
|
573
560
|
}
|
|
574
561
|
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
@@ -639,7 +626,6 @@ var IAPService = class {
|
|
|
639
626
|
}
|
|
640
627
|
return products;
|
|
641
628
|
} catch (error) {
|
|
642
|
-
if (this.debug) console.warn("[Paywallo][Products] StoreKit error:", error);
|
|
643
629
|
return [];
|
|
644
630
|
}
|
|
645
631
|
}
|
|
@@ -649,35 +635,46 @@ var IAPService = class {
|
|
|
649
635
|
getCachedProducts() {
|
|
650
636
|
return new Map(this.productsCache);
|
|
651
637
|
}
|
|
638
|
+
async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
|
|
639
|
+
let lastError;
|
|
640
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
641
|
+
try {
|
|
642
|
+
return await attempt();
|
|
643
|
+
} catch (error) {
|
|
644
|
+
lastError = error;
|
|
645
|
+
if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
throw lastError;
|
|
649
|
+
}
|
|
652
650
|
async validateWithServer(purchase, productId, options) {
|
|
653
651
|
const apiClient = this.getApiClient();
|
|
654
652
|
const product = this.productsCache.get(productId);
|
|
655
653
|
const platform = import_react_native2.Platform.OS;
|
|
656
654
|
const distinctId = PaywalloClient.getDistinctId();
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
655
|
+
return this.validateWithRetry(
|
|
656
|
+
() => apiClient.validatePurchase(
|
|
657
|
+
platform,
|
|
658
|
+
purchase.receipt,
|
|
659
|
+
purchase.productId,
|
|
660
|
+
purchase.transactionId,
|
|
661
|
+
product?.priceValue ?? 0,
|
|
662
|
+
product?.currency ?? "USD",
|
|
663
|
+
this.getDeviceCountry(),
|
|
664
|
+
distinctId,
|
|
665
|
+
options?.paywallPlacement,
|
|
666
|
+
options?.variantKey
|
|
667
|
+
)
|
|
668
668
|
);
|
|
669
|
-
return validation;
|
|
670
669
|
}
|
|
671
670
|
async finishTransaction(transactionId) {
|
|
672
671
|
try {
|
|
673
672
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
674
673
|
} catch (finishError) {
|
|
675
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Failed to finish transaction:", finishError instanceof Error ? finishError.message : finishError);
|
|
676
674
|
}
|
|
677
675
|
}
|
|
678
676
|
async purchase(productId, options) {
|
|
679
677
|
if (!nativeStoreKit.isAvailable()) {
|
|
680
|
-
if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
|
|
681
678
|
return {
|
|
682
679
|
success: false,
|
|
683
680
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
@@ -688,11 +685,11 @@ var IAPService = class {
|
|
|
688
685
|
if (!purchase) {
|
|
689
686
|
return { success: false };
|
|
690
687
|
}
|
|
688
|
+
options?.onValidating?.();
|
|
691
689
|
let validation = null;
|
|
692
690
|
try {
|
|
693
691
|
validation = await this.validateWithServer(purchase, productId, options);
|
|
694
692
|
} catch (validationError) {
|
|
695
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
|
|
696
693
|
return {
|
|
697
694
|
success: false,
|
|
698
695
|
error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
|
|
@@ -708,7 +705,6 @@ var IAPService = class {
|
|
|
708
705
|
return { success: true, purchase };
|
|
709
706
|
} catch (error) {
|
|
710
707
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
711
|
-
if (this.debug) console.warn("[Paywallo][Purchase] FAILED:", e.message);
|
|
712
708
|
return { success: false, error: e };
|
|
713
709
|
}
|
|
714
710
|
}
|
|
@@ -744,14 +740,12 @@ var IAPService = class {
|
|
|
744
740
|
try {
|
|
745
741
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
746
742
|
} catch (finishError) {
|
|
747
|
-
if (this.debug) console.warn("[Paywallo][Restore] Failed to finish tx:", tx.transactionId, finishError instanceof Error ? finishError.message : finishError);
|
|
748
743
|
}
|
|
749
744
|
validated.push(tx);
|
|
750
745
|
}
|
|
751
746
|
}
|
|
752
747
|
return validated;
|
|
753
748
|
} catch (error) {
|
|
754
|
-
if (this.debug) console.warn("[Paywallo][Restore] \u274C FAILED:", error instanceof Error ? error.message : error);
|
|
755
749
|
return [];
|
|
756
750
|
}
|
|
757
751
|
}
|
|
@@ -819,7 +813,7 @@ function usePaywallTracking() {
|
|
|
819
813
|
|
|
820
814
|
// src/domains/paywall/usePaywallActions.ts
|
|
821
815
|
var SCREEN_HEIGHT = import_react_native3.Dimensions.get("window").height;
|
|
822
|
-
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
|
|
816
|
+
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
823
817
|
const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
|
|
824
818
|
const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
|
|
825
819
|
const slideAnim = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
|
|
@@ -828,7 +822,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
828
822
|
const handleClose = (0, import_react2.useCallback)(() => {
|
|
829
823
|
if (isClosing) return;
|
|
830
824
|
setIsClosing(true);
|
|
831
|
-
|
|
825
|
+
const animTarget = openAnim ?? slideAnim;
|
|
826
|
+
import_react_native3.Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
|
|
832
827
|
if (paywallConfig) {
|
|
833
828
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
834
829
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -837,7 +832,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
837
832
|
setIsClosing(false);
|
|
838
833
|
onResult({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
839
834
|
});
|
|
840
|
-
}, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
835
|
+
}, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
841
836
|
const handlePurchase = (0, import_react2.useCallback)(async (productId) => {
|
|
842
837
|
if (isPurchasing) return;
|
|
843
838
|
setIsPurchasing(true);
|
|
@@ -855,7 +850,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
855
850
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
856
851
|
}
|
|
857
852
|
} catch (error) {
|
|
858
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
|
|
859
853
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
860
854
|
} finally {
|
|
861
855
|
setIsPurchasing(false);
|
|
@@ -876,7 +870,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
876
870
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
877
871
|
}
|
|
878
872
|
} catch (error) {
|
|
879
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
|
|
880
873
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
881
874
|
} finally {
|
|
882
875
|
setIsPurchasing(false);
|
|
@@ -951,11 +944,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
951
944
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
952
945
|
const map = /* @__PURE__ */ new Map();
|
|
953
946
|
for (const p of storeProducts) map.set(p.productId, p);
|
|
954
|
-
if (PaywalloClient.getConfig()?.debug) {
|
|
955
|
-
for (const [id, p] of map) {
|
|
956
|
-
console.warn(`[Paywallo][CURRENCY-CHECK] product=${id} currency=${p.currency} price="${p.price}" localizedPrice="${p.localizedPrice}" priceValue=${p.priceValue} fallbackUSD=${!p.currency || p.currency === "USD" ? "POSSIBLE" : "NO"}`);
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
947
|
setProducts(map);
|
|
960
948
|
} catch {
|
|
961
949
|
setProducts(/* @__PURE__ */ new Map());
|
|
@@ -976,7 +964,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
976
964
|
}
|
|
977
965
|
} catch (err) {
|
|
978
966
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
979
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
|
|
980
967
|
setError(e);
|
|
981
968
|
}
|
|
982
969
|
};
|
|
@@ -985,11 +972,96 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
985
972
|
return { paywallConfig, products, error };
|
|
986
973
|
}
|
|
987
974
|
|
|
975
|
+
// src/utils/localization.ts
|
|
976
|
+
var import_react_native4 = require("react-native");
|
|
977
|
+
var currentLanguage = "pt-BR";
|
|
978
|
+
var defaultLanguage = "pt-BR";
|
|
979
|
+
function setCurrentLanguage(language) {
|
|
980
|
+
currentLanguage = language;
|
|
981
|
+
}
|
|
982
|
+
function setDefaultLanguage(language) {
|
|
983
|
+
defaultLanguage = language;
|
|
984
|
+
}
|
|
985
|
+
function getCurrentLanguage() {
|
|
986
|
+
return currentLanguage;
|
|
987
|
+
}
|
|
988
|
+
function getDefaultLanguage() {
|
|
989
|
+
return defaultLanguage;
|
|
990
|
+
}
|
|
991
|
+
function getLocalizedString(text) {
|
|
992
|
+
if (!text) return "";
|
|
993
|
+
if (typeof text === "string") return text;
|
|
994
|
+
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
995
|
+
}
|
|
996
|
+
function detectDeviceLanguage() {
|
|
997
|
+
try {
|
|
998
|
+
if (import_react_native4.Platform.OS === "ios") {
|
|
999
|
+
const settings = import_react_native4.NativeModules.SettingsManager?.settings;
|
|
1000
|
+
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
1001
|
+
}
|
|
1002
|
+
return import_react_native4.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
|
|
1003
|
+
} catch {
|
|
1004
|
+
return "en-US";
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
var SDK_STRINGS = {
|
|
1008
|
+
paywallErrorTitle: {
|
|
1009
|
+
"pt-BR": "Algo deu errado",
|
|
1010
|
+
"pt": "Algo deu errado",
|
|
1011
|
+
"en": "Something went wrong",
|
|
1012
|
+
"en-US": "Something went wrong",
|
|
1013
|
+
"en-GB": "Something went wrong",
|
|
1014
|
+
"es": "Algo sali\xF3 mal",
|
|
1015
|
+
"es-419": "Algo sali\xF3 mal"
|
|
1016
|
+
},
|
|
1017
|
+
paywallRetryButton: {
|
|
1018
|
+
"pt-BR": "Tentar novamente",
|
|
1019
|
+
"pt": "Tentar novamente",
|
|
1020
|
+
"en": "Try again",
|
|
1021
|
+
"en-US": "Try again",
|
|
1022
|
+
"en-GB": "Try again",
|
|
1023
|
+
"es": "Reintentar",
|
|
1024
|
+
"es-419": "Reintentar"
|
|
1025
|
+
},
|
|
1026
|
+
paywallCloseButton: {
|
|
1027
|
+
"pt-BR": "Fechar",
|
|
1028
|
+
"pt": "Fechar",
|
|
1029
|
+
"en": "Close",
|
|
1030
|
+
"en-US": "Close",
|
|
1031
|
+
"en-GB": "Close",
|
|
1032
|
+
"es": "Cerrar",
|
|
1033
|
+
"es-419": "Cerrar"
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
function getSdkString(key) {
|
|
1037
|
+
const map = SDK_STRINGS[key];
|
|
1038
|
+
const lang = currentLanguage;
|
|
1039
|
+
if (lang in map) return map[lang];
|
|
1040
|
+
const base = lang.split("-")[0];
|
|
1041
|
+
if (base && base in map) return map[base];
|
|
1042
|
+
if (defaultLanguage in map) return map[defaultLanguage];
|
|
1043
|
+
return Object.values(map)[0];
|
|
1044
|
+
}
|
|
1045
|
+
function initLocalization(options) {
|
|
1046
|
+
if (options?.defaultLanguage) {
|
|
1047
|
+
defaultLanguage = options.defaultLanguage;
|
|
1048
|
+
}
|
|
1049
|
+
if (options?.detectDevice !== false) {
|
|
1050
|
+
const deviceLanguage = detectDeviceLanguage();
|
|
1051
|
+
currentLanguage = deviceLanguage;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
988
1055
|
// src/domains/paywall/PaywallWebView.tsx
|
|
989
1056
|
var import_react4 = require("react");
|
|
990
|
-
var
|
|
1057
|
+
var import_react_native5 = require("react-native");
|
|
991
1058
|
|
|
992
1059
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
1060
|
+
function deriveMessageId(message) {
|
|
1061
|
+
if (message.id) return message.id;
|
|
1062
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
1063
|
+
return `${message.type}:${payloadKey}`;
|
|
1064
|
+
}
|
|
993
1065
|
function parseWebViewMessage(raw) {
|
|
994
1066
|
try {
|
|
995
1067
|
const parsed = JSON.parse(raw);
|
|
@@ -1013,7 +1085,7 @@ function buildPaywallInjectionScript(data) {
|
|
|
1013
1085
|
secondaryProductId: data.secondaryProductId
|
|
1014
1086
|
}
|
|
1015
1087
|
};
|
|
1016
|
-
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);}
|
|
1088
|
+
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;`;
|
|
1017
1089
|
}
|
|
1018
1090
|
function buildPurchaseStateScript(isPurchasing) {
|
|
1019
1091
|
return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
|
|
@@ -1024,17 +1096,24 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
1024
1096
|
var NativeWebViewComponent = null;
|
|
1025
1097
|
function getNativeWebView() {
|
|
1026
1098
|
if (!NativeWebViewComponent) {
|
|
1027
|
-
NativeWebViewComponent = (0,
|
|
1099
|
+
NativeWebViewComponent = (0, import_react_native5.requireNativeComponent)("PaywalloWebView");
|
|
1028
1100
|
}
|
|
1029
1101
|
return NativeWebViewComponent;
|
|
1030
1102
|
}
|
|
1031
1103
|
var webViewEmitter = null;
|
|
1032
1104
|
function getWebViewEmitter() {
|
|
1033
1105
|
if (!webViewEmitter) {
|
|
1034
|
-
webViewEmitter = new
|
|
1106
|
+
webViewEmitter = new import_react_native5.NativeEventEmitter(import_react_native5.NativeModules.PaywalloWebViewModule);
|
|
1035
1107
|
}
|
|
1036
1108
|
return webViewEmitter;
|
|
1037
1109
|
}
|
|
1110
|
+
function parseErrorEvent(raw) {
|
|
1111
|
+
return {
|
|
1112
|
+
code: typeof raw["code"] === "number" ? raw["code"] : -1,
|
|
1113
|
+
description: typeof raw["description"] === "string" ? raw["description"] : "WebView error",
|
|
1114
|
+
url: typeof raw["url"] === "string" ? raw["url"] : void 0
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1038
1117
|
function PaywallWebView({
|
|
1039
1118
|
paywallId,
|
|
1040
1119
|
craftData,
|
|
@@ -1042,16 +1121,51 @@ function PaywallWebView({
|
|
|
1042
1121
|
primaryProductId,
|
|
1043
1122
|
secondaryProductId,
|
|
1044
1123
|
isPurchasing,
|
|
1124
|
+
webUrl: webUrlProp,
|
|
1045
1125
|
onPurchase,
|
|
1046
1126
|
onClose,
|
|
1047
1127
|
onRestore,
|
|
1048
|
-
onReady
|
|
1128
|
+
onReady,
|
|
1129
|
+
onError
|
|
1049
1130
|
}) {
|
|
1050
1131
|
const paywallDataSent = (0, import_react4.useRef)(false);
|
|
1051
1132
|
const loadEndReceived = (0, import_react4.useRef)(false);
|
|
1052
1133
|
const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
|
|
1134
|
+
const onErrorRef = (0, import_react4.useRef)(onError);
|
|
1135
|
+
onErrorRef.current = onError;
|
|
1136
|
+
const onReadyRef = (0, import_react4.useRef)(onReady);
|
|
1137
|
+
onReadyRef.current = onReady;
|
|
1053
1138
|
const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
|
|
1054
|
-
const
|
|
1139
|
+
const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react4.useState)(
|
|
1140
|
+
() => webUrlProp ?? PaywalloClient.getWebUrl()
|
|
1141
|
+
);
|
|
1142
|
+
(0, import_react4.useEffect)(() => {
|
|
1143
|
+
if (webUrlProp !== void 0) {
|
|
1144
|
+
setResolvedWebUrl(webUrlProp ?? null);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
const url = PaywalloClient.getWebUrl();
|
|
1148
|
+
if (url) {
|
|
1149
|
+
setResolvedWebUrl(url);
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
const interval = setInterval(() => {
|
|
1153
|
+
const u = PaywalloClient.getWebUrl();
|
|
1154
|
+
if (u) {
|
|
1155
|
+
setResolvedWebUrl(u);
|
|
1156
|
+
clearInterval(interval);
|
|
1157
|
+
}
|
|
1158
|
+
}, 200);
|
|
1159
|
+
return () => clearInterval(interval);
|
|
1160
|
+
}, [webUrlProp]);
|
|
1161
|
+
(0, import_react4.useEffect)(() => {
|
|
1162
|
+
if (!resolvedWebUrl) {
|
|
1163
|
+
onErrorRef.current?.({
|
|
1164
|
+
code: -1,
|
|
1165
|
+
description: "Paywall URL not configured. SDK may not be initialized."
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
}, []);
|
|
1055
1169
|
const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
|
|
1056
1170
|
if (!craftData || paywallDataSent.current) return;
|
|
1057
1171
|
paywallDataSent.current = true;
|
|
@@ -1062,19 +1176,21 @@ function PaywallWebView({
|
|
|
1062
1176
|
secondaryProductId
|
|
1063
1177
|
});
|
|
1064
1178
|
setScriptToInject(script);
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
const lastProcessedRef = (0, import_react4.useRef)({ type: "", time: 0 });
|
|
1179
|
+
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1180
|
+
const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
|
|
1068
1181
|
const handleParsedMessage = (0, import_react4.useCallback)(
|
|
1069
1182
|
(message) => {
|
|
1070
|
-
const
|
|
1071
|
-
if (
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
lastProcessedRef.current = { type: message.type, time: now };
|
|
1183
|
+
const msgId = deriveMessageId(message);
|
|
1184
|
+
if (processedIdsRef.current.has(msgId)) return;
|
|
1185
|
+
processedIdsRef.current.add(msgId);
|
|
1186
|
+
setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
|
|
1075
1187
|
switch (message.type) {
|
|
1076
1188
|
case "ready":
|
|
1077
|
-
if (!paywallDataSent.current)
|
|
1189
|
+
if (!paywallDataSent.current) {
|
|
1190
|
+
buildAndSetInjectionScript();
|
|
1191
|
+
} else {
|
|
1192
|
+
onReadyRef.current?.();
|
|
1193
|
+
}
|
|
1078
1194
|
break;
|
|
1079
1195
|
case "purchase":
|
|
1080
1196
|
if (message.payload?.productId) onPurchase(message.payload.productId);
|
|
@@ -1089,7 +1205,7 @@ function PaywallWebView({
|
|
|
1089
1205
|
if (message.payload?.url) {
|
|
1090
1206
|
const url = message.payload.url;
|
|
1091
1207
|
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
|
|
1092
|
-
void
|
|
1208
|
+
void import_react_native5.Linking.openURL(url);
|
|
1093
1209
|
}
|
|
1094
1210
|
}
|
|
1095
1211
|
break;
|
|
@@ -1109,17 +1225,51 @@ function PaywallWebView({
|
|
|
1109
1225
|
const message = parseWebViewMessage(event.data);
|
|
1110
1226
|
if (message) handleParsedMessage(message);
|
|
1111
1227
|
});
|
|
1228
|
+
const errorSub = emitter.addListener(
|
|
1229
|
+
"PaywalloWebViewError",
|
|
1230
|
+
(event) => {
|
|
1231
|
+
onErrorRef.current?.(parseErrorEvent(event));
|
|
1232
|
+
}
|
|
1233
|
+
);
|
|
1112
1234
|
return () => {
|
|
1113
1235
|
loadEndSub.remove();
|
|
1114
1236
|
messageSub.remove();
|
|
1237
|
+
errorSub.remove();
|
|
1115
1238
|
};
|
|
1116
1239
|
}, [buildAndSetInjectionScript, handleParsedMessage]);
|
|
1240
|
+
(0, import_react4.useEffect)(() => {
|
|
1241
|
+
const timer = setTimeout(() => {
|
|
1242
|
+
if (!paywallDataSent.current) {
|
|
1243
|
+
onErrorRef.current?.({
|
|
1244
|
+
code: -1001,
|
|
1245
|
+
description: "Paywall load timed out after 10 seconds"
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
}, 1e4);
|
|
1249
|
+
return () => clearTimeout(timer);
|
|
1250
|
+
}, []);
|
|
1117
1251
|
const handleLoadEnd = (0, import_react4.useCallback)(() => {
|
|
1118
1252
|
if (!loadEndReceived.current) {
|
|
1119
1253
|
loadEndReceived.current = true;
|
|
1120
1254
|
buildAndSetInjectionScript();
|
|
1121
1255
|
}
|
|
1122
1256
|
}, [buildAndSetInjectionScript]);
|
|
1257
|
+
const handleNativeError = (0, import_react4.useCallback)((event) => {
|
|
1258
|
+
const ne = event.nativeEvent;
|
|
1259
|
+
let errorData;
|
|
1260
|
+
if (ne["payload"] && typeof ne["payload"] === "object") {
|
|
1261
|
+
errorData = ne["payload"];
|
|
1262
|
+
} else if (typeof ne["data"] === "string") {
|
|
1263
|
+
try {
|
|
1264
|
+
errorData = JSON.parse(ne["data"]);
|
|
1265
|
+
} catch {
|
|
1266
|
+
errorData = {};
|
|
1267
|
+
}
|
|
1268
|
+
} else {
|
|
1269
|
+
errorData = {};
|
|
1270
|
+
}
|
|
1271
|
+
onErrorRef.current?.(parseErrorEvent(errorData));
|
|
1272
|
+
}, []);
|
|
1123
1273
|
const handleMessage = (0, import_react4.useCallback)(
|
|
1124
1274
|
(event) => {
|
|
1125
1275
|
const message = parseWebViewMessage(event.nativeEvent.data);
|
|
@@ -1131,26 +1281,40 @@ function PaywallWebView({
|
|
|
1131
1281
|
if (!paywallDataSent.current) return;
|
|
1132
1282
|
setScriptToInject(buildPurchaseStateScript(isPurchasing));
|
|
1133
1283
|
}, [isPurchasing]);
|
|
1134
|
-
|
|
1135
|
-
|
|
1284
|
+
if (!resolvedWebUrl) return null;
|
|
1285
|
+
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native5.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1136
1287
|
NativeWebView,
|
|
1137
1288
|
{
|
|
1138
1289
|
sourceUrl: paywallUrl,
|
|
1139
1290
|
injectedJavaScript: scriptToInject,
|
|
1140
1291
|
onMessage: handleMessage,
|
|
1141
1292
|
onLoadEnd: handleLoadEnd,
|
|
1293
|
+
onError: handleNativeError,
|
|
1142
1294
|
style: styles.webview
|
|
1143
1295
|
}
|
|
1144
1296
|
) });
|
|
1145
1297
|
}
|
|
1146
|
-
var styles =
|
|
1298
|
+
var styles = import_react_native5.StyleSheet.create({
|
|
1147
1299
|
container: { flex: 1 },
|
|
1148
1300
|
webview: { flex: 1, backgroundColor: "transparent" }
|
|
1149
1301
|
});
|
|
1150
1302
|
|
|
1151
1303
|
// src/domains/paywall/PaywallModal.tsx
|
|
1152
1304
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1153
|
-
var SCREEN_HEIGHT2 =
|
|
1305
|
+
var SCREEN_HEIGHT2 = import_react_native6.Dimensions.get("window").height;
|
|
1306
|
+
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1307
|
+
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1308
|
+
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1309
|
+
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1310
|
+
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native6.View, { style: styles2.fallbackCard, children: [
|
|
1312
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackTitle, children: title }),
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.fallbackDescription, children: description }),
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.retryButtonText, children: retryLabel }) }),
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.closeButtonText, children: closeLabel }) })
|
|
1316
|
+
] }) });
|
|
1317
|
+
}
|
|
1154
1318
|
function PaywallModal({
|
|
1155
1319
|
placement,
|
|
1156
1320
|
visible,
|
|
@@ -1168,32 +1332,64 @@ function PaywallModal({
|
|
|
1168
1332
|
variantKey,
|
|
1169
1333
|
campaignId
|
|
1170
1334
|
);
|
|
1171
|
-
const
|
|
1172
|
-
const
|
|
1335
|
+
const openAnim = (0, import_react5.useRef)(new import_react_native6.Animated.Value(SCREEN_HEIGHT2)).current;
|
|
1336
|
+
const [modalVisible, setModalVisible] = (0, import_react5.useState)(false);
|
|
1337
|
+
const handleResult = (0, import_react5.useCallback)((result) => {
|
|
1338
|
+
setModalVisible(false);
|
|
1339
|
+
onResult(result);
|
|
1340
|
+
}, [onResult]);
|
|
1341
|
+
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
|
|
1342
|
+
const [webViewError, setWebViewError] = (0, import_react5.useState)(null);
|
|
1343
|
+
const [retryCount, setRetryCount] = (0, import_react5.useState)(0);
|
|
1344
|
+
const retryCountRef = (0, import_react5.useRef)(0);
|
|
1345
|
+
const handleWebViewError = (0, import_react5.useCallback)(
|
|
1346
|
+
(err) => {
|
|
1347
|
+
if (retryCountRef.current >= 1) {
|
|
1348
|
+
setWebViewError({ code: err.code, description: err.description });
|
|
1349
|
+
} else {
|
|
1350
|
+
retryCountRef.current += 1;
|
|
1351
|
+
setRetryCount(retryCountRef.current);
|
|
1352
|
+
}
|
|
1353
|
+
},
|
|
1354
|
+
[]
|
|
1355
|
+
);
|
|
1356
|
+
const handleRetry = (0, import_react5.useCallback)(() => {
|
|
1357
|
+
retryCountRef.current = 0;
|
|
1358
|
+
setWebViewError(null);
|
|
1359
|
+
setRetryCount(0);
|
|
1360
|
+
}, []);
|
|
1173
1361
|
(0, import_react5.useEffect)(() => {
|
|
1174
1362
|
if (visible) {
|
|
1363
|
+
setModalVisible(true);
|
|
1175
1364
|
openAnim.setValue(SCREEN_HEIGHT2);
|
|
1176
|
-
|
|
1365
|
+
import_react_native6.Animated.timing(openAnim, {
|
|
1177
1366
|
toValue: 0,
|
|
1178
1367
|
duration: 550,
|
|
1179
|
-
easing:
|
|
1368
|
+
easing: import_react_native6.Easing.out(import_react_native6.Easing.cubic),
|
|
1180
1369
|
useNativeDriver: true
|
|
1181
1370
|
}).start();
|
|
1182
1371
|
}
|
|
1183
1372
|
}, [visible, openAnim]);
|
|
1184
|
-
if (!visible) return null;
|
|
1373
|
+
if (!modalVisible && !visible) return null;
|
|
1185
1374
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1186
|
-
|
|
1375
|
+
import_react_native6.Modal,
|
|
1187
1376
|
{
|
|
1188
|
-
visible,
|
|
1377
|
+
visible: modalVisible,
|
|
1189
1378
|
animationType: "none",
|
|
1190
1379
|
presentationStyle: "overFullScreen",
|
|
1191
1380
|
onRequestClose: handleClose,
|
|
1192
1381
|
transparent: true,
|
|
1193
1382
|
statusBarTranslucent: true,
|
|
1194
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1195
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1196
|
-
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1383
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native6.View, { style: styles2.container, children: [
|
|
1384
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native6.Text, { style: styles2.errorText, children: error.message }) }),
|
|
1385
|
+
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1386
|
+
ErrorFallback,
|
|
1387
|
+
{
|
|
1388
|
+
description: webViewError.description,
|
|
1389
|
+
onRetry: handleRetry,
|
|
1390
|
+
onClose: handleClose
|
|
1391
|
+
}
|
|
1392
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1197
1393
|
PaywallWebView,
|
|
1198
1394
|
{
|
|
1199
1395
|
paywallId: paywallConfig.id,
|
|
@@ -1204,39 +1400,67 @@ function PaywallModal({
|
|
|
1204
1400
|
isPurchasing,
|
|
1205
1401
|
onClose: handleClose,
|
|
1206
1402
|
onPurchase: handlePurchase,
|
|
1207
|
-
onRestore: handleRestore
|
|
1208
|
-
|
|
1209
|
-
|
|
1403
|
+
onRestore: handleRestore,
|
|
1404
|
+
onError: handleWebViewError
|
|
1405
|
+
},
|
|
1406
|
+
retryCount
|
|
1407
|
+
) })
|
|
1210
1408
|
] }) }) })
|
|
1211
1409
|
}
|
|
1212
1410
|
);
|
|
1213
1411
|
}
|
|
1214
|
-
var styles2 =
|
|
1215
|
-
overlay: {
|
|
1412
|
+
var styles2 = import_react_native6.StyleSheet.create({
|
|
1413
|
+
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1414
|
+
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1415
|
+
container: { flex: 1, backgroundColor: "#fff" },
|
|
1416
|
+
errorContainer: { flex: 1, justifyContent: "center", alignItems: "center", padding: 20 },
|
|
1417
|
+
errorText: { fontSize: 16, color: "#ef4444", textAlign: "center" },
|
|
1418
|
+
fallbackContainer: {
|
|
1216
1419
|
flex: 1,
|
|
1217
|
-
|
|
1420
|
+
justifyContent: "center",
|
|
1421
|
+
alignItems: "center",
|
|
1422
|
+
backgroundColor: "#111010",
|
|
1423
|
+
padding: 24
|
|
1218
1424
|
},
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1425
|
+
fallbackCard: {
|
|
1426
|
+
width: "100%",
|
|
1427
|
+
maxWidth: 360,
|
|
1428
|
+
alignItems: "center",
|
|
1429
|
+
backgroundColor: "rgba(255,255,255,0.07)",
|
|
1430
|
+
borderRadius: 16,
|
|
1431
|
+
padding: 28
|
|
1224
1432
|
},
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1433
|
+
fallbackTitle: {
|
|
1434
|
+
fontSize: 18,
|
|
1435
|
+
fontWeight: "600",
|
|
1436
|
+
color: "#f5f5f5",
|
|
1437
|
+
marginBottom: 10,
|
|
1438
|
+
textAlign: "center"
|
|
1228
1439
|
},
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1440
|
+
fallbackDescription: {
|
|
1441
|
+
fontSize: 14,
|
|
1442
|
+
color: "rgba(255,255,255,0.55)",
|
|
1443
|
+
textAlign: "center",
|
|
1444
|
+
marginBottom: 28,
|
|
1445
|
+
lineHeight: 20
|
|
1446
|
+
},
|
|
1447
|
+
retryButton: {
|
|
1448
|
+
width: "100%",
|
|
1449
|
+
backgroundColor: "#ffffff",
|
|
1450
|
+
borderRadius: 10,
|
|
1451
|
+
paddingVertical: 14,
|
|
1232
1452
|
alignItems: "center",
|
|
1233
|
-
|
|
1453
|
+
marginBottom: 12
|
|
1234
1454
|
},
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1455
|
+
retryButtonText: { fontSize: 15, fontWeight: "600", color: "#111010" },
|
|
1456
|
+
closeButton: {
|
|
1457
|
+
width: "100%",
|
|
1458
|
+
borderRadius: 10,
|
|
1459
|
+
paddingVertical: 14,
|
|
1460
|
+
alignItems: "center",
|
|
1461
|
+
backgroundColor: "rgba(255,255,255,0.1)"
|
|
1462
|
+
},
|
|
1463
|
+
closeButtonText: { fontSize: 15, fontWeight: "500", color: "rgba(255,255,255,0.7)" }
|
|
1240
1464
|
});
|
|
1241
1465
|
|
|
1242
1466
|
// src/domains/paywall/PaywallErrorBoundary.tsx
|
|
@@ -1249,8 +1473,7 @@ var PaywallErrorBoundary = class extends import_react6.Component {
|
|
|
1249
1473
|
static getDerivedStateFromError() {
|
|
1250
1474
|
return { hasError: true };
|
|
1251
1475
|
}
|
|
1252
|
-
componentDidCatch(
|
|
1253
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
|
|
1476
|
+
componentDidCatch(_error) {
|
|
1254
1477
|
}
|
|
1255
1478
|
render() {
|
|
1256
1479
|
return this.state.hasError ? null : this.props.children;
|
|
@@ -1273,7 +1496,7 @@ function usePaywallContext() {
|
|
|
1273
1496
|
|
|
1274
1497
|
// src/domains/paywall/usePaywallPresenter.ts
|
|
1275
1498
|
var import_react8 = require("react");
|
|
1276
|
-
function usePaywallPresenter(preloadedWebView,
|
|
1499
|
+
function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
1277
1500
|
const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react8.useState)(false);
|
|
1278
1501
|
const resolverRef = (0, import_react8.useRef)(null);
|
|
1279
1502
|
const presentedAtRef = (0, import_react8.useRef)(Date.now());
|
|
@@ -1322,9 +1545,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1322
1545
|
};
|
|
1323
1546
|
void track();
|
|
1324
1547
|
setShowingPreloadedWebView(false);
|
|
1325
|
-
clearPreloadedWebView();
|
|
1326
1548
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
1327
|
-
}, [preloadedWebView,
|
|
1549
|
+
}, [preloadedWebView, resolvePreloadedPaywall]);
|
|
1328
1550
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1329
1551
|
}
|
|
1330
1552
|
|
|
@@ -1531,10 +1753,7 @@ var AffiliateManager = class {
|
|
|
1531
1753
|
);
|
|
1532
1754
|
}
|
|
1533
1755
|
}
|
|
1534
|
-
log(...
|
|
1535
|
-
if (this.debug) {
|
|
1536
|
-
console.warn("[AffiliateManager]", ...args);
|
|
1537
|
-
}
|
|
1756
|
+
log(..._args) {
|
|
1538
1757
|
}
|
|
1539
1758
|
};
|
|
1540
1759
|
var affiliateManager = new AffiliateManager();
|
|
@@ -1551,33 +1770,289 @@ var PaywalloAffiliate = {
|
|
|
1551
1770
|
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1552
1771
|
};
|
|
1553
1772
|
|
|
1554
|
-
// src/
|
|
1773
|
+
// src/domains/subscription/SubscriptionCache.ts
|
|
1774
|
+
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1775
|
+
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1776
|
+
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1777
|
+
function keyFor(distinctId) {
|
|
1778
|
+
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
1779
|
+
}
|
|
1780
|
+
var SubscriptionCache = class {
|
|
1781
|
+
constructor(ttl = DEFAULT_TTL) {
|
|
1782
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
1783
|
+
this.legacyCleanupDone = false;
|
|
1784
|
+
this.ttl = ttl;
|
|
1785
|
+
this.storage = new SecureStorage();
|
|
1786
|
+
}
|
|
1787
|
+
async cleanupLegacyCache() {
|
|
1788
|
+
if (this.legacyCleanupDone) return;
|
|
1789
|
+
this.legacyCleanupDone = true;
|
|
1790
|
+
try {
|
|
1791
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1792
|
+
} catch {
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
async get(distinctId) {
|
|
1796
|
+
await this.cleanupLegacyCache();
|
|
1797
|
+
if (!distinctId) return null;
|
|
1798
|
+
const memEntry = this.memoryCache.get(distinctId);
|
|
1799
|
+
if (memEntry && !this.isExpired(memEntry)) {
|
|
1800
|
+
return memEntry;
|
|
1801
|
+
}
|
|
1802
|
+
try {
|
|
1803
|
+
const stored = await this.storage.get(keyFor(distinctId));
|
|
1804
|
+
if (!stored) {
|
|
1805
|
+
return null;
|
|
1806
|
+
}
|
|
1807
|
+
const parsed = JSON.parse(stored);
|
|
1808
|
+
if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
|
|
1809
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1810
|
+
return null;
|
|
1811
|
+
}
|
|
1812
|
+
const cached = parsed;
|
|
1813
|
+
if (cached.data.subscription?.expiresAt) {
|
|
1814
|
+
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
1815
|
+
}
|
|
1816
|
+
if (cached.data.subscription?.originalPurchaseDate) {
|
|
1817
|
+
cached.data.subscription.originalPurchaseDate = new Date(
|
|
1818
|
+
cached.data.subscription.originalPurchaseDate
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
if (cached.data.subscription?.latestPurchaseDate) {
|
|
1822
|
+
cached.data.subscription.latestPurchaseDate = new Date(
|
|
1823
|
+
cached.data.subscription.latestPurchaseDate
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
if (cached.data.subscription?.cancellationDate) {
|
|
1827
|
+
cached.data.subscription.cancellationDate = new Date(
|
|
1828
|
+
cached.data.subscription.cancellationDate
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
1832
|
+
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
1833
|
+
cached.data.subscription.gracePeriodExpiresAt
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
cached.isStale = this.isExpired(cached);
|
|
1837
|
+
if (!cached.isStale) {
|
|
1838
|
+
this.memoryCache.set(distinctId, cached);
|
|
1839
|
+
}
|
|
1840
|
+
return cached;
|
|
1841
|
+
} catch {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
async set(distinctId, data) {
|
|
1846
|
+
if (!distinctId) return;
|
|
1847
|
+
const cached = {
|
|
1848
|
+
data,
|
|
1849
|
+
cachedAt: Date.now(),
|
|
1850
|
+
isStale: false
|
|
1851
|
+
};
|
|
1852
|
+
this.memoryCache.set(distinctId, cached);
|
|
1853
|
+
try {
|
|
1854
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1855
|
+
} catch {
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
async invalidate(distinctId) {
|
|
1859
|
+
if (!distinctId) return;
|
|
1860
|
+
this.memoryCache.delete(distinctId);
|
|
1861
|
+
try {
|
|
1862
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
async invalidateAll() {
|
|
1867
|
+
this.memoryCache.clear();
|
|
1868
|
+
try {
|
|
1869
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1870
|
+
} catch {
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
isExpired(cached) {
|
|
1874
|
+
return Date.now() - cached.cachedAt > this.ttl;
|
|
1875
|
+
}
|
|
1876
|
+
setTTL(ttl) {
|
|
1877
|
+
this.ttl = ttl;
|
|
1878
|
+
}
|
|
1879
|
+
};
|
|
1880
|
+
var subscriptionCache = new SubscriptionCache();
|
|
1881
|
+
|
|
1882
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1555
1883
|
var import_react_native7 = require("react-native");
|
|
1556
1884
|
|
|
1557
|
-
// src/
|
|
1558
|
-
var
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1885
|
+
// src/domains/session/SessionError.ts
|
|
1886
|
+
var SessionError = class extends PaywalloError {
|
|
1887
|
+
constructor(code, message) {
|
|
1888
|
+
super("session", code, message);
|
|
1889
|
+
this.name = "SessionError";
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
var SESSION_ERROR_CODES = {
|
|
1893
|
+
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
1894
|
+
START_FAILED: "SESSION_START_FAILED",
|
|
1895
|
+
END_FAILED: "SESSION_END_FAILED",
|
|
1896
|
+
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
1897
|
+
};
|
|
1898
|
+
|
|
1899
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1900
|
+
var SubscriptionManagerClass = class {
|
|
1901
|
+
constructor() {
|
|
1902
|
+
this.config = null;
|
|
1903
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1904
|
+
this.userId = null;
|
|
1905
|
+
this.cache = new SubscriptionCache();
|
|
1906
|
+
}
|
|
1907
|
+
cacheKey() {
|
|
1908
|
+
return this.userId ?? "__anonymous__";
|
|
1909
|
+
}
|
|
1910
|
+
init(config) {
|
|
1911
|
+
this.config = config;
|
|
1912
|
+
if (config.cacheTTL) {
|
|
1913
|
+
this.cache.setTTL(config.cacheTTL);
|
|
1565
1914
|
}
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1915
|
+
}
|
|
1916
|
+
setUserId(userId) {
|
|
1917
|
+
if (this.userId !== userId) {
|
|
1918
|
+
this.userId = userId;
|
|
1919
|
+
void this.cache.invalidateAll();
|
|
1569
1920
|
}
|
|
1570
|
-
return DEFAULT_WEB_URL;
|
|
1571
|
-
} catch {
|
|
1572
|
-
return DEFAULT_WEB_URL;
|
|
1573
1921
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1922
|
+
async hasActiveSubscription(forceRefresh = false) {
|
|
1923
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1924
|
+
return status.hasActiveSubscription;
|
|
1925
|
+
}
|
|
1926
|
+
async getSubscription(forceRefresh = false) {
|
|
1927
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1928
|
+
return status.subscription;
|
|
1929
|
+
}
|
|
1930
|
+
async getEntitlements(forceRefresh = false) {
|
|
1931
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1932
|
+
return status.entitlements;
|
|
1933
|
+
}
|
|
1934
|
+
async getSubscriptionStatus(forceRefresh = false) {
|
|
1935
|
+
if (!this.config) {
|
|
1936
|
+
return this.getEmptyStatus();
|
|
1937
|
+
}
|
|
1938
|
+
if (!forceRefresh) {
|
|
1939
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1940
|
+
if (cached && !cached.isStale) {
|
|
1941
|
+
return cached.data;
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
try {
|
|
1945
|
+
const status = await this.fetchSubscriptionStatus();
|
|
1946
|
+
await this.cache.set(this.cacheKey(), status);
|
|
1947
|
+
this.notifyListeners(status);
|
|
1948
|
+
return status;
|
|
1949
|
+
} catch (error) {
|
|
1950
|
+
this.log("Failed to fetch subscription status", error);
|
|
1951
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1952
|
+
if (cached) {
|
|
1953
|
+
return cached.data;
|
|
1954
|
+
}
|
|
1955
|
+
return this.getEmptyStatus();
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
async restorePurchases() {
|
|
1959
|
+
if (!this.config) {
|
|
1960
|
+
throw new SessionError(
|
|
1961
|
+
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
1962
|
+
"SubscriptionManager not initialized"
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1966
|
+
return this.getSubscriptionStatus(true);
|
|
1967
|
+
}
|
|
1968
|
+
async fetchSubscriptionStatus() {
|
|
1969
|
+
if (!this.config) {
|
|
1970
|
+
return this.getEmptyStatus();
|
|
1971
|
+
}
|
|
1972
|
+
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
1973
|
+
if (this.userId) {
|
|
1974
|
+
url.searchParams.set("userId", this.userId);
|
|
1975
|
+
}
|
|
1976
|
+
url.searchParams.set("platform", import_react_native7.Platform.OS);
|
|
1977
|
+
const response = await fetch(url.toString(), {
|
|
1978
|
+
method: "GET",
|
|
1979
|
+
headers: {
|
|
1980
|
+
"Content-Type": "application/json",
|
|
1981
|
+
"X-App-Key": this.config.appKey
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
if (!response.ok) {
|
|
1985
|
+
throw new SessionError(
|
|
1986
|
+
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
1987
|
+
`Failed to fetch subscription status: ${response.status}`
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
const data = await response.json();
|
|
1991
|
+
if (data.subscription) {
|
|
1992
|
+
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
1993
|
+
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
1994
|
+
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
1995
|
+
if (data.subscription.cancellationDate) {
|
|
1996
|
+
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
1997
|
+
}
|
|
1998
|
+
if (data.subscription.gracePeriodExpiresAt) {
|
|
1999
|
+
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return data;
|
|
2003
|
+
}
|
|
2004
|
+
getEmptyStatus() {
|
|
2005
|
+
return {
|
|
2006
|
+
hasActiveSubscription: false,
|
|
2007
|
+
subscription: null,
|
|
2008
|
+
entitlements: []
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
addListener(listener) {
|
|
2012
|
+
this.listeners.add(listener);
|
|
2013
|
+
return () => this.listeners.delete(listener);
|
|
2014
|
+
}
|
|
2015
|
+
notifyListeners(status) {
|
|
2016
|
+
for (const listener of this.listeners) {
|
|
2017
|
+
listener(status);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
async onPurchaseComplete() {
|
|
2021
|
+
await this.cache.invalidate(this.cacheKey());
|
|
2022
|
+
await this.getSubscriptionStatus(true);
|
|
2023
|
+
}
|
|
2024
|
+
log(..._args) {
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
var subscriptionManager = new SubscriptionManagerClass();
|
|
2028
|
+
|
|
2029
|
+
// src/core/ApiClient.ts
|
|
2030
|
+
var import_react_native9 = require("react-native");
|
|
2031
|
+
|
|
2032
|
+
// src/core/apiUrlUtils.ts
|
|
2033
|
+
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
2034
|
+
function deriveWebUrl(baseUrl) {
|
|
2035
|
+
try {
|
|
2036
|
+
const url = new URL(baseUrl);
|
|
2037
|
+
if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
|
|
2038
|
+
url.port = "3000";
|
|
2039
|
+
return url.toString().replace(/\/$/, "");
|
|
2040
|
+
}
|
|
2041
|
+
if (url.hostname.startsWith("api.")) {
|
|
2042
|
+
url.hostname = url.hostname.replace("api.", "app.");
|
|
2043
|
+
return url.toString().replace(/\/$/, "");
|
|
2044
|
+
}
|
|
2045
|
+
return DEFAULT_WEB_URL;
|
|
2046
|
+
} catch {
|
|
2047
|
+
return DEFAULT_WEB_URL;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
function buildConditionalFlagUrl(baseUrl, flagKey, context) {
|
|
2051
|
+
const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
|
|
2052
|
+
if (context.platform) url.searchParams.set("platform", context.platform);
|
|
2053
|
+
if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
|
|
2054
|
+
if (context.country) url.searchParams.set("country", context.country);
|
|
2055
|
+
if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
|
|
1581
2056
|
return url.toString();
|
|
1582
2057
|
}
|
|
1583
2058
|
|
|
@@ -1784,10 +2259,7 @@ var HttpClient = class {
|
|
|
1784
2259
|
sleep(ms) {
|
|
1785
2260
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1786
2261
|
}
|
|
1787
|
-
log(
|
|
1788
|
-
if (this.config.debug) {
|
|
1789
|
-
console.warn("[HttpClient]", message, data ?? "");
|
|
1790
|
-
}
|
|
2262
|
+
log(..._args) {
|
|
1791
2263
|
}
|
|
1792
2264
|
setDebug(debug) {
|
|
1793
2265
|
this.config.debug = debug;
|
|
@@ -1798,7 +2270,7 @@ var HttpClient = class {
|
|
|
1798
2270
|
};
|
|
1799
2271
|
|
|
1800
2272
|
// src/core/network/NetworkMonitor.ts
|
|
1801
|
-
var
|
|
2273
|
+
var import_react_native8 = require("react-native");
|
|
1802
2274
|
|
|
1803
2275
|
// src/core/network/types.ts
|
|
1804
2276
|
var DEFAULT_NETWORK_CONFIG = {
|
|
@@ -1844,7 +2316,7 @@ var NetworkMonitorClass = class {
|
|
|
1844
2316
|
}, this.config.checkInterval);
|
|
1845
2317
|
}
|
|
1846
2318
|
setupAppStateListener() {
|
|
1847
|
-
this.appStateSubscription =
|
|
2319
|
+
this.appStateSubscription = import_react_native8.AppState.addEventListener("change", (state) => {
|
|
1848
2320
|
if (state === "active") {
|
|
1849
2321
|
void this.checkConnectivity();
|
|
1850
2322
|
}
|
|
@@ -1931,10 +2403,7 @@ var NetworkMonitorClass = class {
|
|
|
1931
2403
|
this.initialized = false;
|
|
1932
2404
|
this.currentState = "unknown";
|
|
1933
2405
|
}
|
|
1934
|
-
log(
|
|
1935
|
-
if (this.config.debug) {
|
|
1936
|
-
console.warn("[NetworkMonitor]", message, data ?? "");
|
|
1937
|
-
}
|
|
2406
|
+
log(..._args) {
|
|
1938
2407
|
}
|
|
1939
2408
|
setDebug(debug) {
|
|
1940
2409
|
this.config.debug = debug;
|
|
@@ -1964,6 +2433,8 @@ function normalizeForComparison(obj) {
|
|
|
1964
2433
|
return "{" + pairs.join(",") + "}";
|
|
1965
2434
|
}
|
|
1966
2435
|
function findDuplicateIndex(queue, item) {
|
|
2436
|
+
const byId = queue.findIndex((existing) => existing.id === item.id);
|
|
2437
|
+
if (byId !== -1) return byId;
|
|
1967
2438
|
const normalizedBody = normalizeForComparison(item.body);
|
|
1968
2439
|
return queue.findIndex(
|
|
1969
2440
|
(existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
|
|
@@ -2032,9 +2503,9 @@ var OfflineQueueClass = class {
|
|
|
2032
2503
|
void this.saveToStorage();
|
|
2033
2504
|
}
|
|
2034
2505
|
}
|
|
2035
|
-
async enqueue(method, url, body, headers) {
|
|
2506
|
+
async enqueue(method, url, body, headers, id) {
|
|
2036
2507
|
const item = {
|
|
2037
|
-
id: generateUUID(),
|
|
2508
|
+
id: id ?? generateUUID(),
|
|
2038
2509
|
method,
|
|
2039
2510
|
url,
|
|
2040
2511
|
body,
|
|
@@ -2118,7 +2589,6 @@ var OfflineQueueClass = class {
|
|
|
2118
2589
|
this.emit({ type: "processing:end", queueSize: this.queue.length });
|
|
2119
2590
|
return { processed, failed };
|
|
2120
2591
|
}
|
|
2121
|
-
/** Returns true if the item was permanently removed (counted as failed). */
|
|
2122
2592
|
async handleItemFailure(item, error, permanent = false) {
|
|
2123
2593
|
if (permanent || item.attempts >= item.maxAttempts) {
|
|
2124
2594
|
await this.removeItem(item.id);
|
|
@@ -2180,10 +2650,7 @@ var OfflineQueueClass = class {
|
|
|
2180
2650
|
}
|
|
2181
2651
|
}
|
|
2182
2652
|
}
|
|
2183
|
-
log(
|
|
2184
|
-
if (this.config.debug) {
|
|
2185
|
-
console.warn("[OfflineQueue]", message, data ?? "");
|
|
2186
|
-
}
|
|
2653
|
+
log(..._args) {
|
|
2187
2654
|
}
|
|
2188
2655
|
setDebug(debug) {
|
|
2189
2656
|
this.config.debug = debug;
|
|
@@ -2325,10 +2792,7 @@ var QueueProcessorClass = class {
|
|
|
2325
2792
|
this.httpClient = null;
|
|
2326
2793
|
this.initialized = false;
|
|
2327
2794
|
}
|
|
2328
|
-
log(
|
|
2329
|
-
if (this.config.debug) {
|
|
2330
|
-
console.warn("[QueueProcessor]", message, data ?? "");
|
|
2331
|
-
}
|
|
2795
|
+
log(..._args) {
|
|
2332
2796
|
}
|
|
2333
2797
|
setDebug(debug) {
|
|
2334
2798
|
this.config.debug = debug;
|
|
@@ -2340,10 +2804,9 @@ var queueProcessor = new QueueProcessorClass();
|
|
|
2340
2804
|
var ApiCache = class {
|
|
2341
2805
|
constructor() {
|
|
2342
2806
|
this.CACHE_TTL = 3e5;
|
|
2343
|
-
// 5 minutes
|
|
2344
2807
|
this.CACHE_NULL_TTL = 3e4;
|
|
2345
|
-
// 30 seconds — for null/404 variants
|
|
2346
2808
|
this.MAX_SIZE = 200;
|
|
2809
|
+
this.STALE_WINDOW_MS = 864e5;
|
|
2347
2810
|
this.variantCache = /* @__PURE__ */ new Map();
|
|
2348
2811
|
this.paywallCache = /* @__PURE__ */ new Map();
|
|
2349
2812
|
this.campaignCache = /* @__PURE__ */ new Map();
|
|
@@ -2381,7 +2844,7 @@ var ApiCache = class {
|
|
|
2381
2844
|
}
|
|
2382
2845
|
getStaleVariant(key) {
|
|
2383
2846
|
const entry = this.variantCache.get(key);
|
|
2384
|
-
if (entry && Date.now() - entry.ts <
|
|
2847
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2385
2848
|
return void 0;
|
|
2386
2849
|
}
|
|
2387
2850
|
getPaywall(key) {
|
|
@@ -2393,19 +2856,19 @@ var ApiCache = class {
|
|
|
2393
2856
|
}
|
|
2394
2857
|
getStalePaywall(key) {
|
|
2395
2858
|
const entry = this.paywallCache.get(key);
|
|
2396
|
-
if (entry && Date.now() - entry.ts <
|
|
2859
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2397
2860
|
return void 0;
|
|
2398
2861
|
}
|
|
2399
2862
|
getCampaign(key) {
|
|
2400
2863
|
return this.getCached(this.campaignCache, key);
|
|
2401
2864
|
}
|
|
2402
|
-
setCampaign(key, value) {
|
|
2403
|
-
this.setCached(this.campaignCache, key, value);
|
|
2865
|
+
setCampaign(key, value, ttlOverride) {
|
|
2866
|
+
this.setCached(this.campaignCache, key, value, ttlOverride);
|
|
2404
2867
|
this.evictExpiredEntries(this.campaignCache);
|
|
2405
2868
|
}
|
|
2406
2869
|
getStaleCampaign(key) {
|
|
2407
2870
|
const entry = this.campaignCache.get(key);
|
|
2408
|
-
if (entry && Date.now() - entry.ts <
|
|
2871
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2409
2872
|
return void 0;
|
|
2410
2873
|
}
|
|
2411
2874
|
};
|
|
@@ -2413,7 +2876,7 @@ var ApiCache = class {
|
|
|
2413
2876
|
// src/core/ApiClient.ts
|
|
2414
2877
|
var SDK_VERSION = "1.0.0";
|
|
2415
2878
|
var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
|
|
2416
|
-
var
|
|
2879
|
+
var _ApiClient = class _ApiClient {
|
|
2417
2880
|
constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2418
2881
|
this.cache = new ApiCache();
|
|
2419
2882
|
this.appKey = appKey;
|
|
@@ -2424,11 +2887,12 @@ var ApiClient = class {
|
|
|
2424
2887
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2425
2888
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
2426
2889
|
}
|
|
2427
|
-
// ─── Infrastructure ──────────────────────────────────────────────────────────
|
|
2428
|
-
/** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
|
|
2429
2890
|
getHttpClient() {
|
|
2430
2891
|
return this.httpClient;
|
|
2431
2892
|
}
|
|
2893
|
+
getBaseUrl() {
|
|
2894
|
+
return this.baseUrl;
|
|
2895
|
+
}
|
|
2432
2896
|
getWebUrl() {
|
|
2433
2897
|
return this.webUrl;
|
|
2434
2898
|
}
|
|
@@ -2454,7 +2918,6 @@ var ApiClient = class {
|
|
|
2454
2918
|
async clearQueue() {
|
|
2455
2919
|
await offlineQueue.clear();
|
|
2456
2920
|
}
|
|
2457
|
-
// ─── Public HTTP facade (headers injected automatically) ─────────────────────
|
|
2458
2921
|
async get(path) {
|
|
2459
2922
|
return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
|
|
2460
2923
|
}
|
|
@@ -2468,29 +2931,28 @@ var ApiClient = class {
|
|
|
2468
2931
|
message,
|
|
2469
2932
|
context,
|
|
2470
2933
|
sdkVersion: SDK_VERSION,
|
|
2471
|
-
platform:
|
|
2934
|
+
platform: import_react_native9.Platform.OS
|
|
2472
2935
|
}, true);
|
|
2473
2936
|
} catch {
|
|
2474
2937
|
}
|
|
2475
2938
|
}
|
|
2476
|
-
// ─── Business methods ────────────────────────────────────────────────────────
|
|
2477
2939
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2478
2940
|
await this.postWithQueue("/api/v1/events", {
|
|
2479
2941
|
eventName,
|
|
2480
2942
|
distinctId,
|
|
2481
|
-
properties: { ...properties, platform:
|
|
2943
|
+
properties: { ...properties, platform: import_react_native9.Platform.OS },
|
|
2482
2944
|
timestamp: timestamp ?? Date.now(),
|
|
2483
2945
|
environment: this.environment.toLowerCase()
|
|
2484
2946
|
}, `event:${eventName}`);
|
|
2485
2947
|
}
|
|
2486
|
-
async identify(distinctId, properties, email) {
|
|
2487
|
-
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform:
|
|
2948
|
+
async identify(distinctId, properties, email, deviceId) {
|
|
2949
|
+
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native9.Platform.OS, ...deviceId && { deviceId } }, "identify");
|
|
2488
2950
|
}
|
|
2489
2951
|
async startSession(distinctId, sessionId, platform) {
|
|
2490
2952
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2491
2953
|
distinctId,
|
|
2492
2954
|
sessionId,
|
|
2493
|
-
platform: platform ??
|
|
2955
|
+
platform: platform ?? import_react_native9.Platform.OS,
|
|
2494
2956
|
environment: this.environment.toLowerCase()
|
|
2495
2957
|
}, `session.start:${sessionId}`);
|
|
2496
2958
|
return { success: true };
|
|
@@ -2503,6 +2965,11 @@ var ApiClient = class {
|
|
|
2503
2965
|
const key = `${flagKey}:${distinctId}`;
|
|
2504
2966
|
const hit = this.cache.getVariant(key);
|
|
2505
2967
|
if (hit !== void 0) return hit;
|
|
2968
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
2969
|
+
if (persisted !== null) {
|
|
2970
|
+
this.cache.setVariant(key, persisted);
|
|
2971
|
+
return persisted;
|
|
2972
|
+
}
|
|
2506
2973
|
try {
|
|
2507
2974
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2508
2975
|
if (res.status === 404) {
|
|
@@ -2517,6 +2984,7 @@ var ApiClient = class {
|
|
|
2517
2984
|
return { variant: null };
|
|
2518
2985
|
}
|
|
2519
2986
|
this.cache.setVariant(key, res.data);
|
|
2987
|
+
await this.writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2520
2988
|
return res.data;
|
|
2521
2989
|
} catch (error) {
|
|
2522
2990
|
this.log("Failed to get variant:", flagKey, error);
|
|
@@ -2562,7 +3030,7 @@ var ApiClient = class {
|
|
|
2562
3030
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2563
3031
|
if (!result) {
|
|
2564
3032
|
this.log("No campaign found for placement:", placement);
|
|
2565
|
-
this.cache.setCampaign(key, null);
|
|
3033
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2566
3034
|
return null;
|
|
2567
3035
|
}
|
|
2568
3036
|
this.log("Got campaign:", placement, result.variantKey);
|
|
@@ -2575,6 +3043,32 @@ var ApiClient = class {
|
|
|
2575
3043
|
return null;
|
|
2576
3044
|
}
|
|
2577
3045
|
}
|
|
3046
|
+
async getPrimaryCampaign(distinctId) {
|
|
3047
|
+
const key = `primary:${distinctId}`;
|
|
3048
|
+
const hit = this.cache.getCampaign(key);
|
|
3049
|
+
if (hit !== void 0) return hit;
|
|
3050
|
+
try {
|
|
3051
|
+
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
3052
|
+
if (res.status === 404) {
|
|
3053
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
3054
|
+
return null;
|
|
3055
|
+
}
|
|
3056
|
+
if (!res.ok) {
|
|
3057
|
+
this.log("Non-OK response for primary campaign:", res.status);
|
|
3058
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
3059
|
+
if (stale !== void 0) return stale;
|
|
3060
|
+
return null;
|
|
3061
|
+
}
|
|
3062
|
+
this.log("Got primary campaign:", res.data?.campaignId);
|
|
3063
|
+
this.cache.setCampaign(key, res.data);
|
|
3064
|
+
return res.data;
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
this.log("Failed to get primary campaign:", error);
|
|
3067
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
3068
|
+
if (stale !== void 0) return stale;
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
2578
3072
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2579
3073
|
const result = await validatePurchase(
|
|
2580
3074
|
this,
|
|
@@ -2598,6 +3092,35 @@ var ApiClient = class {
|
|
|
2598
3092
|
this.log("Subscription status:", result.hasActiveSubscription);
|
|
2599
3093
|
return result;
|
|
2600
3094
|
}
|
|
3095
|
+
async evaluateFlags(keys, distinctId) {
|
|
3096
|
+
const query = keys.map(encodeURIComponent).join(",");
|
|
3097
|
+
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
3098
|
+
try {
|
|
3099
|
+
const res = await this.httpClient.get(path, {
|
|
3100
|
+
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
3101
|
+
});
|
|
3102
|
+
if (!res.ok) {
|
|
3103
|
+
this.log("Non-OK response for evaluateFlags:", res.status);
|
|
3104
|
+
return {};
|
|
3105
|
+
}
|
|
3106
|
+
const raw = res.data;
|
|
3107
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
3108
|
+
const record = raw;
|
|
3109
|
+
const result = {};
|
|
3110
|
+
for (const key of Object.keys(record)) {
|
|
3111
|
+
const value = record[key];
|
|
3112
|
+
const variant = typeof value === "boolean" ? String(value) : typeof value === "string" ? value : null;
|
|
3113
|
+
result[key] = variant;
|
|
3114
|
+
const flagVariant = { variant };
|
|
3115
|
+
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
3116
|
+
await this.writeFlagToStorage(key, distinctId, flagVariant);
|
|
3117
|
+
}
|
|
3118
|
+
return result;
|
|
3119
|
+
} catch (error) {
|
|
3120
|
+
this.log("Failed to evaluateFlags:", error);
|
|
3121
|
+
return {};
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
2601
3124
|
async getConditionalFlag(flagKey, context) {
|
|
2602
3125
|
try {
|
|
2603
3126
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
@@ -2614,11 +3137,37 @@ var ApiClient = class {
|
|
|
2614
3137
|
return { value: false, flagKey };
|
|
2615
3138
|
}
|
|
2616
3139
|
}
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
3140
|
+
async getVariantFromCache(flagKey, distinctId) {
|
|
3141
|
+
const key = `${flagKey}:${distinctId}`;
|
|
3142
|
+
const hit = this.cache.getVariant(key);
|
|
3143
|
+
if (hit !== void 0) return hit;
|
|
3144
|
+
const persisted = await this.readFlagFromStorage(flagKey, distinctId);
|
|
3145
|
+
if (persisted !== null) {
|
|
3146
|
+
this.cache.setVariant(key, persisted);
|
|
3147
|
+
return persisted;
|
|
3148
|
+
}
|
|
3149
|
+
return null;
|
|
3150
|
+
}
|
|
3151
|
+
async readFlagFromStorage(flagKey, distinctId) {
|
|
3152
|
+
try {
|
|
3153
|
+
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3154
|
+
if (!raw) return null;
|
|
3155
|
+
const parsed = JSON.parse(raw);
|
|
3156
|
+
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3157
|
+
const { variant, payload, cachedAt } = parsed;
|
|
3158
|
+
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
3159
|
+
return { variant, payload };
|
|
3160
|
+
} catch {
|
|
3161
|
+
return null;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3165
|
+
try {
|
|
3166
|
+
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3167
|
+
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3168
|
+
} catch {
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
2622
3171
|
async postWithQueue(url, payload, label) {
|
|
2623
3172
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
2624
3173
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -2637,10 +3186,11 @@ var ApiClient = class {
|
|
|
2637
3186
|
throw error;
|
|
2638
3187
|
}
|
|
2639
3188
|
}
|
|
2640
|
-
log(...
|
|
2641
|
-
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
3189
|
+
log(..._args) {
|
|
2642
3190
|
}
|
|
2643
3191
|
};
|
|
3192
|
+
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3193
|
+
var ApiClient = _ApiClient;
|
|
2644
3194
|
|
|
2645
3195
|
// src/domains/campaign/CampaignError.ts
|
|
2646
3196
|
var CampaignError = class extends PaywalloError {
|
|
@@ -2659,39 +3209,72 @@ var CAMPAIGN_ERROR_CODES = {
|
|
|
2659
3209
|
|
|
2660
3210
|
// src/domains/campaign/CampaignGateService.ts
|
|
2661
3211
|
var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3212
|
+
var PRELOAD_WAIT_MAX_MS = 2e3;
|
|
3213
|
+
var PRELOAD_WAIT_INTERVAL_MS = 100;
|
|
2662
3214
|
var CampaignGateService = class {
|
|
2663
3215
|
constructor() {
|
|
2664
3216
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3217
|
+
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
2665
3218
|
}
|
|
2666
3219
|
async getCampaign(apiClient, placement, context) {
|
|
2667
3220
|
const distinctId = identityManager.getDistinctId();
|
|
2668
3221
|
if (!distinctId) return null;
|
|
2669
3222
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2670
3223
|
}
|
|
2671
|
-
async
|
|
2672
|
-
|
|
3224
|
+
async waitForPreload(placement) {
|
|
3225
|
+
if (!this.pendingPreloads.has(placement)) return;
|
|
3226
|
+
const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
|
|
3227
|
+
await new Promise((resolve) => {
|
|
3228
|
+
const check = () => {
|
|
3229
|
+
if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
|
|
3230
|
+
resolve();
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
|
|
3234
|
+
};
|
|
3235
|
+
check();
|
|
3236
|
+
});
|
|
3237
|
+
}
|
|
3238
|
+
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
3239
|
+
await this.waitForPreload(placement);
|
|
3240
|
+
const preloaded = this.getPreloadedCampaign(placement);
|
|
3241
|
+
const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
|
|
2673
3242
|
if (!campaign) {
|
|
2674
3243
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2675
3244
|
}
|
|
3245
|
+
if (!context?.forceShow) {
|
|
3246
|
+
const isActive = await hasActive();
|
|
3247
|
+
if (isActive) {
|
|
3248
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
2676
3251
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2677
3252
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2678
|
-
return { ...await paywallPresenter(
|
|
3253
|
+
return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2679
3254
|
}
|
|
2680
3255
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2681
|
-
|
|
2682
|
-
|
|
3256
|
+
const isActive = await hasActive();
|
|
3257
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3258
|
+
if (isActive) return true;
|
|
3259
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2683
3260
|
return r.purchased || r.restored;
|
|
2684
3261
|
}
|
|
2685
3262
|
async preloadCampaign(apiClient, placement, context) {
|
|
3263
|
+
this.pendingPreloads.add(placement);
|
|
2686
3264
|
try {
|
|
2687
3265
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2688
3266
|
if (!campaign?.paywall) {
|
|
2689
3267
|
return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
|
|
2690
3268
|
}
|
|
2691
3269
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3270
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
3271
|
+
console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3272
|
+
}
|
|
2692
3273
|
return { success: true };
|
|
2693
3274
|
} catch (error) {
|
|
2694
3275
|
return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
|
|
3276
|
+
} finally {
|
|
3277
|
+
this.pendingPreloads.delete(placement);
|
|
2695
3278
|
}
|
|
2696
3279
|
}
|
|
2697
3280
|
getPreloadedCampaign(placement) {
|
|
@@ -2705,12 +3288,13 @@ var CampaignGateService = class {
|
|
|
2705
3288
|
}
|
|
2706
3289
|
clearPreloaded() {
|
|
2707
3290
|
this.preloadedCampaigns.clear();
|
|
3291
|
+
this.pendingPreloads.clear();
|
|
2708
3292
|
}
|
|
2709
3293
|
};
|
|
2710
3294
|
var campaignGateService = new CampaignGateService();
|
|
2711
3295
|
|
|
2712
3296
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
2713
|
-
var
|
|
3297
|
+
var import_react_native10 = require("react-native");
|
|
2714
3298
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
2715
3299
|
var AdvertisingIdManager = class {
|
|
2716
3300
|
constructor() {
|
|
@@ -2721,7 +3305,7 @@ var AdvertisingIdManager = class {
|
|
|
2721
3305
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
2722
3306
|
try {
|
|
2723
3307
|
const tracking = await import("expo-tracking-transparency");
|
|
2724
|
-
if (
|
|
3308
|
+
if (import_react_native10.Platform.OS === "ios") {
|
|
2725
3309
|
if (!tracking.isAvailable()) {
|
|
2726
3310
|
const idfv2 = await this.collectIdfv();
|
|
2727
3311
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -2740,7 +3324,7 @@ var AdvertisingIdManager = class {
|
|
|
2740
3324
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
2741
3325
|
return this.cached;
|
|
2742
3326
|
}
|
|
2743
|
-
if (
|
|
3327
|
+
if (import_react_native10.Platform.OS === "android") {
|
|
2744
3328
|
const androidStatus = await tracking.getTrackingPermissionsAsync();
|
|
2745
3329
|
const optedIn = androidStatus.granted;
|
|
2746
3330
|
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
@@ -2799,14 +3383,14 @@ var FacebookIdManager = class {
|
|
|
2799
3383
|
var facebookIdManager = new FacebookIdManager();
|
|
2800
3384
|
|
|
2801
3385
|
// src/domains/identity/InstallTracker.ts
|
|
2802
|
-
var
|
|
3386
|
+
var import_react_native13 = require("react-native");
|
|
2803
3387
|
|
|
2804
3388
|
// src/domains/identity/InstallReferrerManager.ts
|
|
2805
|
-
var
|
|
3389
|
+
var import_react_native11 = require("react-native");
|
|
2806
3390
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
2807
3391
|
var InstallReferrerManager = class {
|
|
2808
3392
|
async getReferrer() {
|
|
2809
|
-
if (
|
|
3393
|
+
if (import_react_native11.Platform.OS !== "android") {
|
|
2810
3394
|
return this.emptyResult();
|
|
2811
3395
|
}
|
|
2812
3396
|
let timerId;
|
|
@@ -2866,21 +3450,7 @@ var InstallReferrerManager = class {
|
|
|
2866
3450
|
var installReferrerManager = new InstallReferrerManager();
|
|
2867
3451
|
|
|
2868
3452
|
// src/domains/session/SessionManager.ts
|
|
2869
|
-
var
|
|
2870
|
-
|
|
2871
|
-
// src/domains/session/SessionError.ts
|
|
2872
|
-
var SessionError = class extends PaywalloError {
|
|
2873
|
-
constructor(code, message) {
|
|
2874
|
-
super("session", code, message);
|
|
2875
|
-
this.name = "SessionError";
|
|
2876
|
-
}
|
|
2877
|
-
};
|
|
2878
|
-
var SESSION_ERROR_CODES = {
|
|
2879
|
-
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
2880
|
-
START_FAILED: "SESSION_START_FAILED",
|
|
2881
|
-
END_FAILED: "SESSION_END_FAILED",
|
|
2882
|
-
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
2883
|
-
};
|
|
3453
|
+
var import_react_native12 = require("react-native");
|
|
2884
3454
|
|
|
2885
3455
|
// src/domains/session/sessionTracking.ts
|
|
2886
3456
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
@@ -3048,7 +3618,7 @@ var SessionManager = class {
|
|
|
3048
3618
|
if (this.appStateSubscription) {
|
|
3049
3619
|
this.appStateSubscription.remove();
|
|
3050
3620
|
}
|
|
3051
|
-
this.appStateSubscription =
|
|
3621
|
+
this.appStateSubscription = import_react_native12.AppState.addEventListener("change", this.handleAppStateChange);
|
|
3052
3622
|
}
|
|
3053
3623
|
async handleForeground() {
|
|
3054
3624
|
if (this.backgroundTimestamp) {
|
|
@@ -3109,10 +3679,7 @@ var SessionManager = class {
|
|
|
3109
3679
|
);
|
|
3110
3680
|
}
|
|
3111
3681
|
}
|
|
3112
|
-
log(...
|
|
3113
|
-
if (this.debug) {
|
|
3114
|
-
console.warn("[Paywallo Session]", ...args);
|
|
3115
|
-
}
|
|
3682
|
+
log(..._args) {
|
|
3116
3683
|
}
|
|
3117
3684
|
};
|
|
3118
3685
|
var sessionManager = new SessionManager();
|
|
@@ -3147,7 +3714,7 @@ var InstallTracker = class {
|
|
|
3147
3714
|
}
|
|
3148
3715
|
let deviceData = {};
|
|
3149
3716
|
try {
|
|
3150
|
-
const screen =
|
|
3717
|
+
const screen = import_react_native13.Dimensions.get("screen");
|
|
3151
3718
|
const screenWidth = Math.round(screen.width ?? 0);
|
|
3152
3719
|
const screenHeight = Math.round(screen.height ?? 0);
|
|
3153
3720
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
@@ -3167,9 +3734,6 @@ var InstallTracker = class {
|
|
|
3167
3734
|
locale
|
|
3168
3735
|
};
|
|
3169
3736
|
} catch {
|
|
3170
|
-
if (this.debug) {
|
|
3171
|
-
console.warn("[Paywallo] Failed to collect device data for install event");
|
|
3172
|
-
}
|
|
3173
3737
|
}
|
|
3174
3738
|
const [fbAnonId, referrer, adIds] = await Promise.all([
|
|
3175
3739
|
facebookIdManager.getAnonId(),
|
|
@@ -3179,7 +3743,7 @@ var InstallTracker = class {
|
|
|
3179
3743
|
try {
|
|
3180
3744
|
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3181
3745
|
installedAt,
|
|
3182
|
-
platform:
|
|
3746
|
+
platform: import_react_native13.Platform.OS,
|
|
3183
3747
|
...sessionId && { sessionId },
|
|
3184
3748
|
...deviceData,
|
|
3185
3749
|
...fbAnonId && { fbAnonId },
|
|
@@ -3200,13 +3764,7 @@ var InstallTracker = class {
|
|
|
3200
3764
|
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3201
3765
|
} catch {
|
|
3202
3766
|
}
|
|
3203
|
-
|
|
3204
|
-
console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
|
|
3205
|
-
}
|
|
3206
|
-
} catch (error) {
|
|
3207
|
-
if (this.debug) {
|
|
3208
|
-
console.warn("[Paywallo] Failed to track install:", error);
|
|
3209
|
-
}
|
|
3767
|
+
} catch {
|
|
3210
3768
|
}
|
|
3211
3769
|
}
|
|
3212
3770
|
};
|
|
@@ -3221,6 +3779,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3221
3779
|
this.config = null;
|
|
3222
3780
|
this.apiClient = null;
|
|
3223
3781
|
this.initPromise = null;
|
|
3782
|
+
this.readyPromise = null;
|
|
3783
|
+
this.resolveReady = null;
|
|
3224
3784
|
this.paywallPresenter = null;
|
|
3225
3785
|
this.campaignPresenter = null;
|
|
3226
3786
|
this.subscriptionGetter = null;
|
|
@@ -3230,29 +3790,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3230
3790
|
this.pendingConfig = null;
|
|
3231
3791
|
this.networkCleanup = null;
|
|
3232
3792
|
this.initAttempts = 0;
|
|
3793
|
+
this.autoPreloadedPlacement = null;
|
|
3794
|
+
this.autoPreloadPlacementPromise = null;
|
|
3795
|
+
this.sessionFlagsMap = /* @__PURE__ */ new Map();
|
|
3233
3796
|
}
|
|
3234
3797
|
async init(config) {
|
|
3235
|
-
|
|
3798
|
+
if (!this.readyPromise) {
|
|
3799
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3800
|
+
this.resolveReady = resolve;
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3236
3803
|
if (this.config && this.apiClient) {
|
|
3237
|
-
console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3238
3804
|
return;
|
|
3239
3805
|
}
|
|
3240
3806
|
if (this.initPromise) {
|
|
3241
|
-
console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3242
3807
|
await this.initPromise;
|
|
3243
|
-
console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3244
3808
|
return;
|
|
3245
3809
|
}
|
|
3246
3810
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3247
|
-
console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3248
3811
|
this.pendingConfig = config;
|
|
3249
3812
|
this.initAttempts = 0;
|
|
3250
3813
|
this.initPromise = this.initWithRetry(config);
|
|
3251
3814
|
try {
|
|
3252
3815
|
await this.initPromise;
|
|
3253
|
-
console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3254
3816
|
} catch (error) {
|
|
3255
|
-
console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3256
3817
|
this.initPromise = null;
|
|
3257
3818
|
this.config = null;
|
|
3258
3819
|
this.apiClient = null;
|
|
@@ -3261,6 +3822,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3261
3822
|
throw error;
|
|
3262
3823
|
}
|
|
3263
3824
|
}
|
|
3825
|
+
isReady() {
|
|
3826
|
+
return this.config !== null && this.apiClient !== null;
|
|
3827
|
+
}
|
|
3828
|
+
waitUntilReady() {
|
|
3829
|
+
if (this.isReady()) return Promise.resolve();
|
|
3830
|
+
if (!this.readyPromise) {
|
|
3831
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3832
|
+
this.resolveReady = resolve;
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
3835
|
+
return this.readyPromise;
|
|
3836
|
+
}
|
|
3264
3837
|
async initWithRetry(config) {
|
|
3265
3838
|
while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
|
|
3266
3839
|
try {
|
|
@@ -3275,7 +3848,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3275
3848
|
this.initAttempts++;
|
|
3276
3849
|
if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
|
|
3277
3850
|
const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
|
|
3278
|
-
if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
|
|
3279
3851
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3280
3852
|
}
|
|
3281
3853
|
}
|
|
@@ -3286,11 +3858,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3286
3858
|
this.networkCleanup = networkMonitor.addListener((state) => {
|
|
3287
3859
|
if (state !== "online") return;
|
|
3288
3860
|
if (this.config && this.apiClient) return;
|
|
3289
|
-
if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
|
|
3290
3861
|
this.initPromise = this.initWithRetry(config);
|
|
3291
|
-
this.initPromise.
|
|
3292
|
-
if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
|
|
3293
|
-
}).catch(() => {
|
|
3862
|
+
this.initPromise.catch(() => {
|
|
3294
3863
|
this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
|
|
3295
3864
|
});
|
|
3296
3865
|
});
|
|
@@ -3319,38 +3888,79 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3319
3888
|
} catch {
|
|
3320
3889
|
}
|
|
3321
3890
|
}
|
|
3891
|
+
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
3892
|
+
try {
|
|
3893
|
+
const results = await apiClient.evaluateFlags(keys, distinctId);
|
|
3894
|
+
this.sessionFlagsMap.clear();
|
|
3895
|
+
for (const key of keys) {
|
|
3896
|
+
this.sessionFlagsMap.set(key, key in results ? results[key] : null);
|
|
3897
|
+
}
|
|
3898
|
+
} catch (error) {
|
|
3899
|
+
for (const key of keys) {
|
|
3900
|
+
this.sessionFlagsMap.set(key, null);
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3322
3904
|
async doInit(config) {
|
|
3323
3905
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3324
3906
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3325
|
-
console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3326
3907
|
await Promise.all([
|
|
3327
3908
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3328
3909
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3329
3910
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3330
3911
|
)
|
|
3331
3912
|
]);
|
|
3332
|
-
console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3333
|
-
console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3334
3913
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3335
3914
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3336
3915
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3337
|
-
|
|
3338
|
-
|
|
3916
|
+
subscriptionManager.init({
|
|
3917
|
+
serverUrl: this.apiClient.getBaseUrl(),
|
|
3918
|
+
appKey: config.appKey,
|
|
3919
|
+
cacheTTL: config.subscriptionCacheTTL,
|
|
3920
|
+
debug: config.debug
|
|
3921
|
+
});
|
|
3339
3922
|
await Promise.all([
|
|
3340
3923
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3341
3924
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3342
3925
|
]);
|
|
3343
3926
|
const distinctId = identityManager.getDistinctId();
|
|
3344
|
-
console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3345
3927
|
if (config.autoStartSession !== false) {
|
|
3346
3928
|
sessionManager.startSession().catch(() => {
|
|
3347
|
-
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
3348
3929
|
});
|
|
3349
3930
|
}
|
|
3350
3931
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3351
3932
|
});
|
|
3933
|
+
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3934
|
+
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3935
|
+
}
|
|
3352
3936
|
this.config = config;
|
|
3353
|
-
|
|
3937
|
+
if (this.resolveReady) {
|
|
3938
|
+
this.resolveReady();
|
|
3939
|
+
this.resolveReady = null;
|
|
3940
|
+
}
|
|
3941
|
+
if (config.debug) console.info("[Paywallo INIT] complete");
|
|
3942
|
+
if (this.apiClient) {
|
|
3943
|
+
const apiClientRef = this.apiClient;
|
|
3944
|
+
if (config.autoPreloadCampaign) {
|
|
3945
|
+
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
3946
|
+
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
3947
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
3948
|
+
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
3949
|
+
});
|
|
3950
|
+
} else {
|
|
3951
|
+
this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
|
|
3952
|
+
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
3953
|
+
if (placement) {
|
|
3954
|
+
this.autoPreloadedPlacement = placement;
|
|
3955
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
3956
|
+
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
3957
|
+
});
|
|
3958
|
+
return placement;
|
|
3959
|
+
}
|
|
3960
|
+
return null;
|
|
3961
|
+
}).catch(() => null);
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3354
3964
|
}
|
|
3355
3965
|
async identify(options) {
|
|
3356
3966
|
this.ensureInitialized();
|
|
@@ -3360,7 +3970,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3360
3970
|
const apiClient = this.getApiClientOrThrow();
|
|
3361
3971
|
const distinctId = identityManager.getDistinctId();
|
|
3362
3972
|
if (!distinctId) {
|
|
3363
|
-
console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3364
3973
|
return;
|
|
3365
3974
|
}
|
|
3366
3975
|
if (!isValidEventName(eventName)) {
|
|
@@ -3368,25 +3977,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3368
3977
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
3369
3978
|
}
|
|
3370
3979
|
const sessionId = sessionManager.getSessionId();
|
|
3371
|
-
if (eventName === "$purchase_completed") {
|
|
3372
|
-
console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3373
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3374
|
-
productId: options?.properties?.productId,
|
|
3375
|
-
priceUsd: options?.properties?.priceUsd,
|
|
3376
|
-
price: options?.properties?.price,
|
|
3377
|
-
currency: options?.properties?.currency,
|
|
3378
|
-
placement: options?.properties?.placement
|
|
3379
|
-
}));
|
|
3380
|
-
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3381
|
-
console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3382
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3383
|
-
placement: options?.properties?.placement,
|
|
3384
|
-
variantKey: options?.properties?.variantKey,
|
|
3385
|
-
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3386
|
-
}));
|
|
3387
|
-
} else {
|
|
3388
|
-
console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3389
|
-
}
|
|
3390
3980
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3391
3981
|
...identityManager.getProperties(),
|
|
3392
3982
|
...options?.properties,
|
|
@@ -3397,7 +3987,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3397
3987
|
const apiClient = this.getApiClientOrThrow();
|
|
3398
3988
|
const distinctId = identityManager.getDistinctId();
|
|
3399
3989
|
if (!distinctId) {
|
|
3400
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3401
3990
|
return;
|
|
3402
3991
|
}
|
|
3403
3992
|
if (!Number.isInteger(index) || index < 0) {
|
|
@@ -3421,7 +4010,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3421
4010
|
const apiClient = this.getApiClientOrThrow();
|
|
3422
4011
|
const distinctId = identityManager.getDistinctId();
|
|
3423
4012
|
if (!distinctId) {
|
|
3424
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3425
4013
|
return;
|
|
3426
4014
|
}
|
|
3427
4015
|
if (!actionName || typeof actionName !== "string") {
|
|
@@ -3436,22 +4024,32 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3436
4024
|
...sessionId && { sessionId }
|
|
3437
4025
|
});
|
|
3438
4026
|
}
|
|
4027
|
+
async getVariantCached(flagKey, defaultValue) {
|
|
4028
|
+
this.ensureInitialized();
|
|
4029
|
+
const apiClient = this.getApiClientOrThrow();
|
|
4030
|
+
const distinctId = identityManager.getDistinctId();
|
|
4031
|
+
const result = await apiClient.getVariantFromCache(flagKey, distinctId);
|
|
4032
|
+
if (result) return result;
|
|
4033
|
+
void apiClient.getVariant(flagKey, distinctId).catch(() => {
|
|
4034
|
+
});
|
|
4035
|
+
return { variant: defaultValue ?? null, payload: void 0 };
|
|
4036
|
+
}
|
|
3439
4037
|
async getVariant(flagKey) {
|
|
3440
4038
|
try {
|
|
3441
4039
|
const distinctId = identityManager.getDistinctId();
|
|
3442
4040
|
if (!distinctId) {
|
|
3443
|
-
console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3444
4041
|
return { variant: null };
|
|
3445
4042
|
}
|
|
3446
|
-
|
|
3447
|
-
console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3448
|
-
return result;
|
|
4043
|
+
return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3449
4044
|
} catch (error) {
|
|
3450
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
3451
4045
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
3452
4046
|
return { variant: null };
|
|
3453
4047
|
}
|
|
3454
4048
|
}
|
|
4049
|
+
async evaluateFlags(keys) {
|
|
4050
|
+
this.ensureInitialized();
|
|
4051
|
+
return this.getApiClientOrThrow().evaluateFlags(keys, identityManager.getDistinctId());
|
|
4052
|
+
}
|
|
3455
4053
|
async getConditionalFlag(flagKey, context) {
|
|
3456
4054
|
try {
|
|
3457
4055
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3459,7 +4057,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3459
4057
|
const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
|
|
3460
4058
|
return result.value;
|
|
3461
4059
|
} catch (error) {
|
|
3462
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
|
|
3463
4060
|
if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
|
|
3464
4061
|
return false;
|
|
3465
4062
|
}
|
|
@@ -3477,18 +4074,22 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3477
4074
|
}
|
|
3478
4075
|
async presentCampaign(placement, context) {
|
|
3479
4076
|
this.ensureInitialized();
|
|
3480
|
-
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, this.paywallPresenter, this.campaignPresenter, context);
|
|
4077
|
+
return campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
3481
4078
|
}
|
|
3482
4079
|
async hasActiveSubscription() {
|
|
3483
4080
|
this.ensureInitialized();
|
|
3484
4081
|
try {
|
|
3485
|
-
if (this.activeChecker)
|
|
4082
|
+
if (this.activeChecker) {
|
|
4083
|
+
return await this.activeChecker();
|
|
4084
|
+
}
|
|
3486
4085
|
const distinctId = identityManager.getDistinctId();
|
|
3487
|
-
if (!distinctId || !this.apiClient)
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
4086
|
+
if (!distinctId || !this.apiClient) {
|
|
4087
|
+
return false;
|
|
4088
|
+
}
|
|
4089
|
+
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
4090
|
+
return status.hasActiveSubscription;
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
3492
4093
|
return false;
|
|
3493
4094
|
}
|
|
3494
4095
|
}
|
|
@@ -3502,7 +4103,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3502
4103
|
if (!status.subscription) return null;
|
|
3503
4104
|
return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
|
|
3504
4105
|
} catch {
|
|
3505
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
|
|
3506
4106
|
return null;
|
|
3507
4107
|
}
|
|
3508
4108
|
}
|
|
@@ -3513,19 +4113,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3513
4113
|
}
|
|
3514
4114
|
async reset() {
|
|
3515
4115
|
await identityManager.reset();
|
|
4116
|
+
await subscriptionCache.invalidateAll();
|
|
3516
4117
|
}
|
|
3517
4118
|
async fullReset() {
|
|
3518
4119
|
const wasDebug = this.config?.debug ?? false;
|
|
3519
4120
|
await sessionManager.endSession();
|
|
3520
4121
|
await identityManager.reset();
|
|
4122
|
+
await subscriptionCache.invalidateAll();
|
|
3521
4123
|
queueProcessor.dispose();
|
|
3522
4124
|
offlineQueue.dispose();
|
|
3523
4125
|
networkMonitor.dispose();
|
|
3524
4126
|
campaignGateService.clearPreloaded();
|
|
3525
4127
|
this.cleanupNetworkRecovery();
|
|
4128
|
+
this.sessionFlagsMap.clear();
|
|
3526
4129
|
this.apiClient = null;
|
|
3527
4130
|
this.config = null;
|
|
3528
4131
|
this.initPromise = null;
|
|
4132
|
+
this.readyPromise = null;
|
|
4133
|
+
this.resolveReady = null;
|
|
3529
4134
|
this.pendingConfig = null;
|
|
3530
4135
|
this.initAttempts = 0;
|
|
3531
4136
|
this.paywallPresenter = null;
|
|
@@ -3534,7 +4139,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3534
4139
|
this.activeChecker = null;
|
|
3535
4140
|
this.restoreHandler = null;
|
|
3536
4141
|
this.lastOnboardingStepTimestamp = null;
|
|
3537
|
-
|
|
4142
|
+
this.autoPreloadedPlacement = null;
|
|
4143
|
+
this.autoPreloadPlacementPromise = null;
|
|
4144
|
+
}
|
|
4145
|
+
getSessionFlag(key) {
|
|
4146
|
+
if (!this.config || !this.apiClient) return null;
|
|
4147
|
+
const value = this.sessionFlagsMap.get(key);
|
|
4148
|
+
return value !== void 0 ? value : null;
|
|
3538
4149
|
}
|
|
3539
4150
|
getConfig() {
|
|
3540
4151
|
return this.config;
|
|
@@ -3623,6 +4234,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3623
4234
|
getPreloadedCampaign(placement) {
|
|
3624
4235
|
return campaignGateService.getPreloadedCampaign(placement);
|
|
3625
4236
|
}
|
|
4237
|
+
isPreloaded(placement) {
|
|
4238
|
+
return campaignGateService.getPreloadedCampaign(placement) !== null;
|
|
4239
|
+
}
|
|
4240
|
+
getAutoPreloadedPlacement() {
|
|
4241
|
+
return this.autoPreloadedPlacement;
|
|
4242
|
+
}
|
|
4243
|
+
waitForAutoPreloadedPlacement() {
|
|
4244
|
+
return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
|
|
4245
|
+
}
|
|
3626
4246
|
isOnline() {
|
|
3627
4247
|
return networkMonitor.isOnline();
|
|
3628
4248
|
}
|
|
@@ -3652,48 +4272,6 @@ var PaywalloClient = new PaywalloClientClass();
|
|
|
3652
4272
|
var import_react9 = require("react");
|
|
3653
4273
|
var PaywalloContext = (0, import_react9.createContext)(null);
|
|
3654
4274
|
|
|
3655
|
-
// src/utils/localization.ts
|
|
3656
|
-
var import_react_native12 = require("react-native");
|
|
3657
|
-
var currentLanguage = "pt-BR";
|
|
3658
|
-
var defaultLanguage = "pt-BR";
|
|
3659
|
-
function setCurrentLanguage(language) {
|
|
3660
|
-
currentLanguage = language;
|
|
3661
|
-
}
|
|
3662
|
-
function setDefaultLanguage(language) {
|
|
3663
|
-
defaultLanguage = language;
|
|
3664
|
-
}
|
|
3665
|
-
function getCurrentLanguage() {
|
|
3666
|
-
return currentLanguage;
|
|
3667
|
-
}
|
|
3668
|
-
function getDefaultLanguage() {
|
|
3669
|
-
return defaultLanguage;
|
|
3670
|
-
}
|
|
3671
|
-
function getLocalizedString(text) {
|
|
3672
|
-
if (!text) return "";
|
|
3673
|
-
if (typeof text === "string") return text;
|
|
3674
|
-
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
3675
|
-
}
|
|
3676
|
-
function detectDeviceLanguage() {
|
|
3677
|
-
try {
|
|
3678
|
-
if (import_react_native12.Platform.OS === "ios") {
|
|
3679
|
-
const settings = import_react_native12.NativeModules.SettingsManager?.settings;
|
|
3680
|
-
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
3681
|
-
}
|
|
3682
|
-
return import_react_native12.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
|
|
3683
|
-
} catch {
|
|
3684
|
-
return "en-US";
|
|
3685
|
-
}
|
|
3686
|
-
}
|
|
3687
|
-
function initLocalization(options) {
|
|
3688
|
-
if (options?.defaultLanguage) {
|
|
3689
|
-
defaultLanguage = options.defaultLanguage;
|
|
3690
|
-
}
|
|
3691
|
-
if (options?.detectDevice !== false) {
|
|
3692
|
-
const deviceLanguage = detectDeviceLanguage();
|
|
3693
|
-
currentLanguage = deviceLanguage;
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
|
|
3697
4275
|
// src/hooks/usePaywallo.ts
|
|
3698
4276
|
var import_react10 = require("react");
|
|
3699
4277
|
function usePaywallo() {
|
|
@@ -3719,7 +4297,8 @@ function mapToIAPProduct(product) {
|
|
|
3719
4297
|
type: product.type === "subscription" ? "subs" : "inapp",
|
|
3720
4298
|
subscriptionPeriodAndroid: product.subscriptionPeriod,
|
|
3721
4299
|
introductoryPrice: product.introductoryPrice,
|
|
3722
|
-
freeTrialPeriodAndroid: product.freeTrialPeriod
|
|
4300
|
+
freeTrialPeriodAndroid: product.freeTrialPeriod,
|
|
4301
|
+
trialDays: product.trialDays ?? 0
|
|
3723
4302
|
};
|
|
3724
4303
|
}
|
|
3725
4304
|
function mapToFormattedProduct(product) {
|
|
@@ -3734,6 +4313,7 @@ function mapToFormattedProduct(product) {
|
|
|
3734
4313
|
subscriptionPeriod: product.subscriptionPeriod,
|
|
3735
4314
|
introductoryPrice: product.introductoryPrice,
|
|
3736
4315
|
freeTrialPeriod: product.freeTrialPeriod,
|
|
4316
|
+
trialDays: product.trialDays ?? 0,
|
|
3737
4317
|
pricePerMonth: product.pricePerMonth,
|
|
3738
4318
|
pricePerWeek: product.pricePerWeek,
|
|
3739
4319
|
pricePerDay: product.pricePerDay
|
|
@@ -3815,27 +4395,37 @@ function usePurchase() {
|
|
|
3815
4395
|
setError(null);
|
|
3816
4396
|
try {
|
|
3817
4397
|
const iapService = getIAPService();
|
|
3818
|
-
const result = await iapService.purchase(productId
|
|
4398
|
+
const result = await iapService.purchase(productId, {
|
|
4399
|
+
onValidating: () => setState("validating")
|
|
4400
|
+
});
|
|
3819
4401
|
if (result.success && result.purchase) {
|
|
3820
4402
|
setState("idle");
|
|
3821
4403
|
return {
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
4404
|
+
status: "success",
|
|
4405
|
+
purchase: {
|
|
4406
|
+
productId: result.purchase.productId,
|
|
4407
|
+
transactionId: result.purchase.transactionId,
|
|
4408
|
+
transactionDate: result.purchase.transactionDate,
|
|
4409
|
+
transactionReceipt: result.purchase.receipt
|
|
4410
|
+
}
|
|
3826
4411
|
};
|
|
3827
4412
|
}
|
|
3828
4413
|
if (result.error) {
|
|
3829
|
-
|
|
3830
|
-
setError(purchaseError);
|
|
4414
|
+
setError(result.error);
|
|
3831
4415
|
setState("error");
|
|
3832
|
-
|
|
4416
|
+
return { status: "failed", error: result.error };
|
|
3833
4417
|
}
|
|
3834
4418
|
setState("idle");
|
|
3835
|
-
|
|
4419
|
+
return { status: "cancelled" };
|
|
3836
4420
|
} catch (err) {
|
|
4421
|
+
const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
|
|
4422
|
+
if (purchaseError.userCancelled) {
|
|
4423
|
+
setState("idle");
|
|
4424
|
+
return { status: "cancelled" };
|
|
4425
|
+
}
|
|
4426
|
+
setError(purchaseError);
|
|
3837
4427
|
setState("error");
|
|
3838
|
-
|
|
4428
|
+
return { status: "failed", error: purchaseError };
|
|
3839
4429
|
}
|
|
3840
4430
|
}, []);
|
|
3841
4431
|
const restore = (0, import_react12.useCallback)(async () => {
|
|
@@ -3864,226 +4454,14 @@ function usePurchase() {
|
|
|
3864
4454
|
restore,
|
|
3865
4455
|
error,
|
|
3866
4456
|
isLoading: state === "loading",
|
|
3867
|
-
isPurchasing: state === "purchasing",
|
|
4457
|
+
isPurchasing: state === "purchasing" || state === "validating",
|
|
4458
|
+
isValidating: state === "validating",
|
|
3868
4459
|
isRestoring: state === "restoring"
|
|
3869
4460
|
};
|
|
3870
4461
|
}
|
|
3871
4462
|
|
|
3872
4463
|
// src/hooks/useSubscription.ts
|
|
3873
4464
|
var import_react13 = require("react");
|
|
3874
|
-
|
|
3875
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3876
|
-
var import_react_native13 = require("react-native");
|
|
3877
|
-
|
|
3878
|
-
// src/domains/subscription/SubscriptionCache.ts
|
|
3879
|
-
var CACHE_KEY = "subscription_cache";
|
|
3880
|
-
var DEFAULT_TTL = 5 * 60 * 1e3;
|
|
3881
|
-
var SubscriptionCache = class {
|
|
3882
|
-
constructor(ttl = DEFAULT_TTL) {
|
|
3883
|
-
this.memoryCache = null;
|
|
3884
|
-
this.ttl = ttl;
|
|
3885
|
-
this.storage = new SecureStorage();
|
|
3886
|
-
}
|
|
3887
|
-
async get() {
|
|
3888
|
-
if (this.memoryCache && !this.isExpired(this.memoryCache)) {
|
|
3889
|
-
return this.memoryCache;
|
|
3890
|
-
}
|
|
3891
|
-
try {
|
|
3892
|
-
const stored = await this.storage.get(CACHE_KEY);
|
|
3893
|
-
if (!stored) {
|
|
3894
|
-
return null;
|
|
3895
|
-
}
|
|
3896
|
-
const cached = JSON.parse(stored);
|
|
3897
|
-
if (cached.data.subscription?.expiresAt) {
|
|
3898
|
-
cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
|
|
3899
|
-
}
|
|
3900
|
-
if (cached.data.subscription?.originalPurchaseDate) {
|
|
3901
|
-
cached.data.subscription.originalPurchaseDate = new Date(
|
|
3902
|
-
cached.data.subscription.originalPurchaseDate
|
|
3903
|
-
);
|
|
3904
|
-
}
|
|
3905
|
-
if (cached.data.subscription?.latestPurchaseDate) {
|
|
3906
|
-
cached.data.subscription.latestPurchaseDate = new Date(
|
|
3907
|
-
cached.data.subscription.latestPurchaseDate
|
|
3908
|
-
);
|
|
3909
|
-
}
|
|
3910
|
-
if (cached.data.subscription?.cancellationDate) {
|
|
3911
|
-
cached.data.subscription.cancellationDate = new Date(
|
|
3912
|
-
cached.data.subscription.cancellationDate
|
|
3913
|
-
);
|
|
3914
|
-
}
|
|
3915
|
-
if (cached.data.subscription?.gracePeriodExpiresAt) {
|
|
3916
|
-
cached.data.subscription.gracePeriodExpiresAt = new Date(
|
|
3917
|
-
cached.data.subscription.gracePeriodExpiresAt
|
|
3918
|
-
);
|
|
3919
|
-
}
|
|
3920
|
-
cached.isStale = this.isExpired(cached);
|
|
3921
|
-
if (!cached.isStale) {
|
|
3922
|
-
this.memoryCache = cached;
|
|
3923
|
-
}
|
|
3924
|
-
return cached;
|
|
3925
|
-
} catch {
|
|
3926
|
-
return null;
|
|
3927
|
-
}
|
|
3928
|
-
}
|
|
3929
|
-
async set(data) {
|
|
3930
|
-
const cached = {
|
|
3931
|
-
data,
|
|
3932
|
-
cachedAt: Date.now(),
|
|
3933
|
-
isStale: false
|
|
3934
|
-
};
|
|
3935
|
-
this.memoryCache = cached;
|
|
3936
|
-
try {
|
|
3937
|
-
await this.storage.set(CACHE_KEY, JSON.stringify(cached));
|
|
3938
|
-
} catch {
|
|
3939
|
-
}
|
|
3940
|
-
}
|
|
3941
|
-
async invalidate() {
|
|
3942
|
-
this.memoryCache = null;
|
|
3943
|
-
try {
|
|
3944
|
-
await this.storage.remove(CACHE_KEY);
|
|
3945
|
-
} catch {
|
|
3946
|
-
}
|
|
3947
|
-
}
|
|
3948
|
-
isExpired(cached) {
|
|
3949
|
-
return Date.now() - cached.cachedAt > this.ttl;
|
|
3950
|
-
}
|
|
3951
|
-
setTTL(ttl) {
|
|
3952
|
-
this.ttl = ttl;
|
|
3953
|
-
}
|
|
3954
|
-
};
|
|
3955
|
-
|
|
3956
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
3957
|
-
var SubscriptionManagerClass = class {
|
|
3958
|
-
constructor() {
|
|
3959
|
-
this.config = null;
|
|
3960
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
3961
|
-
this.userId = null;
|
|
3962
|
-
this.cache = new SubscriptionCache();
|
|
3963
|
-
}
|
|
3964
|
-
init(config) {
|
|
3965
|
-
this.config = config;
|
|
3966
|
-
if (config.cacheTTL) {
|
|
3967
|
-
this.cache.setTTL(config.cacheTTL);
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
setUserId(userId) {
|
|
3971
|
-
if (this.userId !== userId) {
|
|
3972
|
-
this.userId = userId;
|
|
3973
|
-
void this.cache.invalidate();
|
|
3974
|
-
}
|
|
3975
|
-
}
|
|
3976
|
-
async hasActiveSubscription(forceRefresh = false) {
|
|
3977
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3978
|
-
return status.hasActiveSubscription;
|
|
3979
|
-
}
|
|
3980
|
-
async getSubscription(forceRefresh = false) {
|
|
3981
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3982
|
-
return status.subscription;
|
|
3983
|
-
}
|
|
3984
|
-
async getEntitlements(forceRefresh = false) {
|
|
3985
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
3986
|
-
return status.entitlements;
|
|
3987
|
-
}
|
|
3988
|
-
async getSubscriptionStatus(forceRefresh = false) {
|
|
3989
|
-
if (!this.config) {
|
|
3990
|
-
return this.getEmptyStatus();
|
|
3991
|
-
}
|
|
3992
|
-
if (!forceRefresh) {
|
|
3993
|
-
const cached = await this.cache.get();
|
|
3994
|
-
if (cached && !cached.isStale) {
|
|
3995
|
-
return cached.data;
|
|
3996
|
-
}
|
|
3997
|
-
}
|
|
3998
|
-
try {
|
|
3999
|
-
const status = await this.fetchSubscriptionStatus();
|
|
4000
|
-
await this.cache.set(status);
|
|
4001
|
-
this.notifyListeners(status);
|
|
4002
|
-
return status;
|
|
4003
|
-
} catch (error) {
|
|
4004
|
-
this.log("Failed to fetch subscription status", error);
|
|
4005
|
-
const cached = await this.cache.get();
|
|
4006
|
-
if (cached) {
|
|
4007
|
-
return cached.data;
|
|
4008
|
-
}
|
|
4009
|
-
return this.getEmptyStatus();
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
async restorePurchases() {
|
|
4013
|
-
if (!this.config) {
|
|
4014
|
-
throw new SessionError(
|
|
4015
|
-
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
4016
|
-
"SubscriptionManager not initialized"
|
|
4017
|
-
);
|
|
4018
|
-
}
|
|
4019
|
-
await this.cache.invalidate();
|
|
4020
|
-
return this.getSubscriptionStatus(true);
|
|
4021
|
-
}
|
|
4022
|
-
async fetchSubscriptionStatus() {
|
|
4023
|
-
if (!this.config) {
|
|
4024
|
-
return this.getEmptyStatus();
|
|
4025
|
-
}
|
|
4026
|
-
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
4027
|
-
if (this.userId) {
|
|
4028
|
-
url.searchParams.set("userId", this.userId);
|
|
4029
|
-
}
|
|
4030
|
-
url.searchParams.set("platform", import_react_native13.Platform.OS);
|
|
4031
|
-
const response = await fetch(url.toString(), {
|
|
4032
|
-
method: "GET",
|
|
4033
|
-
headers: {
|
|
4034
|
-
"Content-Type": "application/json",
|
|
4035
|
-
"X-App-Key": this.config.appKey
|
|
4036
|
-
}
|
|
4037
|
-
});
|
|
4038
|
-
if (!response.ok) {
|
|
4039
|
-
throw new SessionError(
|
|
4040
|
-
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
4041
|
-
`Failed to fetch subscription status: ${response.status}`
|
|
4042
|
-
);
|
|
4043
|
-
}
|
|
4044
|
-
const data = await response.json();
|
|
4045
|
-
if (data.subscription) {
|
|
4046
|
-
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
4047
|
-
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
4048
|
-
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
4049
|
-
if (data.subscription.cancellationDate) {
|
|
4050
|
-
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
4051
|
-
}
|
|
4052
|
-
if (data.subscription.gracePeriodExpiresAt) {
|
|
4053
|
-
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
return data;
|
|
4057
|
-
}
|
|
4058
|
-
getEmptyStatus() {
|
|
4059
|
-
return {
|
|
4060
|
-
hasActiveSubscription: false,
|
|
4061
|
-
subscription: null,
|
|
4062
|
-
entitlements: []
|
|
4063
|
-
};
|
|
4064
|
-
}
|
|
4065
|
-
addListener(listener) {
|
|
4066
|
-
this.listeners.add(listener);
|
|
4067
|
-
return () => this.listeners.delete(listener);
|
|
4068
|
-
}
|
|
4069
|
-
notifyListeners(status) {
|
|
4070
|
-
for (const listener of this.listeners) {
|
|
4071
|
-
listener(status);
|
|
4072
|
-
}
|
|
4073
|
-
}
|
|
4074
|
-
async onPurchaseComplete() {
|
|
4075
|
-
await this.cache.invalidate();
|
|
4076
|
-
await this.getSubscriptionStatus(true);
|
|
4077
|
-
}
|
|
4078
|
-
log(message, data) {
|
|
4079
|
-
if (this.config?.debug) {
|
|
4080
|
-
console.warn(`[SubscriptionManager] ${message}`, data ?? "");
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
};
|
|
4084
|
-
var subscriptionManager = new SubscriptionManagerClass();
|
|
4085
|
-
|
|
4086
|
-
// src/hooks/useSubscription.ts
|
|
4087
4465
|
function useSubscription() {
|
|
4088
4466
|
const [status, setStatus] = (0, import_react13.useState)(null);
|
|
4089
4467
|
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
@@ -4200,10 +4578,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
4200
4578
|
function isCacheEntryValid(entry) {
|
|
4201
4579
|
return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
|
|
4202
4580
|
}
|
|
4203
|
-
function
|
|
4581
|
+
function getCacheEntry(cache, placement) {
|
|
4204
4582
|
const entry = cache.get(placement);
|
|
4205
4583
|
if (!isCacheEntryValid(entry)) return null;
|
|
4206
|
-
cache.delete(placement);
|
|
4207
4584
|
return entry ?? null;
|
|
4208
4585
|
}
|
|
4209
4586
|
|
|
@@ -4216,7 +4593,7 @@ function useCampaignPreload() {
|
|
|
4216
4593
|
[]
|
|
4217
4594
|
);
|
|
4218
4595
|
const consumePreloadedCampaign = (0, import_react14.useCallback)(
|
|
4219
|
-
(placement) =>
|
|
4596
|
+
(placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
|
|
4220
4597
|
[]
|
|
4221
4598
|
);
|
|
4222
4599
|
const preloadCampaign = (0, import_react14.useCallback)(
|
|
@@ -4264,6 +4641,7 @@ function useCampaignPreload() {
|
|
|
4264
4641
|
campaignId: campaign.campaignId,
|
|
4265
4642
|
variantKey: campaign.variantKey
|
|
4266
4643
|
});
|
|
4644
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4267
4645
|
return { success: true };
|
|
4268
4646
|
} catch (error) {
|
|
4269
4647
|
return {
|
|
@@ -4298,7 +4676,6 @@ function useProductLoader() {
|
|
|
4298
4676
|
for (const p of loaded) map.set(p.productId, p);
|
|
4299
4677
|
setProducts(map);
|
|
4300
4678
|
} catch (error) {
|
|
4301
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
|
|
4302
4679
|
} finally {
|
|
4303
4680
|
setIsLoadingProducts(false);
|
|
4304
4681
|
}
|
|
@@ -4308,7 +4685,7 @@ function useProductLoader() {
|
|
|
4308
4685
|
|
|
4309
4686
|
// src/hooks/usePurchaseOrchestrator.ts
|
|
4310
4687
|
var import_react16 = require("react");
|
|
4311
|
-
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
|
|
4688
|
+
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
|
|
4312
4689
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
4313
4690
|
const handlePreloadedPurchase = (0, import_react16.useCallback)(
|
|
4314
4691
|
async (productId) => {
|
|
@@ -4333,17 +4710,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4333
4710
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4334
4711
|
await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4335
4712
|
}
|
|
4713
|
+
invalidateSubscriptionCache();
|
|
4336
4714
|
setShowingPreloadedWebView(false);
|
|
4337
4715
|
clearPreloadedWebView();
|
|
4338
4716
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4339
4717
|
} catch (error) {
|
|
4340
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
|
|
4341
4718
|
setShowingPreloadedWebView(false);
|
|
4342
4719
|
clearPreloadedWebView();
|
|
4343
4720
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4344
4721
|
}
|
|
4345
4722
|
},
|
|
4346
|
-
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
|
|
4723
|
+
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
|
|
4347
4724
|
);
|
|
4348
4725
|
const handlePreloadedRestore = (0, import_react16.useCallback)(async () => {
|
|
4349
4726
|
try {
|
|
@@ -4353,28 +4730,70 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4353
4730
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
4354
4731
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4355
4732
|
}
|
|
4733
|
+
invalidateSubscriptionCache();
|
|
4356
4734
|
setShowingPreloadedWebView(false);
|
|
4357
4735
|
clearPreloadedWebView();
|
|
4358
4736
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4359
4737
|
} catch (error) {
|
|
4360
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
|
|
4361
4738
|
setShowingPreloadedWebView(false);
|
|
4362
4739
|
clearPreloadedWebView();
|
|
4363
4740
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4364
4741
|
}
|
|
4365
|
-
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
|
|
4742
|
+
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
|
|
4366
4743
|
return { handlePreloadedPurchase, handlePreloadedRestore };
|
|
4367
4744
|
}
|
|
4368
4745
|
|
|
4369
4746
|
// src/hooks/useSubscriptionSync.ts
|
|
4370
4747
|
var import_react17 = require("react");
|
|
4371
4748
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4372
|
-
var ACTIVE_STATUSES = ["active", "
|
|
4749
|
+
var ACTIVE_STATUSES = ["active", "grace_period"];
|
|
4750
|
+
function toDomainSubscriptionInfo(sub) {
|
|
4751
|
+
return {
|
|
4752
|
+
id: "",
|
|
4753
|
+
productId: sub.productId,
|
|
4754
|
+
status: sub.status,
|
|
4755
|
+
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4756
|
+
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4757
|
+
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4758
|
+
willRenew: sub.autoRenewEnabled,
|
|
4759
|
+
isTrial: false,
|
|
4760
|
+
isIntroductoryPeriod: false,
|
|
4761
|
+
platform: sub.platform
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4764
|
+
function toDomainResponse(status) {
|
|
4765
|
+
return {
|
|
4766
|
+
hasActiveSubscription: status.hasActiveSubscription,
|
|
4767
|
+
subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
|
|
4768
|
+
entitlements: []
|
|
4769
|
+
};
|
|
4770
|
+
}
|
|
4771
|
+
function isSubscriptionStillActive(sub) {
|
|
4772
|
+
if (!sub) return false;
|
|
4773
|
+
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4774
|
+
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4775
|
+
return true;
|
|
4776
|
+
}
|
|
4777
|
+
async function resolveOfflineFromCache(distinctId) {
|
|
4778
|
+
try {
|
|
4779
|
+
const cached = await subscriptionCache.get(distinctId);
|
|
4780
|
+
if (!cached) return false;
|
|
4781
|
+
const sub = cached.data.subscription;
|
|
4782
|
+
return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
|
|
4783
|
+
} catch {
|
|
4784
|
+
return false;
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
function isActiveCacheEntry(entry) {
|
|
4788
|
+
return isSubscriptionStillActive(entry.data);
|
|
4789
|
+
}
|
|
4373
4790
|
function useSubscriptionSync() {
|
|
4374
4791
|
const [subscription, setSubscription] = (0, import_react17.useState)(null);
|
|
4375
4792
|
const cacheRef = (0, import_react17.useRef)(null);
|
|
4376
4793
|
const invalidateCache = (0, import_react17.useCallback)(() => {
|
|
4377
4794
|
cacheRef.current = null;
|
|
4795
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4796
|
+
if (distinctId) void subscriptionCache.invalidate(distinctId);
|
|
4378
4797
|
}, []);
|
|
4379
4798
|
const fetchAndCache = (0, import_react17.useCallback)(async () => {
|
|
4380
4799
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4387,6 +4806,7 @@ function useSubscriptionSync() {
|
|
|
4387
4806
|
} : null;
|
|
4388
4807
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4389
4808
|
setSubscription(sub);
|
|
4809
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4390
4810
|
return sub;
|
|
4391
4811
|
}, []);
|
|
4392
4812
|
const hasActiveSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4395,10 +4815,28 @@ function useSubscriptionSync() {
|
|
|
4395
4815
|
if (!apiClient || !distinctId) return false;
|
|
4396
4816
|
const cached = cacheRef.current;
|
|
4397
4817
|
if (cached && cached.expiresAt > Date.now()) {
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4818
|
+
return isActiveCacheEntry(cached);
|
|
4819
|
+
}
|
|
4820
|
+
try {
|
|
4821
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4822
|
+
if (persisted && !persisted.isStale) {
|
|
4823
|
+
const persistedSub = persisted.data.subscription;
|
|
4824
|
+
const sub = persistedSub ? {
|
|
4825
|
+
productId: persistedSub.productId,
|
|
4826
|
+
status: persistedSub.status,
|
|
4827
|
+
expiresAt: persistedSub.expiresAt ?? null,
|
|
4828
|
+
platform: persistedSub.platform,
|
|
4829
|
+
autoRenewEnabled: persistedSub.willRenew,
|
|
4830
|
+
inGracePeriod: persistedSub.status === "grace_period"
|
|
4831
|
+
} : null;
|
|
4832
|
+
const stillActive = isSubscriptionStillActive(sub);
|
|
4833
|
+
if (stillActive) {
|
|
4834
|
+
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4835
|
+
setSubscription(sub);
|
|
4836
|
+
return true;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
} catch {
|
|
4402
4840
|
}
|
|
4403
4841
|
try {
|
|
4404
4842
|
const status = await apiClient.getSubscriptionStatus(distinctId);
|
|
@@ -4408,9 +4846,10 @@ function useSubscriptionSync() {
|
|
|
4408
4846
|
} : null;
|
|
4409
4847
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4410
4848
|
setSubscription(sub);
|
|
4849
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4411
4850
|
return status.hasActiveSubscription;
|
|
4412
4851
|
} catch {
|
|
4413
|
-
return
|
|
4852
|
+
return await resolveOfflineFromCache(distinctId);
|
|
4414
4853
|
}
|
|
4415
4854
|
}, []);
|
|
4416
4855
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4422,7 +4861,22 @@ function useSubscriptionSync() {
|
|
|
4422
4861
|
try {
|
|
4423
4862
|
return await fetchAndCache();
|
|
4424
4863
|
} catch {
|
|
4425
|
-
|
|
4864
|
+
try {
|
|
4865
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4866
|
+
if (!persisted?.data.subscription) return null;
|
|
4867
|
+
const s = persisted.data.subscription;
|
|
4868
|
+
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
4869
|
+
return {
|
|
4870
|
+
productId: s.productId,
|
|
4871
|
+
status: normalisedStatus,
|
|
4872
|
+
expiresAt: s.expiresAt ?? null,
|
|
4873
|
+
platform: s.platform,
|
|
4874
|
+
autoRenewEnabled: s.willRenew,
|
|
4875
|
+
inGracePeriod: normalisedStatus === "in_grace_period"
|
|
4876
|
+
};
|
|
4877
|
+
} catch {
|
|
4878
|
+
return null;
|
|
4879
|
+
}
|
|
4426
4880
|
}
|
|
4427
4881
|
}, [fetchAndCache]);
|
|
4428
4882
|
return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
|
|
@@ -4439,13 +4893,38 @@ function PaywalloProvider({ children, config }) {
|
|
|
4439
4893
|
const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
|
|
4440
4894
|
const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
|
|
4441
4895
|
const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4442
|
-
const
|
|
4896
|
+
const autoPreloadTriggeredRef = (0, import_react18.useRef)(false);
|
|
4897
|
+
const preloadedWebViewRef = (0, import_react18.useRef)(preloadedWebView);
|
|
4898
|
+
(0, import_react18.useEffect)(() => {
|
|
4899
|
+
preloadedWebViewRef.current = preloadedWebView;
|
|
4900
|
+
}, [preloadedWebView]);
|
|
4901
|
+
const triggerRepreload = (0, import_react18.useCallback)((placement) => {
|
|
4902
|
+
void PaywalloClient.preloadCampaign(placement).catch(() => {
|
|
4903
|
+
});
|
|
4904
|
+
}, []);
|
|
4905
|
+
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
4906
|
+
const SCREEN_HEIGHT3 = (0, import_react18.useMemo)(() => import_react_native14.Dimensions.get("window").height, []);
|
|
4907
|
+
const slideAnim = (0, import_react18.useRef)(new import_react_native14.Animated.Value(SCREEN_HEIGHT3)).current;
|
|
4908
|
+
const handlePreloadedWebViewReady = (0, import_react18.useCallback)(() => {
|
|
4909
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4910
|
+
console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
4911
|
+
}
|
|
4912
|
+
baseHandlePreloadedWebViewReady();
|
|
4913
|
+
}, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
4914
|
+
const handlePreloadedClose = (0, import_react18.useCallback)(() => {
|
|
4915
|
+
const placement = preloadedWebView?.placement;
|
|
4916
|
+
import_react_native14.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: import_react_native14.Easing.in(import_react_native14.Easing.cubic), useNativeDriver: true }).start(() => {
|
|
4917
|
+
baseHandlePreloadedClose();
|
|
4918
|
+
if (placement) triggerRepreload(placement);
|
|
4919
|
+
});
|
|
4920
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
|
|
4443
4921
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4444
4922
|
preloadedWebView,
|
|
4445
4923
|
resolvePreloadedPaywall,
|
|
4446
4924
|
clearPreloadedWebView,
|
|
4447
4925
|
setShowingPreloadedWebView,
|
|
4448
|
-
presentedAtRef
|
|
4926
|
+
presentedAtRef,
|
|
4927
|
+
invalidateCache
|
|
4449
4928
|
);
|
|
4450
4929
|
const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react18.useState)(false);
|
|
4451
4930
|
const handlePreloadedPurchase = (0, import_react18.useCallback)(async (productId) => {
|
|
@@ -4458,13 +4937,19 @@ function PaywalloProvider({ children, config }) {
|
|
|
4458
4937
|
}, [rawPreloadedPurchase]);
|
|
4459
4938
|
(0, import_react18.useEffect)(() => {
|
|
4460
4939
|
initLocalization({ detectDevice: true });
|
|
4461
|
-
PaywalloClient.init(config).then(() => {
|
|
4940
|
+
PaywalloClient.init(config).then(async () => {
|
|
4462
4941
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
4463
4942
|
setIsInitialized(true);
|
|
4943
|
+
if (!autoPreloadTriggeredRef.current) {
|
|
4944
|
+
autoPreloadTriggeredRef.current = true;
|
|
4945
|
+
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
4946
|
+
if (placement) void preloadCampaign(placement).catch(() => {
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4464
4949
|
}).catch((err) => {
|
|
4465
4950
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
4466
4951
|
});
|
|
4467
|
-
}, [config]);
|
|
4952
|
+
}, [config, preloadCampaign]);
|
|
4468
4953
|
const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
|
|
4469
4954
|
const api = PaywalloClient.getApiClient();
|
|
4470
4955
|
return api ? api.getPaywall(p) : null;
|
|
@@ -4563,28 +5048,54 @@ function PaywalloProvider({ children, config }) {
|
|
|
4563
5048
|
setActivePaywall(null);
|
|
4564
5049
|
}
|
|
4565
5050
|
}, [activePaywall]);
|
|
5051
|
+
const bridgePaywallPresenter = (0, import_react18.useCallback)((placement) => {
|
|
5052
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
5053
|
+
console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
5054
|
+
}
|
|
5055
|
+
if (preloadedWebViewRef.current?.placement === placement) {
|
|
5056
|
+
if (preloadedWebViewRef.current.loaded) {
|
|
5057
|
+
return presentPreloadedWebView();
|
|
5058
|
+
}
|
|
5059
|
+
return new Promise((resolve) => {
|
|
5060
|
+
let elapsed = 0;
|
|
5061
|
+
const poll = () => {
|
|
5062
|
+
if (preloadedWebViewRef.current?.loaded) {
|
|
5063
|
+
resolve(presentPreloadedWebView());
|
|
5064
|
+
return;
|
|
5065
|
+
}
|
|
5066
|
+
elapsed += 100;
|
|
5067
|
+
if (elapsed >= 500) {
|
|
5068
|
+
resolve(presentCampaign(placement));
|
|
5069
|
+
return;
|
|
5070
|
+
}
|
|
5071
|
+
setTimeout(poll, 100);
|
|
5072
|
+
};
|
|
5073
|
+
setTimeout(poll, 100);
|
|
5074
|
+
});
|
|
5075
|
+
}
|
|
5076
|
+
return presentCampaign(placement);
|
|
5077
|
+
}, [presentPreloadedWebView, presentCampaign]);
|
|
4566
5078
|
(0, import_react18.useEffect)(() => {
|
|
4567
5079
|
if (!isInitialized) return;
|
|
4568
5080
|
const onEmergency = async (id) => {
|
|
4569
5081
|
try {
|
|
4570
|
-
await
|
|
5082
|
+
await bridgePaywallPresenter(id);
|
|
4571
5083
|
} catch {
|
|
4572
5084
|
}
|
|
4573
5085
|
};
|
|
4574
|
-
PaywalloClient.registerPaywallPresenter(
|
|
5086
|
+
PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
|
|
4575
5087
|
PaywalloClient.registerSubscriptionGetter(getSubscription);
|
|
4576
5088
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
4577
5089
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
4578
5090
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
4579
|
-
}, [isInitialized,
|
|
4580
|
-
|
|
4581
|
-
if (activePaywall) setPreloadedWebView(null);
|
|
4582
|
-
}, [activePaywall, setPreloadedWebView]);
|
|
5091
|
+
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5092
|
+
const isReady = isInitialized && !isLoadingProducts;
|
|
4583
5093
|
const noOp = (0, import_react18.useCallback)(() => {
|
|
4584
5094
|
}, []);
|
|
4585
5095
|
const contextValue = (0, import_react18.useMemo)(() => ({
|
|
4586
5096
|
config: PaywalloClient.getConfig(),
|
|
4587
5097
|
isInitialized,
|
|
5098
|
+
isReady,
|
|
4588
5099
|
initError,
|
|
4589
5100
|
distinctId,
|
|
4590
5101
|
subscription,
|
|
@@ -4600,6 +5111,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4600
5111
|
refreshProducts
|
|
4601
5112
|
}), [
|
|
4602
5113
|
isInitialized,
|
|
5114
|
+
isReady,
|
|
4603
5115
|
initError,
|
|
4604
5116
|
distinctId,
|
|
4605
5117
|
subscription,
|
|
@@ -4614,20 +5126,16 @@ function PaywalloProvider({ children, config }) {
|
|
|
4614
5126
|
getPaywallConfig,
|
|
4615
5127
|
refreshProducts
|
|
4616
5128
|
]);
|
|
4617
|
-
|
|
4618
|
-
const slideAnim = (0, import_react18.useRef)(new import_react_native14.Animated.Value(SCREEN_HEIGHT3)).current;
|
|
4619
|
-
(0, import_react18.useEffect)(() => {
|
|
5129
|
+
(0, import_react18.useLayoutEffect)(() => {
|
|
4620
5130
|
if (showingPreloadedWebView) {
|
|
4621
5131
|
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
4622
|
-
import_react_native14.Animated.timing(slideAnim, { toValue: 0, duration:
|
|
4623
|
-
} else {
|
|
4624
|
-
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
5132
|
+
import_react_native14.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native14.Easing.out(import_react_native14.Easing.cubic), useNativeDriver: true }).start();
|
|
4625
5133
|
}
|
|
4626
5134
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
|
|
4627
5135
|
const preloadStyle = [
|
|
4628
5136
|
styles3.preloadOverlay,
|
|
4629
5137
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
4630
|
-
{ transform: [{ translateY:
|
|
5138
|
+
showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
|
|
4631
5139
|
];
|
|
4632
5140
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
4633
5141
|
children,
|
|
@@ -4640,6 +5148,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4640
5148
|
primaryProductId: preloadedWebView.primaryProductId,
|
|
4641
5149
|
secondaryProductId: preloadedWebView.secondaryProductId,
|
|
4642
5150
|
isPurchasing: isPreloadPurchasing,
|
|
5151
|
+
webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
|
|
4643
5152
|
onReady: handlePreloadedWebViewReady,
|
|
4644
5153
|
onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
|
|
4645
5154
|
onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
|
|
@@ -4662,8 +5171,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4662
5171
|
}
|
|
4663
5172
|
var styles3 = import_react_native14.StyleSheet.create({
|
|
4664
5173
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
4665
|
-
hidden: { zIndex: -1 },
|
|
4666
|
-
visible: { zIndex: 9999 }
|
|
5174
|
+
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5175
|
+
visible: { zIndex: 9999, opacity: 1 }
|
|
4667
5176
|
});
|
|
4668
5177
|
|
|
4669
5178
|
// src/utils/productVariableResolver.ts
|