@virex-tech/paywallo-sdk 1.1.3 → 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/dist/index.d.mts +75 -47
- package/dist/index.d.ts +75 -47
- package/dist/index.js +770 -543
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +760 -533
- package/dist/index.mjs.map +1 -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);
|
|
@@ -971,7 +964,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
971
964
|
}
|
|
972
965
|
} catch (err) {
|
|
973
966
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
974
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
|
|
975
967
|
setError(e);
|
|
976
968
|
}
|
|
977
969
|
};
|
|
@@ -980,11 +972,96 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
980
972
|
return { paywallConfig, products, error };
|
|
981
973
|
}
|
|
982
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
|
+
|
|
983
1055
|
// src/domains/paywall/PaywallWebView.tsx
|
|
984
1056
|
var import_react4 = require("react");
|
|
985
|
-
var
|
|
1057
|
+
var import_react_native5 = require("react-native");
|
|
986
1058
|
|
|
987
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
|
+
}
|
|
988
1065
|
function parseWebViewMessage(raw) {
|
|
989
1066
|
try {
|
|
990
1067
|
const parsed = JSON.parse(raw);
|
|
@@ -1008,7 +1085,7 @@ function buildPaywallInjectionScript(data) {
|
|
|
1008
1085
|
secondaryProductId: data.secondaryProductId
|
|
1009
1086
|
}
|
|
1010
1087
|
};
|
|
1011
|
-
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;`;
|
|
1012
1089
|
}
|
|
1013
1090
|
function buildPurchaseStateScript(isPurchasing) {
|
|
1014
1091
|
return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
|
|
@@ -1019,14 +1096,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
1019
1096
|
var NativeWebViewComponent = null;
|
|
1020
1097
|
function getNativeWebView() {
|
|
1021
1098
|
if (!NativeWebViewComponent) {
|
|
1022
|
-
NativeWebViewComponent = (0,
|
|
1099
|
+
NativeWebViewComponent = (0, import_react_native5.requireNativeComponent)("PaywalloWebView");
|
|
1023
1100
|
}
|
|
1024
1101
|
return NativeWebViewComponent;
|
|
1025
1102
|
}
|
|
1026
1103
|
var webViewEmitter = null;
|
|
1027
1104
|
function getWebViewEmitter() {
|
|
1028
1105
|
if (!webViewEmitter) {
|
|
1029
|
-
webViewEmitter = new
|
|
1106
|
+
webViewEmitter = new import_react_native5.NativeEventEmitter(import_react_native5.NativeModules.PaywalloWebViewModule);
|
|
1030
1107
|
}
|
|
1031
1108
|
return webViewEmitter;
|
|
1032
1109
|
}
|
|
@@ -1044,6 +1121,7 @@ function PaywallWebView({
|
|
|
1044
1121
|
primaryProductId,
|
|
1045
1122
|
secondaryProductId,
|
|
1046
1123
|
isPurchasing,
|
|
1124
|
+
webUrl: webUrlProp,
|
|
1047
1125
|
onPurchase,
|
|
1048
1126
|
onClose,
|
|
1049
1127
|
onRestore,
|
|
@@ -1055,8 +1133,39 @@ function PaywallWebView({
|
|
|
1055
1133
|
const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
|
|
1056
1134
|
const onErrorRef = (0, import_react4.useRef)(onError);
|
|
1057
1135
|
onErrorRef.current = onError;
|
|
1136
|
+
const onReadyRef = (0, import_react4.useRef)(onReady);
|
|
1137
|
+
onReadyRef.current = onReady;
|
|
1058
1138
|
const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
|
|
1059
|
-
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
|
+
}, []);
|
|
1060
1169
|
const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
|
|
1061
1170
|
if (!craftData || paywallDataSent.current) return;
|
|
1062
1171
|
paywallDataSent.current = true;
|
|
@@ -1067,19 +1176,21 @@ function PaywallWebView({
|
|
|
1067
1176
|
secondaryProductId
|
|
1068
1177
|
});
|
|
1069
1178
|
setScriptToInject(script);
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
const lastProcessedRef = (0, import_react4.useRef)({ type: "", time: 0 });
|
|
1179
|
+
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1180
|
+
const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
|
|
1073
1181
|
const handleParsedMessage = (0, import_react4.useCallback)(
|
|
1074
1182
|
(message) => {
|
|
1075
|
-
const
|
|
1076
|
-
if (
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
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);
|
|
1080
1187
|
switch (message.type) {
|
|
1081
1188
|
case "ready":
|
|
1082
|
-
if (!paywallDataSent.current)
|
|
1189
|
+
if (!paywallDataSent.current) {
|
|
1190
|
+
buildAndSetInjectionScript();
|
|
1191
|
+
} else {
|
|
1192
|
+
onReadyRef.current?.();
|
|
1193
|
+
}
|
|
1083
1194
|
break;
|
|
1084
1195
|
case "purchase":
|
|
1085
1196
|
if (message.payload?.productId) onPurchase(message.payload.productId);
|
|
@@ -1094,7 +1205,7 @@ function PaywallWebView({
|
|
|
1094
1205
|
if (message.payload?.url) {
|
|
1095
1206
|
const url = message.payload.url;
|
|
1096
1207
|
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
|
|
1097
|
-
void
|
|
1208
|
+
void import_react_native5.Linking.openURL(url);
|
|
1098
1209
|
}
|
|
1099
1210
|
}
|
|
1100
1211
|
break;
|
|
@@ -1170,8 +1281,9 @@ function PaywallWebView({
|
|
|
1170
1281
|
if (!paywallDataSent.current) return;
|
|
1171
1282
|
setScriptToInject(buildPurchaseStateScript(isPurchasing));
|
|
1172
1283
|
}, [isPurchasing]);
|
|
1173
|
-
|
|
1174
|
-
|
|
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)(
|
|
1175
1287
|
NativeWebView,
|
|
1176
1288
|
{
|
|
1177
1289
|
sourceUrl: paywallUrl,
|
|
@@ -1183,14 +1295,26 @@ function PaywallWebView({
|
|
|
1183
1295
|
}
|
|
1184
1296
|
) });
|
|
1185
1297
|
}
|
|
1186
|
-
var styles =
|
|
1298
|
+
var styles = import_react_native5.StyleSheet.create({
|
|
1187
1299
|
container: { flex: 1 },
|
|
1188
1300
|
webview: { flex: 1, backgroundColor: "transparent" }
|
|
1189
1301
|
});
|
|
1190
1302
|
|
|
1191
1303
|
// src/domains/paywall/PaywallModal.tsx
|
|
1192
1304
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1193
|
-
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
|
+
}
|
|
1194
1318
|
function PaywallModal({
|
|
1195
1319
|
placement,
|
|
1196
1320
|
visible,
|
|
@@ -1208,7 +1332,13 @@ function PaywallModal({
|
|
|
1208
1332
|
variantKey,
|
|
1209
1333
|
campaignId
|
|
1210
1334
|
);
|
|
1211
|
-
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);
|
|
1212
1342
|
const [webViewError, setWebViewError] = (0, import_react5.useState)(null);
|
|
1213
1343
|
const [retryCount, setRetryCount] = (0, import_react5.useState)(0);
|
|
1214
1344
|
const retryCountRef = (0, import_react5.useRef)(0);
|
|
@@ -1228,36 +1358,38 @@ function PaywallModal({
|
|
|
1228
1358
|
setWebViewError(null);
|
|
1229
1359
|
setRetryCount(0);
|
|
1230
1360
|
}, []);
|
|
1231
|
-
const openAnim = (0, import_react5.useRef)(new import_react_native5.Animated.Value(SCREEN_HEIGHT2)).current;
|
|
1232
1361
|
(0, import_react5.useEffect)(() => {
|
|
1233
1362
|
if (visible) {
|
|
1363
|
+
setModalVisible(true);
|
|
1234
1364
|
openAnim.setValue(SCREEN_HEIGHT2);
|
|
1235
|
-
|
|
1365
|
+
import_react_native6.Animated.timing(openAnim, {
|
|
1236
1366
|
toValue: 0,
|
|
1237
1367
|
duration: 550,
|
|
1238
|
-
easing:
|
|
1368
|
+
easing: import_react_native6.Easing.out(import_react_native6.Easing.cubic),
|
|
1239
1369
|
useNativeDriver: true
|
|
1240
1370
|
}).start();
|
|
1241
1371
|
}
|
|
1242
1372
|
}, [visible, openAnim]);
|
|
1243
|
-
if (!visible) return null;
|
|
1373
|
+
if (!modalVisible && !visible) return null;
|
|
1244
1374
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1245
|
-
|
|
1375
|
+
import_react_native6.Modal,
|
|
1246
1376
|
{
|
|
1247
|
-
visible,
|
|
1377
|
+
visible: modalVisible,
|
|
1248
1378
|
animationType: "none",
|
|
1249
1379
|
presentationStyle: "overFullScreen",
|
|
1250
1380
|
onRequestClose: handleClose,
|
|
1251
1381
|
transparent: true,
|
|
1252
1382
|
statusBarTranslucent: true,
|
|
1253
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1254
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1255
|
-
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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)(
|
|
1261
1393
|
PaywallWebView,
|
|
1262
1394
|
{
|
|
1263
1395
|
paywallId: paywallConfig.id,
|
|
@@ -1277,7 +1409,7 @@ function PaywallModal({
|
|
|
1277
1409
|
}
|
|
1278
1410
|
);
|
|
1279
1411
|
}
|
|
1280
|
-
var styles2 =
|
|
1412
|
+
var styles2 = import_react_native6.StyleSheet.create({
|
|
1281
1413
|
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1282
1414
|
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1283
1415
|
container: { flex: 1, backgroundColor: "#fff" },
|
|
@@ -1341,8 +1473,7 @@ var PaywallErrorBoundary = class extends import_react6.Component {
|
|
|
1341
1473
|
static getDerivedStateFromError() {
|
|
1342
1474
|
return { hasError: true };
|
|
1343
1475
|
}
|
|
1344
|
-
componentDidCatch(
|
|
1345
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
|
|
1476
|
+
componentDidCatch(_error) {
|
|
1346
1477
|
}
|
|
1347
1478
|
render() {
|
|
1348
1479
|
return this.state.hasError ? null : this.props.children;
|
|
@@ -1365,7 +1496,7 @@ function usePaywallContext() {
|
|
|
1365
1496
|
|
|
1366
1497
|
// src/domains/paywall/usePaywallPresenter.ts
|
|
1367
1498
|
var import_react8 = require("react");
|
|
1368
|
-
function usePaywallPresenter(preloadedWebView,
|
|
1499
|
+
function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
1369
1500
|
const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react8.useState)(false);
|
|
1370
1501
|
const resolverRef = (0, import_react8.useRef)(null);
|
|
1371
1502
|
const presentedAtRef = (0, import_react8.useRef)(Date.now());
|
|
@@ -1414,9 +1545,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1414
1545
|
};
|
|
1415
1546
|
void track();
|
|
1416
1547
|
setShowingPreloadedWebView(false);
|
|
1417
|
-
clearPreloadedWebView();
|
|
1418
1548
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
1419
|
-
}, [preloadedWebView,
|
|
1549
|
+
}, [preloadedWebView, resolvePreloadedPaywall]);
|
|
1420
1550
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1421
1551
|
}
|
|
1422
1552
|
|
|
@@ -1623,10 +1753,7 @@ var AffiliateManager = class {
|
|
|
1623
1753
|
);
|
|
1624
1754
|
}
|
|
1625
1755
|
}
|
|
1626
|
-
log(...
|
|
1627
|
-
if (this.debug) {
|
|
1628
|
-
console.warn("[AffiliateManager]", ...args);
|
|
1629
|
-
}
|
|
1756
|
+
log(..._args) {
|
|
1630
1757
|
}
|
|
1631
1758
|
};
|
|
1632
1759
|
var affiliateManager = new AffiliateManager();
|
|
@@ -1644,26 +1771,42 @@ var PaywalloAffiliate = {
|
|
|
1644
1771
|
};
|
|
1645
1772
|
|
|
1646
1773
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1647
|
-
var
|
|
1774
|
+
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1775
|
+
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1648
1776
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1777
|
+
function keyFor(distinctId) {
|
|
1778
|
+
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
1779
|
+
}
|
|
1649
1780
|
var SubscriptionCache = class {
|
|
1650
1781
|
constructor(ttl = DEFAULT_TTL) {
|
|
1651
|
-
this.memoryCache =
|
|
1782
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
1783
|
+
this.legacyCleanupDone = false;
|
|
1652
1784
|
this.ttl = ttl;
|
|
1653
1785
|
this.storage = new SecureStorage();
|
|
1654
1786
|
}
|
|
1655
|
-
async
|
|
1656
|
-
if (this.
|
|
1657
|
-
|
|
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;
|
|
1658
1801
|
}
|
|
1659
1802
|
try {
|
|
1660
|
-
const stored = await this.storage.get(
|
|
1803
|
+
const stored = await this.storage.get(keyFor(distinctId));
|
|
1661
1804
|
if (!stored) {
|
|
1662
1805
|
return null;
|
|
1663
1806
|
}
|
|
1664
1807
|
const parsed = JSON.parse(stored);
|
|
1665
1808
|
if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
|
|
1666
|
-
await this.storage.remove(
|
|
1809
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1667
1810
|
return null;
|
|
1668
1811
|
}
|
|
1669
1812
|
const cached = parsed;
|
|
@@ -1692,32 +1835,39 @@ var SubscriptionCache = class {
|
|
|
1692
1835
|
}
|
|
1693
1836
|
cached.isStale = this.isExpired(cached);
|
|
1694
1837
|
if (!cached.isStale) {
|
|
1695
|
-
this.memoryCache
|
|
1838
|
+
this.memoryCache.set(distinctId, cached);
|
|
1696
1839
|
}
|
|
1697
1840
|
return cached;
|
|
1698
1841
|
} catch {
|
|
1699
1842
|
return null;
|
|
1700
1843
|
}
|
|
1701
1844
|
}
|
|
1702
|
-
async set(data) {
|
|
1845
|
+
async set(distinctId, data) {
|
|
1846
|
+
if (!distinctId) return;
|
|
1703
1847
|
const cached = {
|
|
1704
1848
|
data,
|
|
1705
1849
|
cachedAt: Date.now(),
|
|
1706
1850
|
isStale: false
|
|
1707
1851
|
};
|
|
1708
|
-
this.memoryCache
|
|
1852
|
+
this.memoryCache.set(distinctId, cached);
|
|
1709
1853
|
try {
|
|
1710
|
-
await this.storage.set(
|
|
1711
|
-
} catch
|
|
1712
|
-
if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
|
|
1854
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1855
|
+
} catch {
|
|
1713
1856
|
}
|
|
1714
1857
|
}
|
|
1715
|
-
async invalidate() {
|
|
1716
|
-
|
|
1858
|
+
async invalidate(distinctId) {
|
|
1859
|
+
if (!distinctId) return;
|
|
1860
|
+
this.memoryCache.delete(distinctId);
|
|
1717
1861
|
try {
|
|
1718
|
-
await this.storage.remove(
|
|
1719
|
-
} catch
|
|
1720
|
-
|
|
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 {
|
|
1721
1871
|
}
|
|
1722
1872
|
}
|
|
1723
1873
|
isExpired(cached) {
|
|
@@ -1729,9 +1879,156 @@ var SubscriptionCache = class {
|
|
|
1729
1879
|
};
|
|
1730
1880
|
var subscriptionCache = new SubscriptionCache();
|
|
1731
1881
|
|
|
1732
|
-
// src/
|
|
1882
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1733
1883
|
var import_react_native7 = require("react-native");
|
|
1734
1884
|
|
|
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);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
setUserId(userId) {
|
|
1917
|
+
if (this.userId !== userId) {
|
|
1918
|
+
this.userId = userId;
|
|
1919
|
+
void this.cache.invalidateAll();
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
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
|
+
|
|
1735
2032
|
// src/core/apiUrlUtils.ts
|
|
1736
2033
|
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
1737
2034
|
function deriveWebUrl(baseUrl) {
|
|
@@ -1962,10 +2259,7 @@ var HttpClient = class {
|
|
|
1962
2259
|
sleep(ms) {
|
|
1963
2260
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1964
2261
|
}
|
|
1965
|
-
log(
|
|
1966
|
-
if (this.config.debug) {
|
|
1967
|
-
console.warn("[HttpClient]", message, data ?? "");
|
|
1968
|
-
}
|
|
2262
|
+
log(..._args) {
|
|
1969
2263
|
}
|
|
1970
2264
|
setDebug(debug) {
|
|
1971
2265
|
this.config.debug = debug;
|
|
@@ -1976,7 +2270,7 @@ var HttpClient = class {
|
|
|
1976
2270
|
};
|
|
1977
2271
|
|
|
1978
2272
|
// src/core/network/NetworkMonitor.ts
|
|
1979
|
-
var
|
|
2273
|
+
var import_react_native8 = require("react-native");
|
|
1980
2274
|
|
|
1981
2275
|
// src/core/network/types.ts
|
|
1982
2276
|
var DEFAULT_NETWORK_CONFIG = {
|
|
@@ -2022,7 +2316,7 @@ var NetworkMonitorClass = class {
|
|
|
2022
2316
|
}, this.config.checkInterval);
|
|
2023
2317
|
}
|
|
2024
2318
|
setupAppStateListener() {
|
|
2025
|
-
this.appStateSubscription =
|
|
2319
|
+
this.appStateSubscription = import_react_native8.AppState.addEventListener("change", (state) => {
|
|
2026
2320
|
if (state === "active") {
|
|
2027
2321
|
void this.checkConnectivity();
|
|
2028
2322
|
}
|
|
@@ -2109,10 +2403,7 @@ var NetworkMonitorClass = class {
|
|
|
2109
2403
|
this.initialized = false;
|
|
2110
2404
|
this.currentState = "unknown";
|
|
2111
2405
|
}
|
|
2112
|
-
log(
|
|
2113
|
-
if (this.config.debug) {
|
|
2114
|
-
console.warn("[NetworkMonitor]", message, data ?? "");
|
|
2115
|
-
}
|
|
2406
|
+
log(..._args) {
|
|
2116
2407
|
}
|
|
2117
2408
|
setDebug(debug) {
|
|
2118
2409
|
this.config.debug = debug;
|
|
@@ -2142,6 +2433,8 @@ function normalizeForComparison(obj) {
|
|
|
2142
2433
|
return "{" + pairs.join(",") + "}";
|
|
2143
2434
|
}
|
|
2144
2435
|
function findDuplicateIndex(queue, item) {
|
|
2436
|
+
const byId = queue.findIndex((existing) => existing.id === item.id);
|
|
2437
|
+
if (byId !== -1) return byId;
|
|
2145
2438
|
const normalizedBody = normalizeForComparison(item.body);
|
|
2146
2439
|
return queue.findIndex(
|
|
2147
2440
|
(existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
|
|
@@ -2210,9 +2503,9 @@ var OfflineQueueClass = class {
|
|
|
2210
2503
|
void this.saveToStorage();
|
|
2211
2504
|
}
|
|
2212
2505
|
}
|
|
2213
|
-
async enqueue(method, url, body, headers) {
|
|
2506
|
+
async enqueue(method, url, body, headers, id) {
|
|
2214
2507
|
const item = {
|
|
2215
|
-
id: generateUUID(),
|
|
2508
|
+
id: id ?? generateUUID(),
|
|
2216
2509
|
method,
|
|
2217
2510
|
url,
|
|
2218
2511
|
body,
|
|
@@ -2296,7 +2589,6 @@ var OfflineQueueClass = class {
|
|
|
2296
2589
|
this.emit({ type: "processing:end", queueSize: this.queue.length });
|
|
2297
2590
|
return { processed, failed };
|
|
2298
2591
|
}
|
|
2299
|
-
/** Returns true if the item was permanently removed (counted as failed). */
|
|
2300
2592
|
async handleItemFailure(item, error, permanent = false) {
|
|
2301
2593
|
if (permanent || item.attempts >= item.maxAttempts) {
|
|
2302
2594
|
await this.removeItem(item.id);
|
|
@@ -2358,10 +2650,7 @@ var OfflineQueueClass = class {
|
|
|
2358
2650
|
}
|
|
2359
2651
|
}
|
|
2360
2652
|
}
|
|
2361
|
-
log(
|
|
2362
|
-
if (this.config.debug) {
|
|
2363
|
-
console.warn("[OfflineQueue]", message, data ?? "");
|
|
2364
|
-
}
|
|
2653
|
+
log(..._args) {
|
|
2365
2654
|
}
|
|
2366
2655
|
setDebug(debug) {
|
|
2367
2656
|
this.config.debug = debug;
|
|
@@ -2503,10 +2792,7 @@ var QueueProcessorClass = class {
|
|
|
2503
2792
|
this.httpClient = null;
|
|
2504
2793
|
this.initialized = false;
|
|
2505
2794
|
}
|
|
2506
|
-
log(
|
|
2507
|
-
if (this.config.debug) {
|
|
2508
|
-
console.warn("[QueueProcessor]", message, data ?? "");
|
|
2509
|
-
}
|
|
2795
|
+
log(..._args) {
|
|
2510
2796
|
}
|
|
2511
2797
|
setDebug(debug) {
|
|
2512
2798
|
this.config.debug = debug;
|
|
@@ -2518,10 +2804,9 @@ var queueProcessor = new QueueProcessorClass();
|
|
|
2518
2804
|
var ApiCache = class {
|
|
2519
2805
|
constructor() {
|
|
2520
2806
|
this.CACHE_TTL = 3e5;
|
|
2521
|
-
// 5 minutes
|
|
2522
2807
|
this.CACHE_NULL_TTL = 3e4;
|
|
2523
|
-
// 30 seconds — for null/404 variants
|
|
2524
2808
|
this.MAX_SIZE = 200;
|
|
2809
|
+
this.STALE_WINDOW_MS = 864e5;
|
|
2525
2810
|
this.variantCache = /* @__PURE__ */ new Map();
|
|
2526
2811
|
this.paywallCache = /* @__PURE__ */ new Map();
|
|
2527
2812
|
this.campaignCache = /* @__PURE__ */ new Map();
|
|
@@ -2559,7 +2844,7 @@ var ApiCache = class {
|
|
|
2559
2844
|
}
|
|
2560
2845
|
getStaleVariant(key) {
|
|
2561
2846
|
const entry = this.variantCache.get(key);
|
|
2562
|
-
if (entry && Date.now() - entry.ts <
|
|
2847
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2563
2848
|
return void 0;
|
|
2564
2849
|
}
|
|
2565
2850
|
getPaywall(key) {
|
|
@@ -2571,19 +2856,19 @@ var ApiCache = class {
|
|
|
2571
2856
|
}
|
|
2572
2857
|
getStalePaywall(key) {
|
|
2573
2858
|
const entry = this.paywallCache.get(key);
|
|
2574
|
-
if (entry && Date.now() - entry.ts <
|
|
2859
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2575
2860
|
return void 0;
|
|
2576
2861
|
}
|
|
2577
2862
|
getCampaign(key) {
|
|
2578
2863
|
return this.getCached(this.campaignCache, key);
|
|
2579
2864
|
}
|
|
2580
|
-
setCampaign(key, value) {
|
|
2581
|
-
this.setCached(this.campaignCache, key, value);
|
|
2865
|
+
setCampaign(key, value, ttlOverride) {
|
|
2866
|
+
this.setCached(this.campaignCache, key, value, ttlOverride);
|
|
2582
2867
|
this.evictExpiredEntries(this.campaignCache);
|
|
2583
2868
|
}
|
|
2584
2869
|
getStaleCampaign(key) {
|
|
2585
2870
|
const entry = this.campaignCache.get(key);
|
|
2586
|
-
if (entry && Date.now() - entry.ts <
|
|
2871
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2587
2872
|
return void 0;
|
|
2588
2873
|
}
|
|
2589
2874
|
};
|
|
@@ -2602,11 +2887,12 @@ var _ApiClient = class _ApiClient {
|
|
|
2602
2887
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2603
2888
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
2604
2889
|
}
|
|
2605
|
-
// ─── Infrastructure ──────────────────────────────────────────────────────────
|
|
2606
|
-
/** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
|
|
2607
2890
|
getHttpClient() {
|
|
2608
2891
|
return this.httpClient;
|
|
2609
2892
|
}
|
|
2893
|
+
getBaseUrl() {
|
|
2894
|
+
return this.baseUrl;
|
|
2895
|
+
}
|
|
2610
2896
|
getWebUrl() {
|
|
2611
2897
|
return this.webUrl;
|
|
2612
2898
|
}
|
|
@@ -2632,7 +2918,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2632
2918
|
async clearQueue() {
|
|
2633
2919
|
await offlineQueue.clear();
|
|
2634
2920
|
}
|
|
2635
|
-
// ─── Public HTTP facade (headers injected automatically) ─────────────────────
|
|
2636
2921
|
async get(path) {
|
|
2637
2922
|
return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
|
|
2638
2923
|
}
|
|
@@ -2646,29 +2931,28 @@ var _ApiClient = class _ApiClient {
|
|
|
2646
2931
|
message,
|
|
2647
2932
|
context,
|
|
2648
2933
|
sdkVersion: SDK_VERSION,
|
|
2649
|
-
platform:
|
|
2934
|
+
platform: import_react_native9.Platform.OS
|
|
2650
2935
|
}, true);
|
|
2651
2936
|
} catch {
|
|
2652
2937
|
}
|
|
2653
2938
|
}
|
|
2654
|
-
// ─── Business methods ────────────────────────────────────────────────────────
|
|
2655
2939
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2656
2940
|
await this.postWithQueue("/api/v1/events", {
|
|
2657
2941
|
eventName,
|
|
2658
2942
|
distinctId,
|
|
2659
|
-
properties: { ...properties, platform:
|
|
2943
|
+
properties: { ...properties, platform: import_react_native9.Platform.OS },
|
|
2660
2944
|
timestamp: timestamp ?? Date.now(),
|
|
2661
2945
|
environment: this.environment.toLowerCase()
|
|
2662
2946
|
}, `event:${eventName}`);
|
|
2663
2947
|
}
|
|
2664
|
-
async identify(distinctId, properties, email) {
|
|
2665
|
-
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");
|
|
2666
2950
|
}
|
|
2667
2951
|
async startSession(distinctId, sessionId, platform) {
|
|
2668
2952
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2669
2953
|
distinctId,
|
|
2670
2954
|
sessionId,
|
|
2671
|
-
platform: platform ??
|
|
2955
|
+
platform: platform ?? import_react_native9.Platform.OS,
|
|
2672
2956
|
environment: this.environment.toLowerCase()
|
|
2673
2957
|
}, `session.start:${sessionId}`);
|
|
2674
2958
|
return { success: true };
|
|
@@ -2746,7 +3030,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2746
3030
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2747
3031
|
if (!result) {
|
|
2748
3032
|
this.log("No campaign found for placement:", placement);
|
|
2749
|
-
this.cache.setCampaign(key, null);
|
|
3033
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2750
3034
|
return null;
|
|
2751
3035
|
}
|
|
2752
3036
|
this.log("Got campaign:", placement, result.variantKey);
|
|
@@ -2759,12 +3043,38 @@ var _ApiClient = class _ApiClient {
|
|
|
2759
3043
|
return null;
|
|
2760
3044
|
}
|
|
2761
3045
|
}
|
|
2762
|
-
async
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
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
|
+
}
|
|
3072
|
+
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
3073
|
+
const result = await validatePurchase(
|
|
3074
|
+
this,
|
|
3075
|
+
this.appKey,
|
|
3076
|
+
platform,
|
|
3077
|
+
receipt,
|
|
2768
3078
|
productId,
|
|
2769
3079
|
transactionId,
|
|
2770
3080
|
priceLocal,
|
|
@@ -2827,12 +3137,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2827
3137
|
return { value: false, flagKey };
|
|
2828
3138
|
}
|
|
2829
3139
|
}
|
|
2830
|
-
// ─── Cache-only reads ────────────────────────────────────────────────────────
|
|
2831
|
-
/**
|
|
2832
|
-
* Reads a flag variant from cache layers only — no network call.
|
|
2833
|
-
* Checks in-memory cache first, then SecureStorage.
|
|
2834
|
-
* Returns null when both layers miss.
|
|
2835
|
-
*/
|
|
2836
3140
|
async getVariantFromCache(flagKey, distinctId) {
|
|
2837
3141
|
const key = `${flagKey}:${distinctId}`;
|
|
2838
3142
|
const hit = this.cache.getVariant(key);
|
|
@@ -2864,10 +3168,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2864
3168
|
} catch {
|
|
2865
3169
|
}
|
|
2866
3170
|
}
|
|
2867
|
-
/**
|
|
2868
|
-
* POST with offline queue fallback. Enqueues when offline or on network failure.
|
|
2869
|
-
* Throws only when offlineQueueEnabled is false and the request fails.
|
|
2870
|
-
*/
|
|
2871
3171
|
async postWithQueue(url, payload, label) {
|
|
2872
3172
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
2873
3173
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -2886,11 +3186,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2886
3186
|
throw error;
|
|
2887
3187
|
}
|
|
2888
3188
|
}
|
|
2889
|
-
log(...
|
|
2890
|
-
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
3189
|
+
log(..._args) {
|
|
2891
3190
|
}
|
|
2892
3191
|
};
|
|
2893
|
-
// ─── Private helpers ─────────────────────────────────────────────────────────
|
|
2894
3192
|
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2895
3193
|
var ApiClient = _ApiClient;
|
|
2896
3194
|
|
|
@@ -2911,42 +3209,72 @@ var CAMPAIGN_ERROR_CODES = {
|
|
|
2911
3209
|
|
|
2912
3210
|
// src/domains/campaign/CampaignGateService.ts
|
|
2913
3211
|
var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3212
|
+
var PRELOAD_WAIT_MAX_MS = 2e3;
|
|
3213
|
+
var PRELOAD_WAIT_INTERVAL_MS = 100;
|
|
2914
3214
|
var CampaignGateService = class {
|
|
2915
3215
|
constructor() {
|
|
2916
3216
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3217
|
+
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
2917
3218
|
}
|
|
2918
3219
|
async getCampaign(apiClient, placement, context) {
|
|
2919
3220
|
const distinctId = identityManager.getDistinctId();
|
|
2920
3221
|
if (!distinctId) return null;
|
|
2921
3222
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2922
3223
|
}
|
|
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
|
+
}
|
|
2923
3238
|
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2924
|
-
|
|
3239
|
+
await this.waitForPreload(placement);
|
|
3240
|
+
const preloaded = this.getPreloadedCampaign(placement);
|
|
3241
|
+
const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
|
|
2925
3242
|
if (!campaign) {
|
|
2926
3243
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2927
3244
|
}
|
|
2928
|
-
if (!context?.forceShow
|
|
2929
|
-
|
|
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
|
+
}
|
|
2930
3250
|
}
|
|
2931
3251
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2932
3252
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2933
|
-
return { ...await paywallPresenter(
|
|
3253
|
+
return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2934
3254
|
}
|
|
2935
3255
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2936
|
-
|
|
3256
|
+
const isActive = await hasActive();
|
|
3257
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3258
|
+
if (isActive) return true;
|
|
2937
3259
|
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2938
3260
|
return r.purchased || r.restored;
|
|
2939
3261
|
}
|
|
2940
3262
|
async preloadCampaign(apiClient, placement, context) {
|
|
3263
|
+
this.pendingPreloads.add(placement);
|
|
2941
3264
|
try {
|
|
2942
3265
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2943
3266
|
if (!campaign?.paywall) {
|
|
2944
3267
|
return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
|
|
2945
3268
|
}
|
|
2946
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
|
+
}
|
|
2947
3273
|
return { success: true };
|
|
2948
3274
|
} catch (error) {
|
|
2949
3275
|
return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
|
|
3276
|
+
} finally {
|
|
3277
|
+
this.pendingPreloads.delete(placement);
|
|
2950
3278
|
}
|
|
2951
3279
|
}
|
|
2952
3280
|
getPreloadedCampaign(placement) {
|
|
@@ -2960,12 +3288,13 @@ var CampaignGateService = class {
|
|
|
2960
3288
|
}
|
|
2961
3289
|
clearPreloaded() {
|
|
2962
3290
|
this.preloadedCampaigns.clear();
|
|
3291
|
+
this.pendingPreloads.clear();
|
|
2963
3292
|
}
|
|
2964
3293
|
};
|
|
2965
3294
|
var campaignGateService = new CampaignGateService();
|
|
2966
3295
|
|
|
2967
3296
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
2968
|
-
var
|
|
3297
|
+
var import_react_native10 = require("react-native");
|
|
2969
3298
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
2970
3299
|
var AdvertisingIdManager = class {
|
|
2971
3300
|
constructor() {
|
|
@@ -2976,7 +3305,7 @@ var AdvertisingIdManager = class {
|
|
|
2976
3305
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
2977
3306
|
try {
|
|
2978
3307
|
const tracking = await import("expo-tracking-transparency");
|
|
2979
|
-
if (
|
|
3308
|
+
if (import_react_native10.Platform.OS === "ios") {
|
|
2980
3309
|
if (!tracking.isAvailable()) {
|
|
2981
3310
|
const idfv2 = await this.collectIdfv();
|
|
2982
3311
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -2995,7 +3324,7 @@ var AdvertisingIdManager = class {
|
|
|
2995
3324
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
2996
3325
|
return this.cached;
|
|
2997
3326
|
}
|
|
2998
|
-
if (
|
|
3327
|
+
if (import_react_native10.Platform.OS === "android") {
|
|
2999
3328
|
const androidStatus = await tracking.getTrackingPermissionsAsync();
|
|
3000
3329
|
const optedIn = androidStatus.granted;
|
|
3001
3330
|
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
@@ -3054,14 +3383,14 @@ var FacebookIdManager = class {
|
|
|
3054
3383
|
var facebookIdManager = new FacebookIdManager();
|
|
3055
3384
|
|
|
3056
3385
|
// src/domains/identity/InstallTracker.ts
|
|
3057
|
-
var
|
|
3386
|
+
var import_react_native13 = require("react-native");
|
|
3058
3387
|
|
|
3059
3388
|
// src/domains/identity/InstallReferrerManager.ts
|
|
3060
|
-
var
|
|
3389
|
+
var import_react_native11 = require("react-native");
|
|
3061
3390
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
3062
3391
|
var InstallReferrerManager = class {
|
|
3063
3392
|
async getReferrer() {
|
|
3064
|
-
if (
|
|
3393
|
+
if (import_react_native11.Platform.OS !== "android") {
|
|
3065
3394
|
return this.emptyResult();
|
|
3066
3395
|
}
|
|
3067
3396
|
let timerId;
|
|
@@ -3121,21 +3450,7 @@ var InstallReferrerManager = class {
|
|
|
3121
3450
|
var installReferrerManager = new InstallReferrerManager();
|
|
3122
3451
|
|
|
3123
3452
|
// src/domains/session/SessionManager.ts
|
|
3124
|
-
var
|
|
3125
|
-
|
|
3126
|
-
// src/domains/session/SessionError.ts
|
|
3127
|
-
var SessionError = class extends PaywalloError {
|
|
3128
|
-
constructor(code, message) {
|
|
3129
|
-
super("session", code, message);
|
|
3130
|
-
this.name = "SessionError";
|
|
3131
|
-
}
|
|
3132
|
-
};
|
|
3133
|
-
var SESSION_ERROR_CODES = {
|
|
3134
|
-
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
3135
|
-
START_FAILED: "SESSION_START_FAILED",
|
|
3136
|
-
END_FAILED: "SESSION_END_FAILED",
|
|
3137
|
-
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
3138
|
-
};
|
|
3453
|
+
var import_react_native12 = require("react-native");
|
|
3139
3454
|
|
|
3140
3455
|
// src/domains/session/sessionTracking.ts
|
|
3141
3456
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
@@ -3303,7 +3618,7 @@ var SessionManager = class {
|
|
|
3303
3618
|
if (this.appStateSubscription) {
|
|
3304
3619
|
this.appStateSubscription.remove();
|
|
3305
3620
|
}
|
|
3306
|
-
this.appStateSubscription =
|
|
3621
|
+
this.appStateSubscription = import_react_native12.AppState.addEventListener("change", this.handleAppStateChange);
|
|
3307
3622
|
}
|
|
3308
3623
|
async handleForeground() {
|
|
3309
3624
|
if (this.backgroundTimestamp) {
|
|
@@ -3364,10 +3679,7 @@ var SessionManager = class {
|
|
|
3364
3679
|
);
|
|
3365
3680
|
}
|
|
3366
3681
|
}
|
|
3367
|
-
log(...
|
|
3368
|
-
if (this.debug) {
|
|
3369
|
-
console.warn("[Paywallo Session]", ...args);
|
|
3370
|
-
}
|
|
3682
|
+
log(..._args) {
|
|
3371
3683
|
}
|
|
3372
3684
|
};
|
|
3373
3685
|
var sessionManager = new SessionManager();
|
|
@@ -3402,7 +3714,7 @@ var InstallTracker = class {
|
|
|
3402
3714
|
}
|
|
3403
3715
|
let deviceData = {};
|
|
3404
3716
|
try {
|
|
3405
|
-
const screen =
|
|
3717
|
+
const screen = import_react_native13.Dimensions.get("screen");
|
|
3406
3718
|
const screenWidth = Math.round(screen.width ?? 0);
|
|
3407
3719
|
const screenHeight = Math.round(screen.height ?? 0);
|
|
3408
3720
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
@@ -3422,9 +3734,6 @@ var InstallTracker = class {
|
|
|
3422
3734
|
locale
|
|
3423
3735
|
};
|
|
3424
3736
|
} catch {
|
|
3425
|
-
if (this.debug) {
|
|
3426
|
-
console.warn("[Paywallo] Failed to collect device data for install event");
|
|
3427
|
-
}
|
|
3428
3737
|
}
|
|
3429
3738
|
const [fbAnonId, referrer, adIds] = await Promise.all([
|
|
3430
3739
|
facebookIdManager.getAnonId(),
|
|
@@ -3434,7 +3743,7 @@ var InstallTracker = class {
|
|
|
3434
3743
|
try {
|
|
3435
3744
|
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3436
3745
|
installedAt,
|
|
3437
|
-
platform:
|
|
3746
|
+
platform: import_react_native13.Platform.OS,
|
|
3438
3747
|
...sessionId && { sessionId },
|
|
3439
3748
|
...deviceData,
|
|
3440
3749
|
...fbAnonId && { fbAnonId },
|
|
@@ -3455,13 +3764,7 @@ var InstallTracker = class {
|
|
|
3455
3764
|
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3456
3765
|
} catch {
|
|
3457
3766
|
}
|
|
3458
|
-
|
|
3459
|
-
console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
|
|
3460
|
-
}
|
|
3461
|
-
} catch (error) {
|
|
3462
|
-
if (this.debug) {
|
|
3463
|
-
console.warn("[Paywallo] Failed to track install:", error);
|
|
3464
|
-
}
|
|
3767
|
+
} catch {
|
|
3465
3768
|
}
|
|
3466
3769
|
}
|
|
3467
3770
|
};
|
|
@@ -3471,25 +3774,13 @@ function isValidEventName(name) {
|
|
|
3471
3774
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3472
3775
|
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3473
3776
|
}
|
|
3474
|
-
var OFFLINE_ACTIVE_STATUSES = ["active", "grace_period"];
|
|
3475
|
-
async function resolveClientOfflineFromCache() {
|
|
3476
|
-
try {
|
|
3477
|
-
const cached = await subscriptionCache.get();
|
|
3478
|
-
if (!cached) return false;
|
|
3479
|
-
const sub = cached.data.subscription;
|
|
3480
|
-
if (!sub) return false;
|
|
3481
|
-
if (!OFFLINE_ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
3482
|
-
if (!sub.expiresAt) return true;
|
|
3483
|
-
return sub.expiresAt > /* @__PURE__ */ new Date();
|
|
3484
|
-
} catch {
|
|
3485
|
-
return false;
|
|
3486
|
-
}
|
|
3487
|
-
}
|
|
3488
3777
|
var _PaywalloClientClass = class _PaywalloClientClass {
|
|
3489
3778
|
constructor() {
|
|
3490
3779
|
this.config = null;
|
|
3491
3780
|
this.apiClient = null;
|
|
3492
3781
|
this.initPromise = null;
|
|
3782
|
+
this.readyPromise = null;
|
|
3783
|
+
this.resolveReady = null;
|
|
3493
3784
|
this.paywallPresenter = null;
|
|
3494
3785
|
this.campaignPresenter = null;
|
|
3495
3786
|
this.subscriptionGetter = null;
|
|
@@ -3499,29 +3790,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3499
3790
|
this.pendingConfig = null;
|
|
3500
3791
|
this.networkCleanup = null;
|
|
3501
3792
|
this.initAttempts = 0;
|
|
3793
|
+
this.autoPreloadedPlacement = null;
|
|
3794
|
+
this.autoPreloadPlacementPromise = null;
|
|
3795
|
+
this.sessionFlagsMap = /* @__PURE__ */ new Map();
|
|
3502
3796
|
}
|
|
3503
3797
|
async init(config) {
|
|
3504
|
-
if (
|
|
3798
|
+
if (!this.readyPromise) {
|
|
3799
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3800
|
+
this.resolveReady = resolve;
|
|
3801
|
+
});
|
|
3802
|
+
}
|
|
3505
3803
|
if (this.config && this.apiClient) {
|
|
3506
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3507
3804
|
return;
|
|
3508
3805
|
}
|
|
3509
3806
|
if (this.initPromise) {
|
|
3510
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3511
3807
|
await this.initPromise;
|
|
3512
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3513
3808
|
return;
|
|
3514
3809
|
}
|
|
3515
3810
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3516
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3517
3811
|
this.pendingConfig = config;
|
|
3518
3812
|
this.initAttempts = 0;
|
|
3519
3813
|
this.initPromise = this.initWithRetry(config);
|
|
3520
3814
|
try {
|
|
3521
3815
|
await this.initPromise;
|
|
3522
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3523
3816
|
} catch (error) {
|
|
3524
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3525
3817
|
this.initPromise = null;
|
|
3526
3818
|
this.config = null;
|
|
3527
3819
|
this.apiClient = null;
|
|
@@ -3530,6 +3822,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3530
3822
|
throw error;
|
|
3531
3823
|
}
|
|
3532
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
|
+
}
|
|
3533
3837
|
async initWithRetry(config) {
|
|
3534
3838
|
while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
|
|
3535
3839
|
try {
|
|
@@ -3544,7 +3848,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3544
3848
|
this.initAttempts++;
|
|
3545
3849
|
if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
|
|
3546
3850
|
const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
|
|
3547
|
-
if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
|
|
3548
3851
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3549
3852
|
}
|
|
3550
3853
|
}
|
|
@@ -3555,11 +3858,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3555
3858
|
this.networkCleanup = networkMonitor.addListener((state) => {
|
|
3556
3859
|
if (state !== "online") return;
|
|
3557
3860
|
if (this.config && this.apiClient) return;
|
|
3558
|
-
if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
|
|
3559
3861
|
this.initPromise = this.initWithRetry(config);
|
|
3560
|
-
this.initPromise.
|
|
3561
|
-
if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
|
|
3562
|
-
}).catch(() => {
|
|
3862
|
+
this.initPromise.catch(() => {
|
|
3563
3863
|
this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
|
|
3564
3864
|
});
|
|
3565
3865
|
});
|
|
@@ -3588,38 +3888,79 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3588
3888
|
} catch {
|
|
3589
3889
|
}
|
|
3590
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
|
+
}
|
|
3591
3904
|
async doInit(config) {
|
|
3592
3905
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3593
3906
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3594
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3595
3907
|
await Promise.all([
|
|
3596
3908
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3597
3909
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3598
3910
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3599
3911
|
)
|
|
3600
3912
|
]);
|
|
3601
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3602
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3603
3913
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3604
3914
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3605
3915
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3606
|
-
|
|
3607
|
-
|
|
3916
|
+
subscriptionManager.init({
|
|
3917
|
+
serverUrl: this.apiClient.getBaseUrl(),
|
|
3918
|
+
appKey: config.appKey,
|
|
3919
|
+
cacheTTL: config.subscriptionCacheTTL,
|
|
3920
|
+
debug: config.debug
|
|
3921
|
+
});
|
|
3608
3922
|
await Promise.all([
|
|
3609
3923
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3610
3924
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3611
3925
|
]);
|
|
3612
3926
|
const distinctId = identityManager.getDistinctId();
|
|
3613
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3614
3927
|
if (config.autoStartSession !== false) {
|
|
3615
3928
|
sessionManager.startSession().catch(() => {
|
|
3616
|
-
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
3617
3929
|
});
|
|
3618
3930
|
}
|
|
3619
3931
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3620
3932
|
});
|
|
3933
|
+
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3934
|
+
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3935
|
+
}
|
|
3621
3936
|
this.config = config;
|
|
3622
|
-
if (
|
|
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
|
+
}
|
|
3623
3964
|
}
|
|
3624
3965
|
async identify(options) {
|
|
3625
3966
|
this.ensureInitialized();
|
|
@@ -3629,7 +3970,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3629
3970
|
const apiClient = this.getApiClientOrThrow();
|
|
3630
3971
|
const distinctId = identityManager.getDistinctId();
|
|
3631
3972
|
if (!distinctId) {
|
|
3632
|
-
if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3633
3973
|
return;
|
|
3634
3974
|
}
|
|
3635
3975
|
if (!isValidEventName(eventName)) {
|
|
@@ -3637,25 +3977,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3637
3977
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
3638
3978
|
}
|
|
3639
3979
|
const sessionId = sessionManager.getSessionId();
|
|
3640
|
-
if (eventName === "$purchase_completed") {
|
|
3641
|
-
if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3642
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3643
|
-
productId: options?.properties?.productId,
|
|
3644
|
-
priceUsd: options?.properties?.priceUsd,
|
|
3645
|
-
price: options?.properties?.price,
|
|
3646
|
-
currency: options?.properties?.currency,
|
|
3647
|
-
placement: options?.properties?.placement
|
|
3648
|
-
}));
|
|
3649
|
-
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3650
|
-
if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3651
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3652
|
-
placement: options?.properties?.placement,
|
|
3653
|
-
variantKey: options?.properties?.variantKey,
|
|
3654
|
-
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3655
|
-
}));
|
|
3656
|
-
} else {
|
|
3657
|
-
if (this.config?.debug) console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3658
|
-
}
|
|
3659
3980
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3660
3981
|
...identityManager.getProperties(),
|
|
3661
3982
|
...options?.properties,
|
|
@@ -3666,7 +3987,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3666
3987
|
const apiClient = this.getApiClientOrThrow();
|
|
3667
3988
|
const distinctId = identityManager.getDistinctId();
|
|
3668
3989
|
if (!distinctId) {
|
|
3669
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3670
3990
|
return;
|
|
3671
3991
|
}
|
|
3672
3992
|
if (!Number.isInteger(index) || index < 0) {
|
|
@@ -3690,7 +4010,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3690
4010
|
const apiClient = this.getApiClientOrThrow();
|
|
3691
4011
|
const distinctId = identityManager.getDistinctId();
|
|
3692
4012
|
if (!distinctId) {
|
|
3693
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3694
4013
|
return;
|
|
3695
4014
|
}
|
|
3696
4015
|
if (!actionName || typeof actionName !== "string") {
|
|
@@ -3719,14 +4038,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3719
4038
|
try {
|
|
3720
4039
|
const distinctId = identityManager.getDistinctId();
|
|
3721
4040
|
if (!distinctId) {
|
|
3722
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3723
4041
|
return { variant: null };
|
|
3724
4042
|
}
|
|
3725
|
-
|
|
3726
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3727
|
-
return result;
|
|
4043
|
+
return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3728
4044
|
} catch (error) {
|
|
3729
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
3730
4045
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
3731
4046
|
return { variant: null };
|
|
3732
4047
|
}
|
|
@@ -3742,7 +4057,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3742
4057
|
const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
|
|
3743
4058
|
return result.value;
|
|
3744
4059
|
} catch (error) {
|
|
3745
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
|
|
3746
4060
|
if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
|
|
3747
4061
|
return false;
|
|
3748
4062
|
}
|
|
@@ -3765,15 +4079,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3765
4079
|
async hasActiveSubscription() {
|
|
3766
4080
|
this.ensureInitialized();
|
|
3767
4081
|
try {
|
|
3768
|
-
if (this.activeChecker)
|
|
4082
|
+
if (this.activeChecker) {
|
|
4083
|
+
return await this.activeChecker();
|
|
4084
|
+
}
|
|
3769
4085
|
const distinctId = identityManager.getDistinctId();
|
|
3770
|
-
if (!distinctId || !this.apiClient)
|
|
4086
|
+
if (!distinctId || !this.apiClient) {
|
|
4087
|
+
return false;
|
|
4088
|
+
}
|
|
3771
4089
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
3772
4090
|
return status.hasActiveSubscription;
|
|
3773
|
-
} catch {
|
|
3774
|
-
if (this.
|
|
3775
|
-
|
|
3776
|
-
return resolveClientOfflineFromCache();
|
|
4091
|
+
} catch (error) {
|
|
4092
|
+
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4093
|
+
return false;
|
|
3777
4094
|
}
|
|
3778
4095
|
}
|
|
3779
4096
|
async getSubscription() {
|
|
@@ -3786,7 +4103,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3786
4103
|
if (!status.subscription) return null;
|
|
3787
4104
|
return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
|
|
3788
4105
|
} catch {
|
|
3789
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
|
|
3790
4106
|
return null;
|
|
3791
4107
|
}
|
|
3792
4108
|
}
|
|
@@ -3797,20 +4113,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3797
4113
|
}
|
|
3798
4114
|
async reset() {
|
|
3799
4115
|
await identityManager.reset();
|
|
4116
|
+
await subscriptionCache.invalidateAll();
|
|
3800
4117
|
}
|
|
3801
4118
|
async fullReset() {
|
|
3802
4119
|
const wasDebug = this.config?.debug ?? false;
|
|
3803
4120
|
await sessionManager.endSession();
|
|
3804
4121
|
await identityManager.reset();
|
|
3805
|
-
await subscriptionCache.
|
|
4122
|
+
await subscriptionCache.invalidateAll();
|
|
3806
4123
|
queueProcessor.dispose();
|
|
3807
4124
|
offlineQueue.dispose();
|
|
3808
4125
|
networkMonitor.dispose();
|
|
3809
4126
|
campaignGateService.clearPreloaded();
|
|
3810
4127
|
this.cleanupNetworkRecovery();
|
|
4128
|
+
this.sessionFlagsMap.clear();
|
|
3811
4129
|
this.apiClient = null;
|
|
3812
4130
|
this.config = null;
|
|
3813
4131
|
this.initPromise = null;
|
|
4132
|
+
this.readyPromise = null;
|
|
4133
|
+
this.resolveReady = null;
|
|
3814
4134
|
this.pendingConfig = null;
|
|
3815
4135
|
this.initAttempts = 0;
|
|
3816
4136
|
this.paywallPresenter = null;
|
|
@@ -3819,7 +4139,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3819
4139
|
this.activeChecker = null;
|
|
3820
4140
|
this.restoreHandler = null;
|
|
3821
4141
|
this.lastOnboardingStepTimestamp = null;
|
|
3822
|
-
|
|
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;
|
|
3823
4149
|
}
|
|
3824
4150
|
getConfig() {
|
|
3825
4151
|
return this.config;
|
|
@@ -3908,6 +4234,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3908
4234
|
getPreloadedCampaign(placement) {
|
|
3909
4235
|
return campaignGateService.getPreloadedCampaign(placement);
|
|
3910
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
|
+
}
|
|
3911
4246
|
isOnline() {
|
|
3912
4247
|
return networkMonitor.isOnline();
|
|
3913
4248
|
}
|
|
@@ -3937,48 +4272,6 @@ var PaywalloClient = new PaywalloClientClass();
|
|
|
3937
4272
|
var import_react9 = require("react");
|
|
3938
4273
|
var PaywalloContext = (0, import_react9.createContext)(null);
|
|
3939
4274
|
|
|
3940
|
-
// src/utils/localization.ts
|
|
3941
|
-
var import_react_native12 = require("react-native");
|
|
3942
|
-
var currentLanguage = "pt-BR";
|
|
3943
|
-
var defaultLanguage = "pt-BR";
|
|
3944
|
-
function setCurrentLanguage(language) {
|
|
3945
|
-
currentLanguage = language;
|
|
3946
|
-
}
|
|
3947
|
-
function setDefaultLanguage(language) {
|
|
3948
|
-
defaultLanguage = language;
|
|
3949
|
-
}
|
|
3950
|
-
function getCurrentLanguage() {
|
|
3951
|
-
return currentLanguage;
|
|
3952
|
-
}
|
|
3953
|
-
function getDefaultLanguage() {
|
|
3954
|
-
return defaultLanguage;
|
|
3955
|
-
}
|
|
3956
|
-
function getLocalizedString(text) {
|
|
3957
|
-
if (!text) return "";
|
|
3958
|
-
if (typeof text === "string") return text;
|
|
3959
|
-
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
3960
|
-
}
|
|
3961
|
-
function detectDeviceLanguage() {
|
|
3962
|
-
try {
|
|
3963
|
-
if (import_react_native12.Platform.OS === "ios") {
|
|
3964
|
-
const settings = import_react_native12.NativeModules.SettingsManager?.settings;
|
|
3965
|
-
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
3966
|
-
}
|
|
3967
|
-
return import_react_native12.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
|
|
3968
|
-
} catch {
|
|
3969
|
-
return "en-US";
|
|
3970
|
-
}
|
|
3971
|
-
}
|
|
3972
|
-
function initLocalization(options) {
|
|
3973
|
-
if (options?.defaultLanguage) {
|
|
3974
|
-
defaultLanguage = options.defaultLanguage;
|
|
3975
|
-
}
|
|
3976
|
-
if (options?.detectDevice !== false) {
|
|
3977
|
-
const deviceLanguage = detectDeviceLanguage();
|
|
3978
|
-
currentLanguage = deviceLanguage;
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
4275
|
// src/hooks/usePaywallo.ts
|
|
3983
4276
|
var import_react10 = require("react");
|
|
3984
4277
|
function usePaywallo() {
|
|
@@ -4004,7 +4297,8 @@ function mapToIAPProduct(product) {
|
|
|
4004
4297
|
type: product.type === "subscription" ? "subs" : "inapp",
|
|
4005
4298
|
subscriptionPeriodAndroid: product.subscriptionPeriod,
|
|
4006
4299
|
introductoryPrice: product.introductoryPrice,
|
|
4007
|
-
freeTrialPeriodAndroid: product.freeTrialPeriod
|
|
4300
|
+
freeTrialPeriodAndroid: product.freeTrialPeriod,
|
|
4301
|
+
trialDays: product.trialDays ?? 0
|
|
4008
4302
|
};
|
|
4009
4303
|
}
|
|
4010
4304
|
function mapToFormattedProduct(product) {
|
|
@@ -4019,6 +4313,7 @@ function mapToFormattedProduct(product) {
|
|
|
4019
4313
|
subscriptionPeriod: product.subscriptionPeriod,
|
|
4020
4314
|
introductoryPrice: product.introductoryPrice,
|
|
4021
4315
|
freeTrialPeriod: product.freeTrialPeriod,
|
|
4316
|
+
trialDays: product.trialDays ?? 0,
|
|
4022
4317
|
pricePerMonth: product.pricePerMonth,
|
|
4023
4318
|
pricePerWeek: product.pricePerWeek,
|
|
4024
4319
|
pricePerDay: product.pricePerDay
|
|
@@ -4100,27 +4395,37 @@ function usePurchase() {
|
|
|
4100
4395
|
setError(null);
|
|
4101
4396
|
try {
|
|
4102
4397
|
const iapService = getIAPService();
|
|
4103
|
-
const result = await iapService.purchase(productId
|
|
4398
|
+
const result = await iapService.purchase(productId, {
|
|
4399
|
+
onValidating: () => setState("validating")
|
|
4400
|
+
});
|
|
4104
4401
|
if (result.success && result.purchase) {
|
|
4105
4402
|
setState("idle");
|
|
4106
4403
|
return {
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
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
|
+
}
|
|
4111
4411
|
};
|
|
4112
4412
|
}
|
|
4113
4413
|
if (result.error) {
|
|
4114
|
-
|
|
4115
|
-
setError(purchaseError);
|
|
4414
|
+
setError(result.error);
|
|
4116
4415
|
setState("error");
|
|
4117
|
-
|
|
4416
|
+
return { status: "failed", error: result.error };
|
|
4118
4417
|
}
|
|
4119
4418
|
setState("idle");
|
|
4120
|
-
|
|
4419
|
+
return { status: "cancelled" };
|
|
4121
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);
|
|
4122
4427
|
setState("error");
|
|
4123
|
-
|
|
4428
|
+
return { status: "failed", error: purchaseError };
|
|
4124
4429
|
}
|
|
4125
4430
|
}, []);
|
|
4126
4431
|
const restore = (0, import_react12.useCallback)(async () => {
|
|
@@ -4149,146 +4454,14 @@ function usePurchase() {
|
|
|
4149
4454
|
restore,
|
|
4150
4455
|
error,
|
|
4151
4456
|
isLoading: state === "loading",
|
|
4152
|
-
isPurchasing: state === "purchasing",
|
|
4457
|
+
isPurchasing: state === "purchasing" || state === "validating",
|
|
4458
|
+
isValidating: state === "validating",
|
|
4153
4459
|
isRestoring: state === "restoring"
|
|
4154
4460
|
};
|
|
4155
4461
|
}
|
|
4156
4462
|
|
|
4157
4463
|
// src/hooks/useSubscription.ts
|
|
4158
4464
|
var import_react13 = require("react");
|
|
4159
|
-
|
|
4160
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
4161
|
-
var import_react_native13 = require("react-native");
|
|
4162
|
-
var SubscriptionManagerClass = class {
|
|
4163
|
-
constructor() {
|
|
4164
|
-
this.config = null;
|
|
4165
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
4166
|
-
this.userId = null;
|
|
4167
|
-
this.cache = new SubscriptionCache();
|
|
4168
|
-
}
|
|
4169
|
-
init(config) {
|
|
4170
|
-
this.config = config;
|
|
4171
|
-
if (config.cacheTTL) {
|
|
4172
|
-
this.cache.setTTL(config.cacheTTL);
|
|
4173
|
-
}
|
|
4174
|
-
}
|
|
4175
|
-
setUserId(userId) {
|
|
4176
|
-
if (this.userId !== userId) {
|
|
4177
|
-
this.userId = userId;
|
|
4178
|
-
void this.cache.invalidate();
|
|
4179
|
-
}
|
|
4180
|
-
}
|
|
4181
|
-
async hasActiveSubscription(forceRefresh = false) {
|
|
4182
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4183
|
-
return status.hasActiveSubscription;
|
|
4184
|
-
}
|
|
4185
|
-
async getSubscription(forceRefresh = false) {
|
|
4186
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4187
|
-
return status.subscription;
|
|
4188
|
-
}
|
|
4189
|
-
async getEntitlements(forceRefresh = false) {
|
|
4190
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4191
|
-
return status.entitlements;
|
|
4192
|
-
}
|
|
4193
|
-
async getSubscriptionStatus(forceRefresh = false) {
|
|
4194
|
-
if (!this.config) {
|
|
4195
|
-
return this.getEmptyStatus();
|
|
4196
|
-
}
|
|
4197
|
-
if (!forceRefresh) {
|
|
4198
|
-
const cached = await this.cache.get();
|
|
4199
|
-
if (cached && !cached.isStale) {
|
|
4200
|
-
return cached.data;
|
|
4201
|
-
}
|
|
4202
|
-
}
|
|
4203
|
-
try {
|
|
4204
|
-
const status = await this.fetchSubscriptionStatus();
|
|
4205
|
-
await this.cache.set(status);
|
|
4206
|
-
this.notifyListeners(status);
|
|
4207
|
-
return status;
|
|
4208
|
-
} catch (error) {
|
|
4209
|
-
this.log("Failed to fetch subscription status", error);
|
|
4210
|
-
const cached = await this.cache.get();
|
|
4211
|
-
if (cached) {
|
|
4212
|
-
return cached.data;
|
|
4213
|
-
}
|
|
4214
|
-
return this.getEmptyStatus();
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
async restorePurchases() {
|
|
4218
|
-
if (!this.config) {
|
|
4219
|
-
throw new SessionError(
|
|
4220
|
-
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
4221
|
-
"SubscriptionManager not initialized"
|
|
4222
|
-
);
|
|
4223
|
-
}
|
|
4224
|
-
await this.cache.invalidate();
|
|
4225
|
-
return this.getSubscriptionStatus(true);
|
|
4226
|
-
}
|
|
4227
|
-
async fetchSubscriptionStatus() {
|
|
4228
|
-
if (!this.config) {
|
|
4229
|
-
return this.getEmptyStatus();
|
|
4230
|
-
}
|
|
4231
|
-
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
4232
|
-
if (this.userId) {
|
|
4233
|
-
url.searchParams.set("userId", this.userId);
|
|
4234
|
-
}
|
|
4235
|
-
url.searchParams.set("platform", import_react_native13.Platform.OS);
|
|
4236
|
-
const response = await fetch(url.toString(), {
|
|
4237
|
-
method: "GET",
|
|
4238
|
-
headers: {
|
|
4239
|
-
"Content-Type": "application/json",
|
|
4240
|
-
"X-App-Key": this.config.appKey
|
|
4241
|
-
}
|
|
4242
|
-
});
|
|
4243
|
-
if (!response.ok) {
|
|
4244
|
-
throw new SessionError(
|
|
4245
|
-
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
4246
|
-
`Failed to fetch subscription status: ${response.status}`
|
|
4247
|
-
);
|
|
4248
|
-
}
|
|
4249
|
-
const data = await response.json();
|
|
4250
|
-
if (data.subscription) {
|
|
4251
|
-
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
4252
|
-
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
4253
|
-
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
4254
|
-
if (data.subscription.cancellationDate) {
|
|
4255
|
-
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
4256
|
-
}
|
|
4257
|
-
if (data.subscription.gracePeriodExpiresAt) {
|
|
4258
|
-
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
4259
|
-
}
|
|
4260
|
-
}
|
|
4261
|
-
return data;
|
|
4262
|
-
}
|
|
4263
|
-
getEmptyStatus() {
|
|
4264
|
-
return {
|
|
4265
|
-
hasActiveSubscription: false,
|
|
4266
|
-
subscription: null,
|
|
4267
|
-
entitlements: []
|
|
4268
|
-
};
|
|
4269
|
-
}
|
|
4270
|
-
addListener(listener) {
|
|
4271
|
-
this.listeners.add(listener);
|
|
4272
|
-
return () => this.listeners.delete(listener);
|
|
4273
|
-
}
|
|
4274
|
-
notifyListeners(status) {
|
|
4275
|
-
for (const listener of this.listeners) {
|
|
4276
|
-
listener(status);
|
|
4277
|
-
}
|
|
4278
|
-
}
|
|
4279
|
-
async onPurchaseComplete() {
|
|
4280
|
-
await this.cache.invalidate();
|
|
4281
|
-
await this.getSubscriptionStatus(true);
|
|
4282
|
-
}
|
|
4283
|
-
log(message, data) {
|
|
4284
|
-
if (this.config?.debug) {
|
|
4285
|
-
console.warn(`[SubscriptionManager] ${message}`, data ?? "");
|
|
4286
|
-
}
|
|
4287
|
-
}
|
|
4288
|
-
};
|
|
4289
|
-
var subscriptionManager = new SubscriptionManagerClass();
|
|
4290
|
-
|
|
4291
|
-
// src/hooks/useSubscription.ts
|
|
4292
4465
|
function useSubscription() {
|
|
4293
4466
|
const [status, setStatus] = (0, import_react13.useState)(null);
|
|
4294
4467
|
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
@@ -4405,10 +4578,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
4405
4578
|
function isCacheEntryValid(entry) {
|
|
4406
4579
|
return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
|
|
4407
4580
|
}
|
|
4408
|
-
function
|
|
4581
|
+
function getCacheEntry(cache, placement) {
|
|
4409
4582
|
const entry = cache.get(placement);
|
|
4410
4583
|
if (!isCacheEntryValid(entry)) return null;
|
|
4411
|
-
cache.delete(placement);
|
|
4412
4584
|
return entry ?? null;
|
|
4413
4585
|
}
|
|
4414
4586
|
|
|
@@ -4421,7 +4593,7 @@ function useCampaignPreload() {
|
|
|
4421
4593
|
[]
|
|
4422
4594
|
);
|
|
4423
4595
|
const consumePreloadedCampaign = (0, import_react14.useCallback)(
|
|
4424
|
-
(placement) =>
|
|
4596
|
+
(placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
|
|
4425
4597
|
[]
|
|
4426
4598
|
);
|
|
4427
4599
|
const preloadCampaign = (0, import_react14.useCallback)(
|
|
@@ -4469,6 +4641,7 @@ function useCampaignPreload() {
|
|
|
4469
4641
|
campaignId: campaign.campaignId,
|
|
4470
4642
|
variantKey: campaign.variantKey
|
|
4471
4643
|
});
|
|
4644
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4472
4645
|
return { success: true };
|
|
4473
4646
|
} catch (error) {
|
|
4474
4647
|
return {
|
|
@@ -4503,7 +4676,6 @@ function useProductLoader() {
|
|
|
4503
4676
|
for (const p of loaded) map.set(p.productId, p);
|
|
4504
4677
|
setProducts(map);
|
|
4505
4678
|
} catch (error) {
|
|
4506
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
|
|
4507
4679
|
} finally {
|
|
4508
4680
|
setIsLoadingProducts(false);
|
|
4509
4681
|
}
|
|
@@ -4513,7 +4685,7 @@ function useProductLoader() {
|
|
|
4513
4685
|
|
|
4514
4686
|
// src/hooks/usePurchaseOrchestrator.ts
|
|
4515
4687
|
var import_react16 = require("react");
|
|
4516
|
-
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
|
|
4688
|
+
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
|
|
4517
4689
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
4518
4690
|
const handlePreloadedPurchase = (0, import_react16.useCallback)(
|
|
4519
4691
|
async (productId) => {
|
|
@@ -4538,17 +4710,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4538
4710
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4539
4711
|
await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4540
4712
|
}
|
|
4713
|
+
invalidateSubscriptionCache();
|
|
4541
4714
|
setShowingPreloadedWebView(false);
|
|
4542
4715
|
clearPreloadedWebView();
|
|
4543
4716
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4544
4717
|
} catch (error) {
|
|
4545
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
|
|
4546
4718
|
setShowingPreloadedWebView(false);
|
|
4547
4719
|
clearPreloadedWebView();
|
|
4548
4720
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4549
4721
|
}
|
|
4550
4722
|
},
|
|
4551
|
-
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
|
|
4723
|
+
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
|
|
4552
4724
|
);
|
|
4553
4725
|
const handlePreloadedRestore = (0, import_react16.useCallback)(async () => {
|
|
4554
4726
|
try {
|
|
@@ -4558,16 +4730,16 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4558
4730
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
4559
4731
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4560
4732
|
}
|
|
4733
|
+
invalidateSubscriptionCache();
|
|
4561
4734
|
setShowingPreloadedWebView(false);
|
|
4562
4735
|
clearPreloadedWebView();
|
|
4563
4736
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4564
4737
|
} catch (error) {
|
|
4565
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
|
|
4566
4738
|
setShowingPreloadedWebView(false);
|
|
4567
4739
|
clearPreloadedWebView();
|
|
4568
4740
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4569
4741
|
}
|
|
4570
|
-
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
|
|
4742
|
+
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
|
|
4571
4743
|
return { handlePreloadedPurchase, handlePreloadedRestore };
|
|
4572
4744
|
}
|
|
4573
4745
|
|
|
@@ -4579,7 +4751,6 @@ function toDomainSubscriptionInfo(sub) {
|
|
|
4579
4751
|
return {
|
|
4580
4752
|
id: "",
|
|
4581
4753
|
productId: sub.productId,
|
|
4582
|
-
// The hook's SubscriptionStatus is a subset of the domain's — cast is safe
|
|
4583
4754
|
status: sub.status,
|
|
4584
4755
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4585
4756
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4597,31 +4768,32 @@ function toDomainResponse(status) {
|
|
|
4597
4768
|
entitlements: []
|
|
4598
4769
|
};
|
|
4599
4770
|
}
|
|
4600
|
-
|
|
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) {
|
|
4601
4778
|
try {
|
|
4602
|
-
const cached = await subscriptionCache.get();
|
|
4779
|
+
const cached = await subscriptionCache.get(distinctId);
|
|
4603
4780
|
if (!cached) return false;
|
|
4604
4781
|
const sub = cached.data.subscription;
|
|
4605
|
-
|
|
4606
|
-
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4607
|
-
const hasValidExpiry = sub.expiresAt.getTime() === 0 || sub.expiresAt > /* @__PURE__ */ new Date();
|
|
4608
|
-
return hasValidExpiry;
|
|
4782
|
+
return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
|
|
4609
4783
|
} catch {
|
|
4610
4784
|
return false;
|
|
4611
4785
|
}
|
|
4612
4786
|
}
|
|
4613
4787
|
function isActiveCacheEntry(entry) {
|
|
4614
|
-
|
|
4615
|
-
if (!ACTIVE_STATUSES.includes(entry.data.status)) return false;
|
|
4616
|
-
if (entry.data.expiresAt && entry.data.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4617
|
-
return true;
|
|
4788
|
+
return isSubscriptionStillActive(entry.data);
|
|
4618
4789
|
}
|
|
4619
4790
|
function useSubscriptionSync() {
|
|
4620
4791
|
const [subscription, setSubscription] = (0, import_react17.useState)(null);
|
|
4621
4792
|
const cacheRef = (0, import_react17.useRef)(null);
|
|
4622
4793
|
const invalidateCache = (0, import_react17.useCallback)(() => {
|
|
4623
4794
|
cacheRef.current = null;
|
|
4624
|
-
|
|
4795
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4796
|
+
if (distinctId) void subscriptionCache.invalidate(distinctId);
|
|
4625
4797
|
}, []);
|
|
4626
4798
|
const fetchAndCache = (0, import_react17.useCallback)(async () => {
|
|
4627
4799
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4634,7 +4806,7 @@ function useSubscriptionSync() {
|
|
|
4634
4806
|
} : null;
|
|
4635
4807
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4636
4808
|
setSubscription(sub);
|
|
4637
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4809
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4638
4810
|
return sub;
|
|
4639
4811
|
}, []);
|
|
4640
4812
|
const hasActiveSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4646,23 +4818,23 @@ function useSubscriptionSync() {
|
|
|
4646
4818
|
return isActiveCacheEntry(cached);
|
|
4647
4819
|
}
|
|
4648
4820
|
try {
|
|
4649
|
-
const persisted = await subscriptionCache.get();
|
|
4650
|
-
if (persisted) {
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
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) {
|
|
4660
4834
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4661
4835
|
setSubscription(sub);
|
|
4662
|
-
return
|
|
4836
|
+
return true;
|
|
4663
4837
|
}
|
|
4664
|
-
void fetchAndCache();
|
|
4665
|
-
return persisted.data.hasActiveSubscription;
|
|
4666
4838
|
}
|
|
4667
4839
|
} catch {
|
|
4668
4840
|
}
|
|
@@ -4674,12 +4846,12 @@ function useSubscriptionSync() {
|
|
|
4674
4846
|
} : null;
|
|
4675
4847
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4676
4848
|
setSubscription(sub);
|
|
4677
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4849
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4678
4850
|
return status.hasActiveSubscription;
|
|
4679
4851
|
} catch {
|
|
4680
|
-
return await resolveOfflineFromCache();
|
|
4852
|
+
return await resolveOfflineFromCache(distinctId);
|
|
4681
4853
|
}
|
|
4682
|
-
}, [
|
|
4854
|
+
}, []);
|
|
4683
4855
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
4684
4856
|
const apiClient = PaywalloClient.getApiClient();
|
|
4685
4857
|
const distinctId = PaywalloClient.getDistinctId();
|
|
@@ -4690,7 +4862,7 @@ function useSubscriptionSync() {
|
|
|
4690
4862
|
return await fetchAndCache();
|
|
4691
4863
|
} catch {
|
|
4692
4864
|
try {
|
|
4693
|
-
const persisted = await subscriptionCache.get();
|
|
4865
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4694
4866
|
if (!persisted?.data.subscription) return null;
|
|
4695
4867
|
const s = persisted.data.subscription;
|
|
4696
4868
|
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
@@ -4721,13 +4893,38 @@ function PaywalloProvider({ children, config }) {
|
|
|
4721
4893
|
const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
|
|
4722
4894
|
const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
|
|
4723
4895
|
const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4724
|
-
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]);
|
|
4725
4921
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4726
4922
|
preloadedWebView,
|
|
4727
4923
|
resolvePreloadedPaywall,
|
|
4728
4924
|
clearPreloadedWebView,
|
|
4729
4925
|
setShowingPreloadedWebView,
|
|
4730
|
-
presentedAtRef
|
|
4926
|
+
presentedAtRef,
|
|
4927
|
+
invalidateCache
|
|
4731
4928
|
);
|
|
4732
4929
|
const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react18.useState)(false);
|
|
4733
4930
|
const handlePreloadedPurchase = (0, import_react18.useCallback)(async (productId) => {
|
|
@@ -4740,13 +4937,19 @@ function PaywalloProvider({ children, config }) {
|
|
|
4740
4937
|
}, [rawPreloadedPurchase]);
|
|
4741
4938
|
(0, import_react18.useEffect)(() => {
|
|
4742
4939
|
initLocalization({ detectDevice: true });
|
|
4743
|
-
PaywalloClient.init(config).then(() => {
|
|
4940
|
+
PaywalloClient.init(config).then(async () => {
|
|
4744
4941
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
4745
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
|
+
}
|
|
4746
4949
|
}).catch((err) => {
|
|
4747
4950
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
4748
4951
|
});
|
|
4749
|
-
}, [config]);
|
|
4952
|
+
}, [config, preloadCampaign]);
|
|
4750
4953
|
const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
|
|
4751
4954
|
const api = PaywalloClient.getApiClient();
|
|
4752
4955
|
return api ? api.getPaywall(p) : null;
|
|
@@ -4845,28 +5048,54 @@ function PaywalloProvider({ children, config }) {
|
|
|
4845
5048
|
setActivePaywall(null);
|
|
4846
5049
|
}
|
|
4847
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]);
|
|
4848
5078
|
(0, import_react18.useEffect)(() => {
|
|
4849
5079
|
if (!isInitialized) return;
|
|
4850
5080
|
const onEmergency = async (id) => {
|
|
4851
5081
|
try {
|
|
4852
|
-
await
|
|
5082
|
+
await bridgePaywallPresenter(id);
|
|
4853
5083
|
} catch {
|
|
4854
5084
|
}
|
|
4855
5085
|
};
|
|
4856
|
-
PaywalloClient.registerPaywallPresenter(
|
|
5086
|
+
PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
|
|
4857
5087
|
PaywalloClient.registerSubscriptionGetter(getSubscription);
|
|
4858
5088
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
4859
5089
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
4860
5090
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
4861
|
-
}, [isInitialized,
|
|
4862
|
-
|
|
4863
|
-
if (activePaywall) setPreloadedWebView(null);
|
|
4864
|
-
}, [activePaywall, setPreloadedWebView]);
|
|
5091
|
+
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5092
|
+
const isReady = isInitialized && !isLoadingProducts;
|
|
4865
5093
|
const noOp = (0, import_react18.useCallback)(() => {
|
|
4866
5094
|
}, []);
|
|
4867
5095
|
const contextValue = (0, import_react18.useMemo)(() => ({
|
|
4868
5096
|
config: PaywalloClient.getConfig(),
|
|
4869
5097
|
isInitialized,
|
|
5098
|
+
isReady,
|
|
4870
5099
|
initError,
|
|
4871
5100
|
distinctId,
|
|
4872
5101
|
subscription,
|
|
@@ -4882,6 +5111,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4882
5111
|
refreshProducts
|
|
4883
5112
|
}), [
|
|
4884
5113
|
isInitialized,
|
|
5114
|
+
isReady,
|
|
4885
5115
|
initError,
|
|
4886
5116
|
distinctId,
|
|
4887
5117
|
subscription,
|
|
@@ -4896,20 +5126,16 @@ function PaywalloProvider({ children, config }) {
|
|
|
4896
5126
|
getPaywallConfig,
|
|
4897
5127
|
refreshProducts
|
|
4898
5128
|
]);
|
|
4899
|
-
|
|
4900
|
-
const slideAnim = (0, import_react18.useRef)(new import_react_native14.Animated.Value(SCREEN_HEIGHT3)).current;
|
|
4901
|
-
(0, import_react18.useEffect)(() => {
|
|
5129
|
+
(0, import_react18.useLayoutEffect)(() => {
|
|
4902
5130
|
if (showingPreloadedWebView) {
|
|
4903
5131
|
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
4904
|
-
import_react_native14.Animated.timing(slideAnim, { toValue: 0, duration:
|
|
4905
|
-
} else {
|
|
4906
|
-
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();
|
|
4907
5133
|
}
|
|
4908
5134
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
|
|
4909
5135
|
const preloadStyle = [
|
|
4910
5136
|
styles3.preloadOverlay,
|
|
4911
5137
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
4912
|
-
{ transform: [{ translateY:
|
|
5138
|
+
showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
|
|
4913
5139
|
];
|
|
4914
5140
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
4915
5141
|
children,
|
|
@@ -4922,6 +5148,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4922
5148
|
primaryProductId: preloadedWebView.primaryProductId,
|
|
4923
5149
|
secondaryProductId: preloadedWebView.secondaryProductId,
|
|
4924
5150
|
isPurchasing: isPreloadPurchasing,
|
|
5151
|
+
webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
|
|
4925
5152
|
onReady: handlePreloadedWebViewReady,
|
|
4926
5153
|
onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
|
|
4927
5154
|
onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
|
|
@@ -4944,8 +5171,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4944
5171
|
}
|
|
4945
5172
|
var styles3 = import_react_native14.StyleSheet.create({
|
|
4946
5173
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
4947
|
-
hidden: { zIndex: -1 },
|
|
4948
|
-
visible: { zIndex: 9999 }
|
|
5174
|
+
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5175
|
+
visible: { zIndex: 9999, opacity: 1 }
|
|
4949
5176
|
});
|
|
4950
5177
|
|
|
4951
5178
|
// src/utils/productVariableResolver.ts
|