@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.mjs
CHANGED
|
@@ -145,9 +145,6 @@ var SecureStorage = class {
|
|
|
145
145
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
146
146
|
constructor(_debug = false) {
|
|
147
147
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
|
|
150
|
-
*/
|
|
151
148
|
async get(key) {
|
|
152
149
|
const keychainValue = await this.getFromKeychain(key);
|
|
153
150
|
if (keychainValue) {
|
|
@@ -163,9 +160,6 @@ var SecureStorage = class {
|
|
|
163
160
|
return null;
|
|
164
161
|
}
|
|
165
162
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Set a value in both Keychain (if available) and AsyncStorage.
|
|
168
|
-
*/
|
|
169
163
|
async set(key, value) {
|
|
170
164
|
try {
|
|
171
165
|
await Promise.all([
|
|
@@ -229,11 +223,13 @@ var secureStorage = new SecureStorage();
|
|
|
229
223
|
|
|
230
224
|
// src/domains/identity/IdentityManager.ts
|
|
231
225
|
var DEVICE_ID_KEY = "device_id";
|
|
226
|
+
var ANON_ID_KEY = "anon_id";
|
|
232
227
|
var USER_EMAIL_KEY = "user_email";
|
|
233
228
|
var USER_PROPERTIES_KEY = "user_properties";
|
|
234
229
|
var IdentityManager = class {
|
|
235
230
|
constructor() {
|
|
236
231
|
this.deviceId = null;
|
|
232
|
+
this.anonId = null;
|
|
237
233
|
this.email = null;
|
|
238
234
|
this.properties = {};
|
|
239
235
|
this.apiClient = null;
|
|
@@ -260,23 +256,23 @@ var IdentityManager = class {
|
|
|
260
256
|
try {
|
|
261
257
|
if (DeviceInfo) {
|
|
262
258
|
this.deviceId = await DeviceInfo.getUniqueId();
|
|
263
|
-
this.log("Got device ID from DeviceInfo:", this.deviceId);
|
|
264
259
|
} else {
|
|
265
260
|
this.deviceId = generateUUID();
|
|
266
|
-
this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
|
|
267
261
|
}
|
|
268
262
|
} catch {
|
|
269
263
|
this.deviceId = generateUUID();
|
|
270
|
-
this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
|
|
271
264
|
}
|
|
272
265
|
const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
|
|
273
266
|
if (!stored) {
|
|
274
|
-
this.log("Failed to persist device ID to secure storage, using fallback");
|
|
275
267
|
await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
|
|
276
|
-
this.log("Fallback also failed \u2014 device ID only in memory");
|
|
277
268
|
});
|
|
278
269
|
}
|
|
279
270
|
}
|
|
271
|
+
if (!this.anonId) {
|
|
272
|
+
this.anonId = "$paywallo_anon:" + generateUUID();
|
|
273
|
+
await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
|
|
274
|
+
});
|
|
275
|
+
}
|
|
280
276
|
this.initialized = true;
|
|
281
277
|
return this.deviceId ?? "";
|
|
282
278
|
}
|
|
@@ -300,21 +296,18 @@ var IdentityManager = class {
|
|
|
300
296
|
this.properties = { ...this.properties, ...properties };
|
|
301
297
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
302
298
|
}
|
|
303
|
-
if (this.apiClient && this.
|
|
299
|
+
if (this.apiClient && this.anonId) {
|
|
304
300
|
try {
|
|
305
|
-
await this.apiClient.identify(this.
|
|
306
|
-
|
|
307
|
-
} catch (error) {
|
|
308
|
-
this.log("Failed to update identity on server:", error);
|
|
301
|
+
await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
|
|
302
|
+
} catch {
|
|
309
303
|
}
|
|
310
304
|
}
|
|
311
305
|
}
|
|
312
306
|
getDistinctId() {
|
|
313
|
-
if (!this.initialized || !this.
|
|
314
|
-
if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
|
|
307
|
+
if (!this.initialized || !this.anonId) {
|
|
315
308
|
return "";
|
|
316
309
|
}
|
|
317
|
-
return this.
|
|
310
|
+
return this.anonId;
|
|
318
311
|
}
|
|
319
312
|
getDeviceId() {
|
|
320
313
|
return this.deviceId;
|
|
@@ -329,24 +322,22 @@ var IdentityManager = class {
|
|
|
329
322
|
this.ensureInitialized();
|
|
330
323
|
this.properties = { ...this.properties, ...properties };
|
|
331
324
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
332
|
-
if (this.apiClient && this.
|
|
325
|
+
if (this.apiClient && this.anonId) {
|
|
333
326
|
try {
|
|
334
|
-
await this.apiClient.identify(this.
|
|
335
|
-
} catch
|
|
336
|
-
this.log("Failed to update properties on server:", error);
|
|
327
|
+
await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
|
|
328
|
+
} catch {
|
|
337
329
|
}
|
|
338
330
|
}
|
|
339
331
|
}
|
|
340
332
|
async reset() {
|
|
333
|
+
const newAnonId = "$paywallo_anon:" + generateUUID();
|
|
334
|
+
this.anonId = newAnonId;
|
|
341
335
|
this.email = null;
|
|
342
336
|
this.properties = {};
|
|
343
|
-
this.initialized = false;
|
|
344
|
-
this.apiClient = null;
|
|
345
|
-
this.deviceId = null;
|
|
346
337
|
await Promise.all([
|
|
338
|
+
this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
|
|
347
339
|
this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
|
|
348
|
-
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
349
|
-
this.secureStorage?.remove("device_id_fallback") ?? Promise.resolve(false)
|
|
340
|
+
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
350
341
|
]);
|
|
351
342
|
}
|
|
352
343
|
getState() {
|
|
@@ -363,21 +354,23 @@ var IdentityManager = class {
|
|
|
363
354
|
if (!this.secureStorage) {
|
|
364
355
|
return;
|
|
365
356
|
}
|
|
366
|
-
const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
357
|
+
const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
367
358
|
this.secureStorage.get(DEVICE_ID_KEY),
|
|
359
|
+
this.secureStorage.get(ANON_ID_KEY),
|
|
368
360
|
this.secureStorage.get(USER_EMAIL_KEY),
|
|
369
361
|
this.secureStorage.get(USER_PROPERTIES_KEY)
|
|
370
362
|
]);
|
|
371
363
|
if (storedDeviceId) {
|
|
372
364
|
this.deviceId = storedDeviceId;
|
|
373
|
-
this.log("Loaded device ID from storage:", this.deviceId);
|
|
374
365
|
} else {
|
|
375
366
|
const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
|
|
376
367
|
if (fallbackDeviceId) {
|
|
377
368
|
this.deviceId = fallbackDeviceId;
|
|
378
|
-
this.log("Loaded device ID from fallback storage:", this.deviceId);
|
|
379
369
|
}
|
|
380
370
|
}
|
|
371
|
+
if (storedAnonId) {
|
|
372
|
+
this.anonId = storedAnonId;
|
|
373
|
+
}
|
|
381
374
|
if (storedEmail) {
|
|
382
375
|
this.email = storedEmail;
|
|
383
376
|
}
|
|
@@ -397,11 +390,6 @@ var IdentityManager = class {
|
|
|
397
390
|
);
|
|
398
391
|
}
|
|
399
392
|
}
|
|
400
|
-
log(...args) {
|
|
401
|
-
if (this.debug) {
|
|
402
|
-
console.warn("[Paywallo Identity]", ...args);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
393
|
};
|
|
406
394
|
var identityManager = new IdentityManager();
|
|
407
395
|
|
|
@@ -486,7 +474,6 @@ var nativeStoreKit = {
|
|
|
486
474
|
isAvailable,
|
|
487
475
|
async getProducts(productIds) {
|
|
488
476
|
if (!PaywalloStoreKitNative) {
|
|
489
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
|
|
490
477
|
return [];
|
|
491
478
|
}
|
|
492
479
|
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
@@ -557,7 +544,6 @@ var IAPService = class {
|
|
|
557
544
|
}
|
|
558
545
|
return products;
|
|
559
546
|
} catch (error) {
|
|
560
|
-
if (this.debug) console.warn("[Paywallo][Products] StoreKit error:", error);
|
|
561
547
|
return [];
|
|
562
548
|
}
|
|
563
549
|
}
|
|
@@ -567,35 +553,46 @@ var IAPService = class {
|
|
|
567
553
|
getCachedProducts() {
|
|
568
554
|
return new Map(this.productsCache);
|
|
569
555
|
}
|
|
556
|
+
async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
|
|
557
|
+
let lastError;
|
|
558
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
559
|
+
try {
|
|
560
|
+
return await attempt();
|
|
561
|
+
} catch (error) {
|
|
562
|
+
lastError = error;
|
|
563
|
+
if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
throw lastError;
|
|
567
|
+
}
|
|
570
568
|
async validateWithServer(purchase, productId, options) {
|
|
571
569
|
const apiClient = this.getApiClient();
|
|
572
570
|
const product = this.productsCache.get(productId);
|
|
573
571
|
const platform = Platform2.OS;
|
|
574
572
|
const distinctId = PaywalloClient.getDistinctId();
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
573
|
+
return this.validateWithRetry(
|
|
574
|
+
() => apiClient.validatePurchase(
|
|
575
|
+
platform,
|
|
576
|
+
purchase.receipt,
|
|
577
|
+
purchase.productId,
|
|
578
|
+
purchase.transactionId,
|
|
579
|
+
product?.priceValue ?? 0,
|
|
580
|
+
product?.currency ?? "USD",
|
|
581
|
+
this.getDeviceCountry(),
|
|
582
|
+
distinctId,
|
|
583
|
+
options?.paywallPlacement,
|
|
584
|
+
options?.variantKey
|
|
585
|
+
)
|
|
586
586
|
);
|
|
587
|
-
return validation;
|
|
588
587
|
}
|
|
589
588
|
async finishTransaction(transactionId) {
|
|
590
589
|
try {
|
|
591
590
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
592
591
|
} catch (finishError) {
|
|
593
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Failed to finish transaction:", finishError instanceof Error ? finishError.message : finishError);
|
|
594
592
|
}
|
|
595
593
|
}
|
|
596
594
|
async purchase(productId, options) {
|
|
597
595
|
if (!nativeStoreKit.isAvailable()) {
|
|
598
|
-
if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
|
|
599
596
|
return {
|
|
600
597
|
success: false,
|
|
601
598
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
@@ -606,11 +603,11 @@ var IAPService = class {
|
|
|
606
603
|
if (!purchase) {
|
|
607
604
|
return { success: false };
|
|
608
605
|
}
|
|
606
|
+
options?.onValidating?.();
|
|
609
607
|
let validation = null;
|
|
610
608
|
try {
|
|
611
609
|
validation = await this.validateWithServer(purchase, productId, options);
|
|
612
610
|
} catch (validationError) {
|
|
613
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
|
|
614
611
|
return {
|
|
615
612
|
success: false,
|
|
616
613
|
error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
|
|
@@ -626,7 +623,6 @@ var IAPService = class {
|
|
|
626
623
|
return { success: true, purchase };
|
|
627
624
|
} catch (error) {
|
|
628
625
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
629
|
-
if (this.debug) console.warn("[Paywallo][Purchase] FAILED:", e.message);
|
|
630
626
|
return { success: false, error: e };
|
|
631
627
|
}
|
|
632
628
|
}
|
|
@@ -662,14 +658,12 @@ var IAPService = class {
|
|
|
662
658
|
try {
|
|
663
659
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
664
660
|
} catch (finishError) {
|
|
665
|
-
if (this.debug) console.warn("[Paywallo][Restore] Failed to finish tx:", tx.transactionId, finishError instanceof Error ? finishError.message : finishError);
|
|
666
661
|
}
|
|
667
662
|
validated.push(tx);
|
|
668
663
|
}
|
|
669
664
|
}
|
|
670
665
|
return validated;
|
|
671
666
|
} catch (error) {
|
|
672
|
-
if (this.debug) console.warn("[Paywallo][Restore] \u274C FAILED:", error instanceof Error ? error.message : error);
|
|
673
667
|
return [];
|
|
674
668
|
}
|
|
675
669
|
}
|
|
@@ -737,7 +731,7 @@ function usePaywallTracking() {
|
|
|
737
731
|
|
|
738
732
|
// src/domains/paywall/usePaywallActions.ts
|
|
739
733
|
var SCREEN_HEIGHT = Dimensions.get("window").height;
|
|
740
|
-
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
|
|
734
|
+
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
741
735
|
const [isPurchasing, setIsPurchasing] = useState(false);
|
|
742
736
|
const [isClosing, setIsClosing] = useState(false);
|
|
743
737
|
const slideAnim = useRef(new Animated.Value(0)).current;
|
|
@@ -746,7 +740,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
746
740
|
const handleClose = useCallback2(() => {
|
|
747
741
|
if (isClosing) return;
|
|
748
742
|
setIsClosing(true);
|
|
749
|
-
|
|
743
|
+
const animTarget = openAnim ?? slideAnim;
|
|
744
|
+
Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
|
|
750
745
|
if (paywallConfig) {
|
|
751
746
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
752
747
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -755,7 +750,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
755
750
|
setIsClosing(false);
|
|
756
751
|
onResult({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
757
752
|
});
|
|
758
|
-
}, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
753
|
+
}, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
759
754
|
const handlePurchase = useCallback2(async (productId) => {
|
|
760
755
|
if (isPurchasing) return;
|
|
761
756
|
setIsPurchasing(true);
|
|
@@ -773,7 +768,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
773
768
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
774
769
|
}
|
|
775
770
|
} catch (error) {
|
|
776
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
|
|
777
771
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
778
772
|
} finally {
|
|
779
773
|
setIsPurchasing(false);
|
|
@@ -794,7 +788,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
794
788
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
795
789
|
}
|
|
796
790
|
} catch (error) {
|
|
797
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
|
|
798
791
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
799
792
|
} finally {
|
|
800
793
|
setIsPurchasing(false);
|
|
@@ -889,7 +882,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
889
882
|
}
|
|
890
883
|
} catch (err) {
|
|
891
884
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
892
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Error loading paywall:", e.message);
|
|
893
885
|
setError(e);
|
|
894
886
|
}
|
|
895
887
|
};
|
|
@@ -898,18 +890,103 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
898
890
|
return { paywallConfig, products, error };
|
|
899
891
|
}
|
|
900
892
|
|
|
893
|
+
// src/utils/localization.ts
|
|
894
|
+
import { NativeModules as NativeModules2, Platform as Platform3 } from "react-native";
|
|
895
|
+
var currentLanguage = "pt-BR";
|
|
896
|
+
var defaultLanguage = "pt-BR";
|
|
897
|
+
function setCurrentLanguage(language) {
|
|
898
|
+
currentLanguage = language;
|
|
899
|
+
}
|
|
900
|
+
function setDefaultLanguage(language) {
|
|
901
|
+
defaultLanguage = language;
|
|
902
|
+
}
|
|
903
|
+
function getCurrentLanguage() {
|
|
904
|
+
return currentLanguage;
|
|
905
|
+
}
|
|
906
|
+
function getDefaultLanguage() {
|
|
907
|
+
return defaultLanguage;
|
|
908
|
+
}
|
|
909
|
+
function getLocalizedString(text) {
|
|
910
|
+
if (!text) return "";
|
|
911
|
+
if (typeof text === "string") return text;
|
|
912
|
+
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
913
|
+
}
|
|
914
|
+
function detectDeviceLanguage() {
|
|
915
|
+
try {
|
|
916
|
+
if (Platform3.OS === "ios") {
|
|
917
|
+
const settings = NativeModules2.SettingsManager?.settings;
|
|
918
|
+
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
919
|
+
}
|
|
920
|
+
return NativeModules2.I18nManager?.localeIdentifier ?? "en-US";
|
|
921
|
+
} catch {
|
|
922
|
+
return "en-US";
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
var SDK_STRINGS = {
|
|
926
|
+
paywallErrorTitle: {
|
|
927
|
+
"pt-BR": "Algo deu errado",
|
|
928
|
+
"pt": "Algo deu errado",
|
|
929
|
+
"en": "Something went wrong",
|
|
930
|
+
"en-US": "Something went wrong",
|
|
931
|
+
"en-GB": "Something went wrong",
|
|
932
|
+
"es": "Algo sali\xF3 mal",
|
|
933
|
+
"es-419": "Algo sali\xF3 mal"
|
|
934
|
+
},
|
|
935
|
+
paywallRetryButton: {
|
|
936
|
+
"pt-BR": "Tentar novamente",
|
|
937
|
+
"pt": "Tentar novamente",
|
|
938
|
+
"en": "Try again",
|
|
939
|
+
"en-US": "Try again",
|
|
940
|
+
"en-GB": "Try again",
|
|
941
|
+
"es": "Reintentar",
|
|
942
|
+
"es-419": "Reintentar"
|
|
943
|
+
},
|
|
944
|
+
paywallCloseButton: {
|
|
945
|
+
"pt-BR": "Fechar",
|
|
946
|
+
"pt": "Fechar",
|
|
947
|
+
"en": "Close",
|
|
948
|
+
"en-US": "Close",
|
|
949
|
+
"en-GB": "Close",
|
|
950
|
+
"es": "Cerrar",
|
|
951
|
+
"es-419": "Cerrar"
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
function getSdkString(key) {
|
|
955
|
+
const map = SDK_STRINGS[key];
|
|
956
|
+
const lang = currentLanguage;
|
|
957
|
+
if (lang in map) return map[lang];
|
|
958
|
+
const base = lang.split("-")[0];
|
|
959
|
+
if (base && base in map) return map[base];
|
|
960
|
+
if (defaultLanguage in map) return map[defaultLanguage];
|
|
961
|
+
return Object.values(map)[0];
|
|
962
|
+
}
|
|
963
|
+
function initLocalization(options) {
|
|
964
|
+
if (options?.defaultLanguage) {
|
|
965
|
+
defaultLanguage = options.defaultLanguage;
|
|
966
|
+
}
|
|
967
|
+
if (options?.detectDevice !== false) {
|
|
968
|
+
const deviceLanguage = detectDeviceLanguage();
|
|
969
|
+
currentLanguage = deviceLanguage;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
901
973
|
// src/domains/paywall/PaywallWebView.tsx
|
|
902
974
|
import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState3 } from "react";
|
|
903
975
|
import {
|
|
904
976
|
Linking,
|
|
905
977
|
NativeEventEmitter,
|
|
906
|
-
NativeModules as
|
|
978
|
+
NativeModules as NativeModules3,
|
|
907
979
|
requireNativeComponent,
|
|
908
980
|
StyleSheet,
|
|
909
981
|
View
|
|
910
982
|
} from "react-native";
|
|
911
983
|
|
|
912
984
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
985
|
+
function deriveMessageId(message) {
|
|
986
|
+
if (message.id) return message.id;
|
|
987
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
988
|
+
return `${message.type}:${payloadKey}`;
|
|
989
|
+
}
|
|
913
990
|
function parseWebViewMessage(raw) {
|
|
914
991
|
try {
|
|
915
992
|
const parsed = JSON.parse(raw);
|
|
@@ -933,7 +1010,7 @@ function buildPaywallInjectionScript(data) {
|
|
|
933
1010
|
secondaryProductId: data.secondaryProductId
|
|
934
1011
|
}
|
|
935
1012
|
};
|
|
936
|
-
return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}
|
|
1013
|
+
return `(function(){var d=${JSON.stringify(paywallData)},a=0,m=50;function t(){a++;if(window.receiveNativeMessage){window.receiveNativeMessage(d);return;}if(a<m){setTimeout(t,100);}}t();})();true;`;
|
|
937
1014
|
}
|
|
938
1015
|
function buildPurchaseStateScript(isPurchasing) {
|
|
939
1016
|
return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
|
|
@@ -951,7 +1028,7 @@ function getNativeWebView() {
|
|
|
951
1028
|
var webViewEmitter = null;
|
|
952
1029
|
function getWebViewEmitter() {
|
|
953
1030
|
if (!webViewEmitter) {
|
|
954
|
-
webViewEmitter = new NativeEventEmitter(
|
|
1031
|
+
webViewEmitter = new NativeEventEmitter(NativeModules3.PaywalloWebViewModule);
|
|
955
1032
|
}
|
|
956
1033
|
return webViewEmitter;
|
|
957
1034
|
}
|
|
@@ -969,6 +1046,7 @@ function PaywallWebView({
|
|
|
969
1046
|
primaryProductId,
|
|
970
1047
|
secondaryProductId,
|
|
971
1048
|
isPurchasing,
|
|
1049
|
+
webUrl: webUrlProp,
|
|
972
1050
|
onPurchase,
|
|
973
1051
|
onClose,
|
|
974
1052
|
onRestore,
|
|
@@ -980,8 +1058,39 @@ function PaywallWebView({
|
|
|
980
1058
|
const [scriptToInject, setScriptToInject] = useState3(void 0);
|
|
981
1059
|
const onErrorRef = useRef3(onError);
|
|
982
1060
|
onErrorRef.current = onError;
|
|
1061
|
+
const onReadyRef = useRef3(onReady);
|
|
1062
|
+
onReadyRef.current = onReady;
|
|
983
1063
|
const NativeWebView = useMemo(() => getNativeWebView(), []);
|
|
984
|
-
const
|
|
1064
|
+
const [resolvedWebUrl, setResolvedWebUrl] = useState3(
|
|
1065
|
+
() => webUrlProp ?? PaywalloClient.getWebUrl()
|
|
1066
|
+
);
|
|
1067
|
+
useEffect2(() => {
|
|
1068
|
+
if (webUrlProp !== void 0) {
|
|
1069
|
+
setResolvedWebUrl(webUrlProp ?? null);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const url = PaywalloClient.getWebUrl();
|
|
1073
|
+
if (url) {
|
|
1074
|
+
setResolvedWebUrl(url);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const interval = setInterval(() => {
|
|
1078
|
+
const u = PaywalloClient.getWebUrl();
|
|
1079
|
+
if (u) {
|
|
1080
|
+
setResolvedWebUrl(u);
|
|
1081
|
+
clearInterval(interval);
|
|
1082
|
+
}
|
|
1083
|
+
}, 200);
|
|
1084
|
+
return () => clearInterval(interval);
|
|
1085
|
+
}, [webUrlProp]);
|
|
1086
|
+
useEffect2(() => {
|
|
1087
|
+
if (!resolvedWebUrl) {
|
|
1088
|
+
onErrorRef.current?.({
|
|
1089
|
+
code: -1,
|
|
1090
|
+
description: "Paywall URL not configured. SDK may not be initialized."
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}, []);
|
|
985
1094
|
const buildAndSetInjectionScript = useCallback3(() => {
|
|
986
1095
|
if (!craftData || paywallDataSent.current) return;
|
|
987
1096
|
paywallDataSent.current = true;
|
|
@@ -992,19 +1101,21 @@ function PaywallWebView({
|
|
|
992
1101
|
secondaryProductId
|
|
993
1102
|
});
|
|
994
1103
|
setScriptToInject(script);
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const lastProcessedRef = useRef3({ type: "", time: 0 });
|
|
1104
|
+
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1105
|
+
const processedIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
998
1106
|
const handleParsedMessage = useCallback3(
|
|
999
1107
|
(message) => {
|
|
1000
|
-
const
|
|
1001
|
-
if (
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
lastProcessedRef.current = { type: message.type, time: now };
|
|
1108
|
+
const msgId = deriveMessageId(message);
|
|
1109
|
+
if (processedIdsRef.current.has(msgId)) return;
|
|
1110
|
+
processedIdsRef.current.add(msgId);
|
|
1111
|
+
setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
|
|
1005
1112
|
switch (message.type) {
|
|
1006
1113
|
case "ready":
|
|
1007
|
-
if (!paywallDataSent.current)
|
|
1114
|
+
if (!paywallDataSent.current) {
|
|
1115
|
+
buildAndSetInjectionScript();
|
|
1116
|
+
} else {
|
|
1117
|
+
onReadyRef.current?.();
|
|
1118
|
+
}
|
|
1008
1119
|
break;
|
|
1009
1120
|
case "purchase":
|
|
1010
1121
|
if (message.payload?.productId) onPurchase(message.payload.productId);
|
|
@@ -1095,7 +1206,8 @@ function PaywallWebView({
|
|
|
1095
1206
|
if (!paywallDataSent.current) return;
|
|
1096
1207
|
setScriptToInject(buildPurchaseStateScript(isPurchasing));
|
|
1097
1208
|
}, [isPurchasing]);
|
|
1098
|
-
|
|
1209
|
+
if (!resolvedWebUrl) return null;
|
|
1210
|
+
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1099
1211
|
return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsx(
|
|
1100
1212
|
NativeWebView,
|
|
1101
1213
|
{
|
|
@@ -1116,6 +1228,18 @@ var styles = StyleSheet.create({
|
|
|
1116
1228
|
// src/domains/paywall/PaywallModal.tsx
|
|
1117
1229
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1118
1230
|
var SCREEN_HEIGHT2 = Dimensions2.get("window").height;
|
|
1231
|
+
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1232
|
+
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1233
|
+
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1234
|
+
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1235
|
+
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1236
|
+
return /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
|
|
1237
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: title }),
|
|
1238
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: description }),
|
|
1239
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: retryLabel }) }),
|
|
1240
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: closeLabel }) })
|
|
1241
|
+
] }) });
|
|
1242
|
+
}
|
|
1119
1243
|
function PaywallModal({
|
|
1120
1244
|
placement,
|
|
1121
1245
|
visible,
|
|
@@ -1133,7 +1257,13 @@ function PaywallModal({
|
|
|
1133
1257
|
variantKey,
|
|
1134
1258
|
campaignId
|
|
1135
1259
|
);
|
|
1136
|
-
const
|
|
1260
|
+
const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
|
|
1261
|
+
const [modalVisible, setModalVisible] = useState4(false);
|
|
1262
|
+
const handleResult = useCallback4((result) => {
|
|
1263
|
+
setModalVisible(false);
|
|
1264
|
+
onResult(result);
|
|
1265
|
+
}, [onResult]);
|
|
1266
|
+
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
|
|
1137
1267
|
const [webViewError, setWebViewError] = useState4(null);
|
|
1138
1268
|
const [retryCount, setRetryCount] = useState4(0);
|
|
1139
1269
|
const retryCountRef = useRef4(0);
|
|
@@ -1153,9 +1283,9 @@ function PaywallModal({
|
|
|
1153
1283
|
setWebViewError(null);
|
|
1154
1284
|
setRetryCount(0);
|
|
1155
1285
|
}, []);
|
|
1156
|
-
const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
|
|
1157
1286
|
useEffect3(() => {
|
|
1158
1287
|
if (visible) {
|
|
1288
|
+
setModalVisible(true);
|
|
1159
1289
|
openAnim.setValue(SCREEN_HEIGHT2);
|
|
1160
1290
|
Animated2.timing(openAnim, {
|
|
1161
1291
|
toValue: 0,
|
|
@@ -1165,11 +1295,11 @@ function PaywallModal({
|
|
|
1165
1295
|
}).start();
|
|
1166
1296
|
}
|
|
1167
1297
|
}, [visible, openAnim]);
|
|
1168
|
-
if (!visible) return null;
|
|
1298
|
+
if (!modalVisible && !visible) return null;
|
|
1169
1299
|
return /* @__PURE__ */ jsx2(
|
|
1170
1300
|
Modal,
|
|
1171
1301
|
{
|
|
1172
|
-
visible,
|
|
1302
|
+
visible: modalVisible,
|
|
1173
1303
|
animationType: "none",
|
|
1174
1304
|
presentationStyle: "overFullScreen",
|
|
1175
1305
|
onRequestClose: handleClose,
|
|
@@ -1177,12 +1307,14 @@ function PaywallModal({
|
|
|
1177
1307
|
statusBarTranslucent: true,
|
|
1178
1308
|
children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsx2(Animated2.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.container, children: [
|
|
1179
1309
|
error && /* @__PURE__ */ jsx2(View2, { style: styles2.errorContainer, children: /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: error.message }) }),
|
|
1180
|
-
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1310
|
+
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
|
|
1311
|
+
ErrorFallback,
|
|
1312
|
+
{
|
|
1313
|
+
description: webViewError.description,
|
|
1314
|
+
onRetry: handleRetry,
|
|
1315
|
+
onClose: handleClose
|
|
1316
|
+
}
|
|
1317
|
+
) : /* @__PURE__ */ jsx2(
|
|
1186
1318
|
PaywallWebView,
|
|
1187
1319
|
{
|
|
1188
1320
|
paywallId: paywallConfig.id,
|
|
@@ -1266,8 +1398,7 @@ var PaywallErrorBoundary = class extends Component {
|
|
|
1266
1398
|
static getDerivedStateFromError() {
|
|
1267
1399
|
return { hasError: true };
|
|
1268
1400
|
}
|
|
1269
|
-
componentDidCatch(
|
|
1270
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Paywall render error:", error.message);
|
|
1401
|
+
componentDidCatch(_error) {
|
|
1271
1402
|
}
|
|
1272
1403
|
render() {
|
|
1273
1404
|
return this.state.hasError ? null : this.props.children;
|
|
@@ -1290,7 +1421,7 @@ function usePaywallContext() {
|
|
|
1290
1421
|
|
|
1291
1422
|
// src/domains/paywall/usePaywallPresenter.ts
|
|
1292
1423
|
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
|
|
1293
|
-
function usePaywallPresenter(preloadedWebView,
|
|
1424
|
+
function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
1294
1425
|
const [showingPreloadedWebView, setShowingPreloadedWebView] = useState5(false);
|
|
1295
1426
|
const resolverRef = useRef5(null);
|
|
1296
1427
|
const presentedAtRef = useRef5(Date.now());
|
|
@@ -1339,9 +1470,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1339
1470
|
};
|
|
1340
1471
|
void track();
|
|
1341
1472
|
setShowingPreloadedWebView(false);
|
|
1342
|
-
clearPreloadedWebView();
|
|
1343
1473
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
1344
|
-
}, [preloadedWebView,
|
|
1474
|
+
}, [preloadedWebView, resolvePreloadedPaywall]);
|
|
1345
1475
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1346
1476
|
}
|
|
1347
1477
|
|
|
@@ -1548,10 +1678,7 @@ var AffiliateManager = class {
|
|
|
1548
1678
|
);
|
|
1549
1679
|
}
|
|
1550
1680
|
}
|
|
1551
|
-
log(...
|
|
1552
|
-
if (this.debug) {
|
|
1553
|
-
console.warn("[AffiliateManager]", ...args);
|
|
1554
|
-
}
|
|
1681
|
+
log(..._args) {
|
|
1555
1682
|
}
|
|
1556
1683
|
};
|
|
1557
1684
|
var affiliateManager = new AffiliateManager();
|
|
@@ -1569,26 +1696,42 @@ var PaywalloAffiliate = {
|
|
|
1569
1696
|
};
|
|
1570
1697
|
|
|
1571
1698
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1572
|
-
var
|
|
1699
|
+
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1700
|
+
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1573
1701
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1702
|
+
function keyFor(distinctId) {
|
|
1703
|
+
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
1704
|
+
}
|
|
1574
1705
|
var SubscriptionCache = class {
|
|
1575
1706
|
constructor(ttl = DEFAULT_TTL) {
|
|
1576
|
-
this.memoryCache =
|
|
1707
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
1708
|
+
this.legacyCleanupDone = false;
|
|
1577
1709
|
this.ttl = ttl;
|
|
1578
1710
|
this.storage = new SecureStorage();
|
|
1579
1711
|
}
|
|
1580
|
-
async
|
|
1581
|
-
if (this.
|
|
1582
|
-
|
|
1712
|
+
async cleanupLegacyCache() {
|
|
1713
|
+
if (this.legacyCleanupDone) return;
|
|
1714
|
+
this.legacyCleanupDone = true;
|
|
1715
|
+
try {
|
|
1716
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1717
|
+
} catch {
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
async get(distinctId) {
|
|
1721
|
+
await this.cleanupLegacyCache();
|
|
1722
|
+
if (!distinctId) return null;
|
|
1723
|
+
const memEntry = this.memoryCache.get(distinctId);
|
|
1724
|
+
if (memEntry && !this.isExpired(memEntry)) {
|
|
1725
|
+
return memEntry;
|
|
1583
1726
|
}
|
|
1584
1727
|
try {
|
|
1585
|
-
const stored = await this.storage.get(
|
|
1728
|
+
const stored = await this.storage.get(keyFor(distinctId));
|
|
1586
1729
|
if (!stored) {
|
|
1587
1730
|
return null;
|
|
1588
1731
|
}
|
|
1589
1732
|
const parsed = JSON.parse(stored);
|
|
1590
1733
|
if (typeof parsed !== "object" || parsed === null || !("data" in parsed) || typeof parsed.data !== "object" || parsed.data === null || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") {
|
|
1591
|
-
await this.storage.remove(
|
|
1734
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1592
1735
|
return null;
|
|
1593
1736
|
}
|
|
1594
1737
|
const cached = parsed;
|
|
@@ -1617,32 +1760,39 @@ var SubscriptionCache = class {
|
|
|
1617
1760
|
}
|
|
1618
1761
|
cached.isStale = this.isExpired(cached);
|
|
1619
1762
|
if (!cached.isStale) {
|
|
1620
|
-
this.memoryCache
|
|
1763
|
+
this.memoryCache.set(distinctId, cached);
|
|
1621
1764
|
}
|
|
1622
1765
|
return cached;
|
|
1623
1766
|
} catch {
|
|
1624
1767
|
return null;
|
|
1625
1768
|
}
|
|
1626
1769
|
}
|
|
1627
|
-
async set(data) {
|
|
1770
|
+
async set(distinctId, data) {
|
|
1771
|
+
if (!distinctId) return;
|
|
1628
1772
|
const cached = {
|
|
1629
1773
|
data,
|
|
1630
1774
|
cachedAt: Date.now(),
|
|
1631
1775
|
isStale: false
|
|
1632
1776
|
};
|
|
1633
|
-
this.memoryCache
|
|
1777
|
+
this.memoryCache.set(distinctId, cached);
|
|
1634
1778
|
try {
|
|
1635
|
-
await this.storage.set(
|
|
1636
|
-
} catch
|
|
1637
|
-
if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
|
|
1779
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1780
|
+
} catch {
|
|
1638
1781
|
}
|
|
1639
1782
|
}
|
|
1640
|
-
async invalidate() {
|
|
1641
|
-
|
|
1783
|
+
async invalidate(distinctId) {
|
|
1784
|
+
if (!distinctId) return;
|
|
1785
|
+
this.memoryCache.delete(distinctId);
|
|
1642
1786
|
try {
|
|
1643
|
-
await this.storage.remove(
|
|
1644
|
-
} catch
|
|
1645
|
-
|
|
1787
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1788
|
+
} catch {
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
async invalidateAll() {
|
|
1792
|
+
this.memoryCache.clear();
|
|
1793
|
+
try {
|
|
1794
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1795
|
+
} catch {
|
|
1646
1796
|
}
|
|
1647
1797
|
}
|
|
1648
1798
|
isExpired(cached) {
|
|
@@ -1654,8 +1804,155 @@ var SubscriptionCache = class {
|
|
|
1654
1804
|
};
|
|
1655
1805
|
var subscriptionCache = new SubscriptionCache();
|
|
1656
1806
|
|
|
1807
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1808
|
+
import { Platform as Platform4 } from "react-native";
|
|
1809
|
+
|
|
1810
|
+
// src/domains/session/SessionError.ts
|
|
1811
|
+
var SessionError = class extends PaywalloError {
|
|
1812
|
+
constructor(code, message) {
|
|
1813
|
+
super("session", code, message);
|
|
1814
|
+
this.name = "SessionError";
|
|
1815
|
+
}
|
|
1816
|
+
};
|
|
1817
|
+
var SESSION_ERROR_CODES = {
|
|
1818
|
+
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
1819
|
+
START_FAILED: "SESSION_START_FAILED",
|
|
1820
|
+
END_FAILED: "SESSION_END_FAILED",
|
|
1821
|
+
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
1822
|
+
};
|
|
1823
|
+
|
|
1824
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1825
|
+
var SubscriptionManagerClass = class {
|
|
1826
|
+
constructor() {
|
|
1827
|
+
this.config = null;
|
|
1828
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1829
|
+
this.userId = null;
|
|
1830
|
+
this.cache = new SubscriptionCache();
|
|
1831
|
+
}
|
|
1832
|
+
cacheKey() {
|
|
1833
|
+
return this.userId ?? "__anonymous__";
|
|
1834
|
+
}
|
|
1835
|
+
init(config) {
|
|
1836
|
+
this.config = config;
|
|
1837
|
+
if (config.cacheTTL) {
|
|
1838
|
+
this.cache.setTTL(config.cacheTTL);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
setUserId(userId) {
|
|
1842
|
+
if (this.userId !== userId) {
|
|
1843
|
+
this.userId = userId;
|
|
1844
|
+
void this.cache.invalidateAll();
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
async hasActiveSubscription(forceRefresh = false) {
|
|
1848
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1849
|
+
return status.hasActiveSubscription;
|
|
1850
|
+
}
|
|
1851
|
+
async getSubscription(forceRefresh = false) {
|
|
1852
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1853
|
+
return status.subscription;
|
|
1854
|
+
}
|
|
1855
|
+
async getEntitlements(forceRefresh = false) {
|
|
1856
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1857
|
+
return status.entitlements;
|
|
1858
|
+
}
|
|
1859
|
+
async getSubscriptionStatus(forceRefresh = false) {
|
|
1860
|
+
if (!this.config) {
|
|
1861
|
+
return this.getEmptyStatus();
|
|
1862
|
+
}
|
|
1863
|
+
if (!forceRefresh) {
|
|
1864
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1865
|
+
if (cached && !cached.isStale) {
|
|
1866
|
+
return cached.data;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
try {
|
|
1870
|
+
const status = await this.fetchSubscriptionStatus();
|
|
1871
|
+
await this.cache.set(this.cacheKey(), status);
|
|
1872
|
+
this.notifyListeners(status);
|
|
1873
|
+
return status;
|
|
1874
|
+
} catch (error) {
|
|
1875
|
+
this.log("Failed to fetch subscription status", error);
|
|
1876
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1877
|
+
if (cached) {
|
|
1878
|
+
return cached.data;
|
|
1879
|
+
}
|
|
1880
|
+
return this.getEmptyStatus();
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
async restorePurchases() {
|
|
1884
|
+
if (!this.config) {
|
|
1885
|
+
throw new SessionError(
|
|
1886
|
+
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
1887
|
+
"SubscriptionManager not initialized"
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1891
|
+
return this.getSubscriptionStatus(true);
|
|
1892
|
+
}
|
|
1893
|
+
async fetchSubscriptionStatus() {
|
|
1894
|
+
if (!this.config) {
|
|
1895
|
+
return this.getEmptyStatus();
|
|
1896
|
+
}
|
|
1897
|
+
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
1898
|
+
if (this.userId) {
|
|
1899
|
+
url.searchParams.set("userId", this.userId);
|
|
1900
|
+
}
|
|
1901
|
+
url.searchParams.set("platform", Platform4.OS);
|
|
1902
|
+
const response = await fetch(url.toString(), {
|
|
1903
|
+
method: "GET",
|
|
1904
|
+
headers: {
|
|
1905
|
+
"Content-Type": "application/json",
|
|
1906
|
+
"X-App-Key": this.config.appKey
|
|
1907
|
+
}
|
|
1908
|
+
});
|
|
1909
|
+
if (!response.ok) {
|
|
1910
|
+
throw new SessionError(
|
|
1911
|
+
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
1912
|
+
`Failed to fetch subscription status: ${response.status}`
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
const data = await response.json();
|
|
1916
|
+
if (data.subscription) {
|
|
1917
|
+
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
1918
|
+
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
1919
|
+
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
1920
|
+
if (data.subscription.cancellationDate) {
|
|
1921
|
+
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
1922
|
+
}
|
|
1923
|
+
if (data.subscription.gracePeriodExpiresAt) {
|
|
1924
|
+
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
return data;
|
|
1928
|
+
}
|
|
1929
|
+
getEmptyStatus() {
|
|
1930
|
+
return {
|
|
1931
|
+
hasActiveSubscription: false,
|
|
1932
|
+
subscription: null,
|
|
1933
|
+
entitlements: []
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
addListener(listener) {
|
|
1937
|
+
this.listeners.add(listener);
|
|
1938
|
+
return () => this.listeners.delete(listener);
|
|
1939
|
+
}
|
|
1940
|
+
notifyListeners(status) {
|
|
1941
|
+
for (const listener of this.listeners) {
|
|
1942
|
+
listener(status);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
async onPurchaseComplete() {
|
|
1946
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1947
|
+
await this.getSubscriptionStatus(true);
|
|
1948
|
+
}
|
|
1949
|
+
log(..._args) {
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
var subscriptionManager = new SubscriptionManagerClass();
|
|
1953
|
+
|
|
1657
1954
|
// src/core/ApiClient.ts
|
|
1658
|
-
import { Platform as
|
|
1955
|
+
import { Platform as Platform5 } from "react-native";
|
|
1659
1956
|
|
|
1660
1957
|
// src/core/apiUrlUtils.ts
|
|
1661
1958
|
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
@@ -1887,10 +2184,7 @@ var HttpClient = class {
|
|
|
1887
2184
|
sleep(ms) {
|
|
1888
2185
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1889
2186
|
}
|
|
1890
|
-
log(
|
|
1891
|
-
if (this.config.debug) {
|
|
1892
|
-
console.warn("[HttpClient]", message, data ?? "");
|
|
1893
|
-
}
|
|
2187
|
+
log(..._args) {
|
|
1894
2188
|
}
|
|
1895
2189
|
setDebug(debug) {
|
|
1896
2190
|
this.config.debug = debug;
|
|
@@ -2034,10 +2328,7 @@ var NetworkMonitorClass = class {
|
|
|
2034
2328
|
this.initialized = false;
|
|
2035
2329
|
this.currentState = "unknown";
|
|
2036
2330
|
}
|
|
2037
|
-
log(
|
|
2038
|
-
if (this.config.debug) {
|
|
2039
|
-
console.warn("[NetworkMonitor]", message, data ?? "");
|
|
2040
|
-
}
|
|
2331
|
+
log(..._args) {
|
|
2041
2332
|
}
|
|
2042
2333
|
setDebug(debug) {
|
|
2043
2334
|
this.config.debug = debug;
|
|
@@ -2067,6 +2358,8 @@ function normalizeForComparison(obj) {
|
|
|
2067
2358
|
return "{" + pairs.join(",") + "}";
|
|
2068
2359
|
}
|
|
2069
2360
|
function findDuplicateIndex(queue, item) {
|
|
2361
|
+
const byId = queue.findIndex((existing) => existing.id === item.id);
|
|
2362
|
+
if (byId !== -1) return byId;
|
|
2070
2363
|
const normalizedBody = normalizeForComparison(item.body);
|
|
2071
2364
|
return queue.findIndex(
|
|
2072
2365
|
(existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
|
|
@@ -2135,9 +2428,9 @@ var OfflineQueueClass = class {
|
|
|
2135
2428
|
void this.saveToStorage();
|
|
2136
2429
|
}
|
|
2137
2430
|
}
|
|
2138
|
-
async enqueue(method, url, body, headers) {
|
|
2431
|
+
async enqueue(method, url, body, headers, id) {
|
|
2139
2432
|
const item = {
|
|
2140
|
-
id: generateUUID(),
|
|
2433
|
+
id: id ?? generateUUID(),
|
|
2141
2434
|
method,
|
|
2142
2435
|
url,
|
|
2143
2436
|
body,
|
|
@@ -2221,7 +2514,6 @@ var OfflineQueueClass = class {
|
|
|
2221
2514
|
this.emit({ type: "processing:end", queueSize: this.queue.length });
|
|
2222
2515
|
return { processed, failed };
|
|
2223
2516
|
}
|
|
2224
|
-
/** Returns true if the item was permanently removed (counted as failed). */
|
|
2225
2517
|
async handleItemFailure(item, error, permanent = false) {
|
|
2226
2518
|
if (permanent || item.attempts >= item.maxAttempts) {
|
|
2227
2519
|
await this.removeItem(item.id);
|
|
@@ -2283,10 +2575,7 @@ var OfflineQueueClass = class {
|
|
|
2283
2575
|
}
|
|
2284
2576
|
}
|
|
2285
2577
|
}
|
|
2286
|
-
log(
|
|
2287
|
-
if (this.config.debug) {
|
|
2288
|
-
console.warn("[OfflineQueue]", message, data ?? "");
|
|
2289
|
-
}
|
|
2578
|
+
log(..._args) {
|
|
2290
2579
|
}
|
|
2291
2580
|
setDebug(debug) {
|
|
2292
2581
|
this.config.debug = debug;
|
|
@@ -2428,10 +2717,7 @@ var QueueProcessorClass = class {
|
|
|
2428
2717
|
this.httpClient = null;
|
|
2429
2718
|
this.initialized = false;
|
|
2430
2719
|
}
|
|
2431
|
-
log(
|
|
2432
|
-
if (this.config.debug) {
|
|
2433
|
-
console.warn("[QueueProcessor]", message, data ?? "");
|
|
2434
|
-
}
|
|
2720
|
+
log(..._args) {
|
|
2435
2721
|
}
|
|
2436
2722
|
setDebug(debug) {
|
|
2437
2723
|
this.config.debug = debug;
|
|
@@ -2443,10 +2729,9 @@ var queueProcessor = new QueueProcessorClass();
|
|
|
2443
2729
|
var ApiCache = class {
|
|
2444
2730
|
constructor() {
|
|
2445
2731
|
this.CACHE_TTL = 3e5;
|
|
2446
|
-
// 5 minutes
|
|
2447
2732
|
this.CACHE_NULL_TTL = 3e4;
|
|
2448
|
-
// 30 seconds — for null/404 variants
|
|
2449
2733
|
this.MAX_SIZE = 200;
|
|
2734
|
+
this.STALE_WINDOW_MS = 864e5;
|
|
2450
2735
|
this.variantCache = /* @__PURE__ */ new Map();
|
|
2451
2736
|
this.paywallCache = /* @__PURE__ */ new Map();
|
|
2452
2737
|
this.campaignCache = /* @__PURE__ */ new Map();
|
|
@@ -2484,7 +2769,7 @@ var ApiCache = class {
|
|
|
2484
2769
|
}
|
|
2485
2770
|
getStaleVariant(key) {
|
|
2486
2771
|
const entry = this.variantCache.get(key);
|
|
2487
|
-
if (entry && Date.now() - entry.ts <
|
|
2772
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2488
2773
|
return void 0;
|
|
2489
2774
|
}
|
|
2490
2775
|
getPaywall(key) {
|
|
@@ -2496,19 +2781,19 @@ var ApiCache = class {
|
|
|
2496
2781
|
}
|
|
2497
2782
|
getStalePaywall(key) {
|
|
2498
2783
|
const entry = this.paywallCache.get(key);
|
|
2499
|
-
if (entry && Date.now() - entry.ts <
|
|
2784
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2500
2785
|
return void 0;
|
|
2501
2786
|
}
|
|
2502
2787
|
getCampaign(key) {
|
|
2503
2788
|
return this.getCached(this.campaignCache, key);
|
|
2504
2789
|
}
|
|
2505
|
-
setCampaign(key, value) {
|
|
2506
|
-
this.setCached(this.campaignCache, key, value);
|
|
2790
|
+
setCampaign(key, value, ttlOverride) {
|
|
2791
|
+
this.setCached(this.campaignCache, key, value, ttlOverride);
|
|
2507
2792
|
this.evictExpiredEntries(this.campaignCache);
|
|
2508
2793
|
}
|
|
2509
2794
|
getStaleCampaign(key) {
|
|
2510
2795
|
const entry = this.campaignCache.get(key);
|
|
2511
|
-
if (entry && Date.now() - entry.ts <
|
|
2796
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2512
2797
|
return void 0;
|
|
2513
2798
|
}
|
|
2514
2799
|
};
|
|
@@ -2527,11 +2812,12 @@ var _ApiClient = class _ApiClient {
|
|
|
2527
2812
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2528
2813
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
2529
2814
|
}
|
|
2530
|
-
// ─── Infrastructure ──────────────────────────────────────────────────────────
|
|
2531
|
-
/** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
|
|
2532
2815
|
getHttpClient() {
|
|
2533
2816
|
return this.httpClient;
|
|
2534
2817
|
}
|
|
2818
|
+
getBaseUrl() {
|
|
2819
|
+
return this.baseUrl;
|
|
2820
|
+
}
|
|
2535
2821
|
getWebUrl() {
|
|
2536
2822
|
return this.webUrl;
|
|
2537
2823
|
}
|
|
@@ -2557,7 +2843,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2557
2843
|
async clearQueue() {
|
|
2558
2844
|
await offlineQueue.clear();
|
|
2559
2845
|
}
|
|
2560
|
-
// ─── Public HTTP facade (headers injected automatically) ─────────────────────
|
|
2561
2846
|
async get(path) {
|
|
2562
2847
|
return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
|
|
2563
2848
|
}
|
|
@@ -2571,29 +2856,28 @@ var _ApiClient = class _ApiClient {
|
|
|
2571
2856
|
message,
|
|
2572
2857
|
context,
|
|
2573
2858
|
sdkVersion: SDK_VERSION,
|
|
2574
|
-
platform:
|
|
2859
|
+
platform: Platform5.OS
|
|
2575
2860
|
}, true);
|
|
2576
2861
|
} catch {
|
|
2577
2862
|
}
|
|
2578
2863
|
}
|
|
2579
|
-
// ─── Business methods ────────────────────────────────────────────────────────
|
|
2580
2864
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2581
2865
|
await this.postWithQueue("/api/v1/events", {
|
|
2582
2866
|
eventName,
|
|
2583
2867
|
distinctId,
|
|
2584
|
-
properties: { ...properties, platform:
|
|
2868
|
+
properties: { ...properties, platform: Platform5.OS },
|
|
2585
2869
|
timestamp: timestamp ?? Date.now(),
|
|
2586
2870
|
environment: this.environment.toLowerCase()
|
|
2587
2871
|
}, `event:${eventName}`);
|
|
2588
2872
|
}
|
|
2589
|
-
async identify(distinctId, properties, email) {
|
|
2590
|
-
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform:
|
|
2873
|
+
async identify(distinctId, properties, email, deviceId) {
|
|
2874
|
+
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
|
|
2591
2875
|
}
|
|
2592
2876
|
async startSession(distinctId, sessionId, platform) {
|
|
2593
2877
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2594
2878
|
distinctId,
|
|
2595
2879
|
sessionId,
|
|
2596
|
-
platform: platform ??
|
|
2880
|
+
platform: platform ?? Platform5.OS,
|
|
2597
2881
|
environment: this.environment.toLowerCase()
|
|
2598
2882
|
}, `session.start:${sessionId}`);
|
|
2599
2883
|
return { success: true };
|
|
@@ -2671,7 +2955,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2671
2955
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2672
2956
|
if (!result) {
|
|
2673
2957
|
this.log("No campaign found for placement:", placement);
|
|
2674
|
-
this.cache.setCampaign(key, null);
|
|
2958
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2675
2959
|
return null;
|
|
2676
2960
|
}
|
|
2677
2961
|
this.log("Got campaign:", placement, result.variantKey);
|
|
@@ -2684,18 +2968,44 @@ var _ApiClient = class _ApiClient {
|
|
|
2684
2968
|
return null;
|
|
2685
2969
|
}
|
|
2686
2970
|
}
|
|
2687
|
-
async
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2971
|
+
async getPrimaryCampaign(distinctId) {
|
|
2972
|
+
const key = `primary:${distinctId}`;
|
|
2973
|
+
const hit = this.cache.getCampaign(key);
|
|
2974
|
+
if (hit !== void 0) return hit;
|
|
2975
|
+
try {
|
|
2976
|
+
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2977
|
+
if (res.status === 404) {
|
|
2978
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
if (!res.ok) {
|
|
2982
|
+
this.log("Non-OK response for primary campaign:", res.status);
|
|
2983
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
2984
|
+
if (stale !== void 0) return stale;
|
|
2985
|
+
return null;
|
|
2986
|
+
}
|
|
2987
|
+
this.log("Got primary campaign:", res.data?.campaignId);
|
|
2988
|
+
this.cache.setCampaign(key, res.data);
|
|
2989
|
+
return res.data;
|
|
2990
|
+
} catch (error) {
|
|
2991
|
+
this.log("Failed to get primary campaign:", error);
|
|
2992
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
2993
|
+
if (stale !== void 0) return stale;
|
|
2994
|
+
return null;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2998
|
+
const result = await validatePurchase(
|
|
2999
|
+
this,
|
|
3000
|
+
this.appKey,
|
|
3001
|
+
platform,
|
|
3002
|
+
receipt,
|
|
3003
|
+
productId,
|
|
3004
|
+
transactionId,
|
|
3005
|
+
priceLocal,
|
|
3006
|
+
currency,
|
|
3007
|
+
country,
|
|
3008
|
+
distinctId,
|
|
2699
3009
|
paywallPlacement,
|
|
2700
3010
|
variantKey
|
|
2701
3011
|
);
|
|
@@ -2752,12 +3062,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2752
3062
|
return { value: false, flagKey };
|
|
2753
3063
|
}
|
|
2754
3064
|
}
|
|
2755
|
-
// ─── Cache-only reads ────────────────────────────────────────────────────────
|
|
2756
|
-
/**
|
|
2757
|
-
* Reads a flag variant from cache layers only — no network call.
|
|
2758
|
-
* Checks in-memory cache first, then SecureStorage.
|
|
2759
|
-
* Returns null when both layers miss.
|
|
2760
|
-
*/
|
|
2761
3065
|
async getVariantFromCache(flagKey, distinctId) {
|
|
2762
3066
|
const key = `${flagKey}:${distinctId}`;
|
|
2763
3067
|
const hit = this.cache.getVariant(key);
|
|
@@ -2789,10 +3093,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2789
3093
|
} catch {
|
|
2790
3094
|
}
|
|
2791
3095
|
}
|
|
2792
|
-
/**
|
|
2793
|
-
* POST with offline queue fallback. Enqueues when offline or on network failure.
|
|
2794
|
-
* Throws only when offlineQueueEnabled is false and the request fails.
|
|
2795
|
-
*/
|
|
2796
3096
|
async postWithQueue(url, payload, label) {
|
|
2797
3097
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
2798
3098
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -2811,11 +3111,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2811
3111
|
throw error;
|
|
2812
3112
|
}
|
|
2813
3113
|
}
|
|
2814
|
-
log(...
|
|
2815
|
-
if (this.debug) console.warn("[Paywallo API]", ...args);
|
|
3114
|
+
log(..._args) {
|
|
2816
3115
|
}
|
|
2817
3116
|
};
|
|
2818
|
-
// ─── Private helpers ─────────────────────────────────────────────────────────
|
|
2819
3117
|
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2820
3118
|
var ApiClient = _ApiClient;
|
|
2821
3119
|
|
|
@@ -2836,42 +3134,72 @@ var CAMPAIGN_ERROR_CODES = {
|
|
|
2836
3134
|
|
|
2837
3135
|
// src/domains/campaign/CampaignGateService.ts
|
|
2838
3136
|
var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3137
|
+
var PRELOAD_WAIT_MAX_MS = 2e3;
|
|
3138
|
+
var PRELOAD_WAIT_INTERVAL_MS = 100;
|
|
2839
3139
|
var CampaignGateService = class {
|
|
2840
3140
|
constructor() {
|
|
2841
3141
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3142
|
+
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
2842
3143
|
}
|
|
2843
3144
|
async getCampaign(apiClient, placement, context) {
|
|
2844
3145
|
const distinctId = identityManager.getDistinctId();
|
|
2845
3146
|
if (!distinctId) return null;
|
|
2846
3147
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2847
3148
|
}
|
|
3149
|
+
async waitForPreload(placement) {
|
|
3150
|
+
if (!this.pendingPreloads.has(placement)) return;
|
|
3151
|
+
const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
|
|
3152
|
+
await new Promise((resolve) => {
|
|
3153
|
+
const check = () => {
|
|
3154
|
+
if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
|
|
3155
|
+
resolve();
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
|
|
3159
|
+
};
|
|
3160
|
+
check();
|
|
3161
|
+
});
|
|
3162
|
+
}
|
|
2848
3163
|
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2849
|
-
|
|
3164
|
+
await this.waitForPreload(placement);
|
|
3165
|
+
const preloaded = this.getPreloadedCampaign(placement);
|
|
3166
|
+
const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
|
|
2850
3167
|
if (!campaign) {
|
|
2851
3168
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2852
3169
|
}
|
|
2853
|
-
if (!context?.forceShow
|
|
2854
|
-
|
|
3170
|
+
if (!context?.forceShow) {
|
|
3171
|
+
const isActive = await hasActive();
|
|
3172
|
+
if (isActive) {
|
|
3173
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
3174
|
+
}
|
|
2855
3175
|
}
|
|
2856
3176
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2857
3177
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2858
|
-
return { ...await paywallPresenter(
|
|
3178
|
+
return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2859
3179
|
}
|
|
2860
3180
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2861
|
-
|
|
3181
|
+
const isActive = await hasActive();
|
|
3182
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3183
|
+
if (isActive) return true;
|
|
2862
3184
|
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2863
3185
|
return r.purchased || r.restored;
|
|
2864
3186
|
}
|
|
2865
3187
|
async preloadCampaign(apiClient, placement, context) {
|
|
3188
|
+
this.pendingPreloads.add(placement);
|
|
2866
3189
|
try {
|
|
2867
3190
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2868
3191
|
if (!campaign?.paywall) {
|
|
2869
3192
|
return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
|
|
2870
3193
|
}
|
|
2871
3194
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3195
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
3196
|
+
console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3197
|
+
}
|
|
2872
3198
|
return { success: true };
|
|
2873
3199
|
} catch (error) {
|
|
2874
3200
|
return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
|
|
3201
|
+
} finally {
|
|
3202
|
+
this.pendingPreloads.delete(placement);
|
|
2875
3203
|
}
|
|
2876
3204
|
}
|
|
2877
3205
|
getPreloadedCampaign(placement) {
|
|
@@ -2885,12 +3213,13 @@ var CampaignGateService = class {
|
|
|
2885
3213
|
}
|
|
2886
3214
|
clearPreloaded() {
|
|
2887
3215
|
this.preloadedCampaigns.clear();
|
|
3216
|
+
this.pendingPreloads.clear();
|
|
2888
3217
|
}
|
|
2889
3218
|
};
|
|
2890
3219
|
var campaignGateService = new CampaignGateService();
|
|
2891
3220
|
|
|
2892
3221
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
2893
|
-
import { Platform as
|
|
3222
|
+
import { Platform as Platform6 } from "react-native";
|
|
2894
3223
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
2895
3224
|
var AdvertisingIdManager = class {
|
|
2896
3225
|
constructor() {
|
|
@@ -2901,7 +3230,7 @@ var AdvertisingIdManager = class {
|
|
|
2901
3230
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
2902
3231
|
try {
|
|
2903
3232
|
const tracking = await import("expo-tracking-transparency");
|
|
2904
|
-
if (
|
|
3233
|
+
if (Platform6.OS === "ios") {
|
|
2905
3234
|
if (!tracking.isAvailable()) {
|
|
2906
3235
|
const idfv2 = await this.collectIdfv();
|
|
2907
3236
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -2920,7 +3249,7 @@ var AdvertisingIdManager = class {
|
|
|
2920
3249
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
2921
3250
|
return this.cached;
|
|
2922
3251
|
}
|
|
2923
|
-
if (
|
|
3252
|
+
if (Platform6.OS === "android") {
|
|
2924
3253
|
const androidStatus = await tracking.getTrackingPermissionsAsync();
|
|
2925
3254
|
const optedIn = androidStatus.granted;
|
|
2926
3255
|
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
@@ -2979,14 +3308,14 @@ var FacebookIdManager = class {
|
|
|
2979
3308
|
var facebookIdManager = new FacebookIdManager();
|
|
2980
3309
|
|
|
2981
3310
|
// src/domains/identity/InstallTracker.ts
|
|
2982
|
-
import { Dimensions as Dimensions3, Platform as
|
|
3311
|
+
import { Dimensions as Dimensions3, Platform as Platform8 } from "react-native";
|
|
2983
3312
|
|
|
2984
3313
|
// src/domains/identity/InstallReferrerManager.ts
|
|
2985
|
-
import { Platform as
|
|
3314
|
+
import { Platform as Platform7 } from "react-native";
|
|
2986
3315
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
2987
3316
|
var InstallReferrerManager = class {
|
|
2988
3317
|
async getReferrer() {
|
|
2989
|
-
if (
|
|
3318
|
+
if (Platform7.OS !== "android") {
|
|
2990
3319
|
return this.emptyResult();
|
|
2991
3320
|
}
|
|
2992
3321
|
let timerId;
|
|
@@ -3048,20 +3377,6 @@ var installReferrerManager = new InstallReferrerManager();
|
|
|
3048
3377
|
// src/domains/session/SessionManager.ts
|
|
3049
3378
|
import { AppState as AppState2 } from "react-native";
|
|
3050
3379
|
|
|
3051
|
-
// src/domains/session/SessionError.ts
|
|
3052
|
-
var SessionError = class extends PaywalloError {
|
|
3053
|
-
constructor(code, message) {
|
|
3054
|
-
super("session", code, message);
|
|
3055
|
-
this.name = "SessionError";
|
|
3056
|
-
}
|
|
3057
|
-
};
|
|
3058
|
-
var SESSION_ERROR_CODES = {
|
|
3059
|
-
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
3060
|
-
START_FAILED: "SESSION_START_FAILED",
|
|
3061
|
-
END_FAILED: "SESSION_END_FAILED",
|
|
3062
|
-
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
3063
|
-
};
|
|
3064
|
-
|
|
3065
3380
|
// src/domains/session/sessionTracking.ts
|
|
3066
3381
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
3067
3382
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3289,10 +3604,7 @@ var SessionManager = class {
|
|
|
3289
3604
|
);
|
|
3290
3605
|
}
|
|
3291
3606
|
}
|
|
3292
|
-
log(...
|
|
3293
|
-
if (this.debug) {
|
|
3294
|
-
console.warn("[Paywallo Session]", ...args);
|
|
3295
|
-
}
|
|
3607
|
+
log(..._args) {
|
|
3296
3608
|
}
|
|
3297
3609
|
};
|
|
3298
3610
|
var sessionManager = new SessionManager();
|
|
@@ -3347,9 +3659,6 @@ var InstallTracker = class {
|
|
|
3347
3659
|
locale
|
|
3348
3660
|
};
|
|
3349
3661
|
} catch {
|
|
3350
|
-
if (this.debug) {
|
|
3351
|
-
console.warn("[Paywallo] Failed to collect device data for install event");
|
|
3352
|
-
}
|
|
3353
3662
|
}
|
|
3354
3663
|
const [fbAnonId, referrer, adIds] = await Promise.all([
|
|
3355
3664
|
facebookIdManager.getAnonId(),
|
|
@@ -3359,7 +3668,7 @@ var InstallTracker = class {
|
|
|
3359
3668
|
try {
|
|
3360
3669
|
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3361
3670
|
installedAt,
|
|
3362
|
-
platform:
|
|
3671
|
+
platform: Platform8.OS,
|
|
3363
3672
|
...sessionId && { sessionId },
|
|
3364
3673
|
...deviceData,
|
|
3365
3674
|
...fbAnonId && { fbAnonId },
|
|
@@ -3380,13 +3689,7 @@ var InstallTracker = class {
|
|
|
3380
3689
|
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3381
3690
|
} catch {
|
|
3382
3691
|
}
|
|
3383
|
-
|
|
3384
|
-
console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
|
|
3385
|
-
}
|
|
3386
|
-
} catch (error) {
|
|
3387
|
-
if (this.debug) {
|
|
3388
|
-
console.warn("[Paywallo] Failed to track install:", error);
|
|
3389
|
-
}
|
|
3692
|
+
} catch {
|
|
3390
3693
|
}
|
|
3391
3694
|
}
|
|
3392
3695
|
};
|
|
@@ -3396,25 +3699,13 @@ function isValidEventName(name) {
|
|
|
3396
3699
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3397
3700
|
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3398
3701
|
}
|
|
3399
|
-
var OFFLINE_ACTIVE_STATUSES = ["active", "grace_period"];
|
|
3400
|
-
async function resolveClientOfflineFromCache() {
|
|
3401
|
-
try {
|
|
3402
|
-
const cached = await subscriptionCache.get();
|
|
3403
|
-
if (!cached) return false;
|
|
3404
|
-
const sub = cached.data.subscription;
|
|
3405
|
-
if (!sub) return false;
|
|
3406
|
-
if (!OFFLINE_ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
3407
|
-
if (!sub.expiresAt) return true;
|
|
3408
|
-
return sub.expiresAt > /* @__PURE__ */ new Date();
|
|
3409
|
-
} catch {
|
|
3410
|
-
return false;
|
|
3411
|
-
}
|
|
3412
|
-
}
|
|
3413
3702
|
var _PaywalloClientClass = class _PaywalloClientClass {
|
|
3414
3703
|
constructor() {
|
|
3415
3704
|
this.config = null;
|
|
3416
3705
|
this.apiClient = null;
|
|
3417
3706
|
this.initPromise = null;
|
|
3707
|
+
this.readyPromise = null;
|
|
3708
|
+
this.resolveReady = null;
|
|
3418
3709
|
this.paywallPresenter = null;
|
|
3419
3710
|
this.campaignPresenter = null;
|
|
3420
3711
|
this.subscriptionGetter = null;
|
|
@@ -3424,29 +3715,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3424
3715
|
this.pendingConfig = null;
|
|
3425
3716
|
this.networkCleanup = null;
|
|
3426
3717
|
this.initAttempts = 0;
|
|
3718
|
+
this.autoPreloadedPlacement = null;
|
|
3719
|
+
this.autoPreloadPlacementPromise = null;
|
|
3720
|
+
this.sessionFlagsMap = /* @__PURE__ */ new Map();
|
|
3427
3721
|
}
|
|
3428
3722
|
async init(config) {
|
|
3429
|
-
if (
|
|
3723
|
+
if (!this.readyPromise) {
|
|
3724
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3725
|
+
this.resolveReady = resolve;
|
|
3726
|
+
});
|
|
3727
|
+
}
|
|
3430
3728
|
if (this.config && this.apiClient) {
|
|
3431
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3432
3729
|
return;
|
|
3433
3730
|
}
|
|
3434
3731
|
if (this.initPromise) {
|
|
3435
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3436
3732
|
await this.initPromise;
|
|
3437
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3438
3733
|
return;
|
|
3439
3734
|
}
|
|
3440
3735
|
if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
|
|
3441
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Starting fresh init...");
|
|
3442
3736
|
this.pendingConfig = config;
|
|
3443
3737
|
this.initAttempts = 0;
|
|
3444
3738
|
this.initPromise = this.initWithRetry(config);
|
|
3445
3739
|
try {
|
|
3446
3740
|
await this.initPromise;
|
|
3447
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3448
3741
|
} catch (error) {
|
|
3449
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3450
3742
|
this.initPromise = null;
|
|
3451
3743
|
this.config = null;
|
|
3452
3744
|
this.apiClient = null;
|
|
@@ -3455,6 +3747,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3455
3747
|
throw error;
|
|
3456
3748
|
}
|
|
3457
3749
|
}
|
|
3750
|
+
isReady() {
|
|
3751
|
+
return this.config !== null && this.apiClient !== null;
|
|
3752
|
+
}
|
|
3753
|
+
waitUntilReady() {
|
|
3754
|
+
if (this.isReady()) return Promise.resolve();
|
|
3755
|
+
if (!this.readyPromise) {
|
|
3756
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3757
|
+
this.resolveReady = resolve;
|
|
3758
|
+
});
|
|
3759
|
+
}
|
|
3760
|
+
return this.readyPromise;
|
|
3761
|
+
}
|
|
3458
3762
|
async initWithRetry(config) {
|
|
3459
3763
|
while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
|
|
3460
3764
|
try {
|
|
@@ -3469,7 +3773,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3469
3773
|
this.initAttempts++;
|
|
3470
3774
|
if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
|
|
3471
3775
|
const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
|
|
3472
|
-
if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
|
|
3473
3776
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3474
3777
|
}
|
|
3475
3778
|
}
|
|
@@ -3480,11 +3783,8 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3480
3783
|
this.networkCleanup = networkMonitor.addListener((state) => {
|
|
3481
3784
|
if (state !== "online") return;
|
|
3482
3785
|
if (this.config && this.apiClient) return;
|
|
3483
|
-
if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
|
|
3484
3786
|
this.initPromise = this.initWithRetry(config);
|
|
3485
|
-
this.initPromise.
|
|
3486
|
-
if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
|
|
3487
|
-
}).catch(() => {
|
|
3787
|
+
this.initPromise.catch(() => {
|
|
3488
3788
|
this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
|
|
3489
3789
|
});
|
|
3490
3790
|
});
|
|
@@ -3513,38 +3813,79 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3513
3813
|
} catch {
|
|
3514
3814
|
}
|
|
3515
3815
|
}
|
|
3816
|
+
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
3817
|
+
try {
|
|
3818
|
+
const results = await apiClient.evaluateFlags(keys, distinctId);
|
|
3819
|
+
this.sessionFlagsMap.clear();
|
|
3820
|
+
for (const key of keys) {
|
|
3821
|
+
this.sessionFlagsMap.set(key, key in results ? results[key] : null);
|
|
3822
|
+
}
|
|
3823
|
+
} catch (error) {
|
|
3824
|
+
for (const key of keys) {
|
|
3825
|
+
this.sessionFlagsMap.set(key, null);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3516
3829
|
async doInit(config) {
|
|
3517
3830
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3518
3831
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3519
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3520
3832
|
await Promise.all([
|
|
3521
3833
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3522
3834
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3523
3835
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3524
3836
|
)
|
|
3525
3837
|
]);
|
|
3526
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 1 DONE");
|
|
3527
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3528
3838
|
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3529
3839
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3530
3840
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3531
|
-
|
|
3532
|
-
|
|
3841
|
+
subscriptionManager.init({
|
|
3842
|
+
serverUrl: this.apiClient.getBaseUrl(),
|
|
3843
|
+
appKey: config.appKey,
|
|
3844
|
+
cacheTTL: config.subscriptionCacheTTL,
|
|
3845
|
+
debug: config.debug
|
|
3846
|
+
});
|
|
3533
3847
|
await Promise.all([
|
|
3534
3848
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3535
3849
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3536
3850
|
]);
|
|
3537
3851
|
const distinctId = identityManager.getDistinctId();
|
|
3538
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3539
3852
|
if (config.autoStartSession !== false) {
|
|
3540
3853
|
sessionManager.startSession().catch(() => {
|
|
3541
|
-
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
3542
3854
|
});
|
|
3543
3855
|
}
|
|
3544
3856
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3545
3857
|
});
|
|
3858
|
+
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3859
|
+
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3860
|
+
}
|
|
3546
3861
|
this.config = config;
|
|
3547
|
-
if (
|
|
3862
|
+
if (this.resolveReady) {
|
|
3863
|
+
this.resolveReady();
|
|
3864
|
+
this.resolveReady = null;
|
|
3865
|
+
}
|
|
3866
|
+
if (config.debug) console.info("[Paywallo INIT] complete");
|
|
3867
|
+
if (this.apiClient) {
|
|
3868
|
+
const apiClientRef = this.apiClient;
|
|
3869
|
+
if (config.autoPreloadCampaign) {
|
|
3870
|
+
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
3871
|
+
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
3872
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
3873
|
+
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
3874
|
+
});
|
|
3875
|
+
} else {
|
|
3876
|
+
this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
|
|
3877
|
+
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
3878
|
+
if (placement) {
|
|
3879
|
+
this.autoPreloadedPlacement = placement;
|
|
3880
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
3881
|
+
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
3882
|
+
});
|
|
3883
|
+
return placement;
|
|
3884
|
+
}
|
|
3885
|
+
return null;
|
|
3886
|
+
}).catch(() => null);
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3548
3889
|
}
|
|
3549
3890
|
async identify(options) {
|
|
3550
3891
|
this.ensureInitialized();
|
|
@@ -3554,7 +3895,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3554
3895
|
const apiClient = this.getApiClientOrThrow();
|
|
3555
3896
|
const distinctId = identityManager.getDistinctId();
|
|
3556
3897
|
if (!distinctId) {
|
|
3557
|
-
if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3558
3898
|
return;
|
|
3559
3899
|
}
|
|
3560
3900
|
if (!isValidEventName(eventName)) {
|
|
@@ -3562,25 +3902,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3562
3902
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
3563
3903
|
}
|
|
3564
3904
|
const sessionId = sessionManager.getSessionId();
|
|
3565
|
-
if (eventName === "$purchase_completed") {
|
|
3566
|
-
if (this.config?.debug) console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
|
|
3567
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3568
|
-
productId: options?.properties?.productId,
|
|
3569
|
-
priceUsd: options?.properties?.priceUsd,
|
|
3570
|
-
price: options?.properties?.price,
|
|
3571
|
-
currency: options?.properties?.currency,
|
|
3572
|
-
placement: options?.properties?.placement
|
|
3573
|
-
}));
|
|
3574
|
-
} else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
|
|
3575
|
-
if (this.config?.debug) console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
|
|
3576
|
-
distinctId: distinctId.substring(0, 8) + "...",
|
|
3577
|
-
placement: options?.properties?.placement,
|
|
3578
|
-
variantKey: options?.properties?.variantKey,
|
|
3579
|
-
timeOnPaywall: options?.properties?.timeOnPaywall
|
|
3580
|
-
}));
|
|
3581
|
-
} else {
|
|
3582
|
-
if (this.config?.debug) console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
|
|
3583
|
-
}
|
|
3584
3905
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3585
3906
|
...identityManager.getProperties(),
|
|
3586
3907
|
...options?.properties,
|
|
@@ -3591,7 +3912,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3591
3912
|
const apiClient = this.getApiClientOrThrow();
|
|
3592
3913
|
const distinctId = identityManager.getDistinctId();
|
|
3593
3914
|
if (!distinctId) {
|
|
3594
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3595
3915
|
return;
|
|
3596
3916
|
}
|
|
3597
3917
|
if (!Number.isInteger(index) || index < 0) {
|
|
@@ -3615,7 +3935,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3615
3935
|
const apiClient = this.getApiClientOrThrow();
|
|
3616
3936
|
const distinctId = identityManager.getDistinctId();
|
|
3617
3937
|
if (!distinctId) {
|
|
3618
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3619
3938
|
return;
|
|
3620
3939
|
}
|
|
3621
3940
|
if (!actionName || typeof actionName !== "string") {
|
|
@@ -3644,14 +3963,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3644
3963
|
try {
|
|
3645
3964
|
const distinctId = identityManager.getDistinctId();
|
|
3646
3965
|
if (!distinctId) {
|
|
3647
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3648
3966
|
return { variant: null };
|
|
3649
3967
|
}
|
|
3650
|
-
|
|
3651
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3652
|
-
return result;
|
|
3968
|
+
return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3653
3969
|
} catch (error) {
|
|
3654
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
3655
3970
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
3656
3971
|
return { variant: null };
|
|
3657
3972
|
}
|
|
@@ -3667,7 +3982,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3667
3982
|
const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
|
|
3668
3983
|
return result.value;
|
|
3669
3984
|
} catch (error) {
|
|
3670
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
|
|
3671
3985
|
if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
|
|
3672
3986
|
return false;
|
|
3673
3987
|
}
|
|
@@ -3690,15 +4004,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3690
4004
|
async hasActiveSubscription() {
|
|
3691
4005
|
this.ensureInitialized();
|
|
3692
4006
|
try {
|
|
3693
|
-
if (this.activeChecker)
|
|
4007
|
+
if (this.activeChecker) {
|
|
4008
|
+
return await this.activeChecker();
|
|
4009
|
+
}
|
|
3694
4010
|
const distinctId = identityManager.getDistinctId();
|
|
3695
|
-
if (!distinctId || !this.apiClient)
|
|
4011
|
+
if (!distinctId || !this.apiClient) {
|
|
4012
|
+
return false;
|
|
4013
|
+
}
|
|
3696
4014
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
3697
4015
|
return status.hasActiveSubscription;
|
|
3698
|
-
} catch {
|
|
3699
|
-
if (this.
|
|
3700
|
-
|
|
3701
|
-
return resolveClientOfflineFromCache();
|
|
4016
|
+
} catch (error) {
|
|
4017
|
+
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4018
|
+
return false;
|
|
3702
4019
|
}
|
|
3703
4020
|
}
|
|
3704
4021
|
async getSubscription() {
|
|
@@ -3711,7 +4028,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3711
4028
|
if (!status.subscription) return null;
|
|
3712
4029
|
return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
|
|
3713
4030
|
} catch {
|
|
3714
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
|
|
3715
4031
|
return null;
|
|
3716
4032
|
}
|
|
3717
4033
|
}
|
|
@@ -3722,20 +4038,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3722
4038
|
}
|
|
3723
4039
|
async reset() {
|
|
3724
4040
|
await identityManager.reset();
|
|
4041
|
+
await subscriptionCache.invalidateAll();
|
|
3725
4042
|
}
|
|
3726
4043
|
async fullReset() {
|
|
3727
4044
|
const wasDebug = this.config?.debug ?? false;
|
|
3728
4045
|
await sessionManager.endSession();
|
|
3729
4046
|
await identityManager.reset();
|
|
3730
|
-
await subscriptionCache.
|
|
4047
|
+
await subscriptionCache.invalidateAll();
|
|
3731
4048
|
queueProcessor.dispose();
|
|
3732
4049
|
offlineQueue.dispose();
|
|
3733
4050
|
networkMonitor.dispose();
|
|
3734
4051
|
campaignGateService.clearPreloaded();
|
|
3735
4052
|
this.cleanupNetworkRecovery();
|
|
4053
|
+
this.sessionFlagsMap.clear();
|
|
3736
4054
|
this.apiClient = null;
|
|
3737
4055
|
this.config = null;
|
|
3738
4056
|
this.initPromise = null;
|
|
4057
|
+
this.readyPromise = null;
|
|
4058
|
+
this.resolveReady = null;
|
|
3739
4059
|
this.pendingConfig = null;
|
|
3740
4060
|
this.initAttempts = 0;
|
|
3741
4061
|
this.paywallPresenter = null;
|
|
@@ -3744,7 +4064,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3744
4064
|
this.activeChecker = null;
|
|
3745
4065
|
this.restoreHandler = null;
|
|
3746
4066
|
this.lastOnboardingStepTimestamp = null;
|
|
3747
|
-
|
|
4067
|
+
this.autoPreloadedPlacement = null;
|
|
4068
|
+
this.autoPreloadPlacementPromise = null;
|
|
4069
|
+
}
|
|
4070
|
+
getSessionFlag(key) {
|
|
4071
|
+
if (!this.config || !this.apiClient) return null;
|
|
4072
|
+
const value = this.sessionFlagsMap.get(key);
|
|
4073
|
+
return value !== void 0 ? value : null;
|
|
3748
4074
|
}
|
|
3749
4075
|
getConfig() {
|
|
3750
4076
|
return this.config;
|
|
@@ -3833,6 +4159,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3833
4159
|
getPreloadedCampaign(placement) {
|
|
3834
4160
|
return campaignGateService.getPreloadedCampaign(placement);
|
|
3835
4161
|
}
|
|
4162
|
+
isPreloaded(placement) {
|
|
4163
|
+
return campaignGateService.getPreloadedCampaign(placement) !== null;
|
|
4164
|
+
}
|
|
4165
|
+
getAutoPreloadedPlacement() {
|
|
4166
|
+
return this.autoPreloadedPlacement;
|
|
4167
|
+
}
|
|
4168
|
+
waitForAutoPreloadedPlacement() {
|
|
4169
|
+
return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
|
|
4170
|
+
}
|
|
3836
4171
|
isOnline() {
|
|
3837
4172
|
return networkMonitor.isOnline();
|
|
3838
4173
|
}
|
|
@@ -3862,48 +4197,6 @@ var PaywalloClient = new PaywalloClientClass();
|
|
|
3862
4197
|
import { createContext as createContext2 } from "react";
|
|
3863
4198
|
var PaywalloContext = createContext2(null);
|
|
3864
4199
|
|
|
3865
|
-
// src/utils/localization.ts
|
|
3866
|
-
import { NativeModules as NativeModules3, Platform as Platform7 } from "react-native";
|
|
3867
|
-
var currentLanguage = "pt-BR";
|
|
3868
|
-
var defaultLanguage = "pt-BR";
|
|
3869
|
-
function setCurrentLanguage(language) {
|
|
3870
|
-
currentLanguage = language;
|
|
3871
|
-
}
|
|
3872
|
-
function setDefaultLanguage(language) {
|
|
3873
|
-
defaultLanguage = language;
|
|
3874
|
-
}
|
|
3875
|
-
function getCurrentLanguage() {
|
|
3876
|
-
return currentLanguage;
|
|
3877
|
-
}
|
|
3878
|
-
function getDefaultLanguage() {
|
|
3879
|
-
return defaultLanguage;
|
|
3880
|
-
}
|
|
3881
|
-
function getLocalizedString(text) {
|
|
3882
|
-
if (!text) return "";
|
|
3883
|
-
if (typeof text === "string") return text;
|
|
3884
|
-
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
3885
|
-
}
|
|
3886
|
-
function detectDeviceLanguage() {
|
|
3887
|
-
try {
|
|
3888
|
-
if (Platform7.OS === "ios") {
|
|
3889
|
-
const settings = NativeModules3.SettingsManager?.settings;
|
|
3890
|
-
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
3891
|
-
}
|
|
3892
|
-
return NativeModules3.I18nManager?.localeIdentifier ?? "en-US";
|
|
3893
|
-
} catch {
|
|
3894
|
-
return "en-US";
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
function initLocalization(options) {
|
|
3898
|
-
if (options?.defaultLanguage) {
|
|
3899
|
-
defaultLanguage = options.defaultLanguage;
|
|
3900
|
-
}
|
|
3901
|
-
if (options?.detectDevice !== false) {
|
|
3902
|
-
const deviceLanguage = detectDeviceLanguage();
|
|
3903
|
-
currentLanguage = deviceLanguage;
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
4200
|
// src/hooks/usePaywallo.ts
|
|
3908
4201
|
import { useContext as useContext2 } from "react";
|
|
3909
4202
|
function usePaywallo() {
|
|
@@ -3929,7 +4222,8 @@ function mapToIAPProduct(product) {
|
|
|
3929
4222
|
type: product.type === "subscription" ? "subs" : "inapp",
|
|
3930
4223
|
subscriptionPeriodAndroid: product.subscriptionPeriod,
|
|
3931
4224
|
introductoryPrice: product.introductoryPrice,
|
|
3932
|
-
freeTrialPeriodAndroid: product.freeTrialPeriod
|
|
4225
|
+
freeTrialPeriodAndroid: product.freeTrialPeriod,
|
|
4226
|
+
trialDays: product.trialDays ?? 0
|
|
3933
4227
|
};
|
|
3934
4228
|
}
|
|
3935
4229
|
function mapToFormattedProduct(product) {
|
|
@@ -3944,6 +4238,7 @@ function mapToFormattedProduct(product) {
|
|
|
3944
4238
|
subscriptionPeriod: product.subscriptionPeriod,
|
|
3945
4239
|
introductoryPrice: product.introductoryPrice,
|
|
3946
4240
|
freeTrialPeriod: product.freeTrialPeriod,
|
|
4241
|
+
trialDays: product.trialDays ?? 0,
|
|
3947
4242
|
pricePerMonth: product.pricePerMonth,
|
|
3948
4243
|
pricePerWeek: product.pricePerWeek,
|
|
3949
4244
|
pricePerDay: product.pricePerDay
|
|
@@ -4025,27 +4320,37 @@ function usePurchase() {
|
|
|
4025
4320
|
setError(null);
|
|
4026
4321
|
try {
|
|
4027
4322
|
const iapService = getIAPService();
|
|
4028
|
-
const result = await iapService.purchase(productId
|
|
4323
|
+
const result = await iapService.purchase(productId, {
|
|
4324
|
+
onValidating: () => setState("validating")
|
|
4325
|
+
});
|
|
4029
4326
|
if (result.success && result.purchase) {
|
|
4030
4327
|
setState("idle");
|
|
4031
4328
|
return {
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4329
|
+
status: "success",
|
|
4330
|
+
purchase: {
|
|
4331
|
+
productId: result.purchase.productId,
|
|
4332
|
+
transactionId: result.purchase.transactionId,
|
|
4333
|
+
transactionDate: result.purchase.transactionDate,
|
|
4334
|
+
transactionReceipt: result.purchase.receipt
|
|
4335
|
+
}
|
|
4036
4336
|
};
|
|
4037
4337
|
}
|
|
4038
4338
|
if (result.error) {
|
|
4039
|
-
|
|
4040
|
-
setError(purchaseError);
|
|
4339
|
+
setError(result.error);
|
|
4041
4340
|
setState("error");
|
|
4042
|
-
|
|
4341
|
+
return { status: "failed", error: result.error };
|
|
4043
4342
|
}
|
|
4044
4343
|
setState("idle");
|
|
4045
|
-
|
|
4344
|
+
return { status: "cancelled" };
|
|
4046
4345
|
} catch (err) {
|
|
4346
|
+
const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
|
|
4347
|
+
if (purchaseError.userCancelled) {
|
|
4348
|
+
setState("idle");
|
|
4349
|
+
return { status: "cancelled" };
|
|
4350
|
+
}
|
|
4351
|
+
setError(purchaseError);
|
|
4047
4352
|
setState("error");
|
|
4048
|
-
|
|
4353
|
+
return { status: "failed", error: purchaseError };
|
|
4049
4354
|
}
|
|
4050
4355
|
}, []);
|
|
4051
4356
|
const restore = useCallback7(async () => {
|
|
@@ -4074,146 +4379,14 @@ function usePurchase() {
|
|
|
4074
4379
|
restore,
|
|
4075
4380
|
error,
|
|
4076
4381
|
isLoading: state === "loading",
|
|
4077
|
-
isPurchasing: state === "purchasing",
|
|
4382
|
+
isPurchasing: state === "purchasing" || state === "validating",
|
|
4383
|
+
isValidating: state === "validating",
|
|
4078
4384
|
isRestoring: state === "restoring"
|
|
4079
4385
|
};
|
|
4080
4386
|
}
|
|
4081
4387
|
|
|
4082
4388
|
// src/hooks/useSubscription.ts
|
|
4083
4389
|
import { useCallback as useCallback8, useEffect as useEffect6, useState as useState8 } from "react";
|
|
4084
|
-
|
|
4085
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
4086
|
-
import { Platform as Platform8 } from "react-native";
|
|
4087
|
-
var SubscriptionManagerClass = class {
|
|
4088
|
-
constructor() {
|
|
4089
|
-
this.config = null;
|
|
4090
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
4091
|
-
this.userId = null;
|
|
4092
|
-
this.cache = new SubscriptionCache();
|
|
4093
|
-
}
|
|
4094
|
-
init(config) {
|
|
4095
|
-
this.config = config;
|
|
4096
|
-
if (config.cacheTTL) {
|
|
4097
|
-
this.cache.setTTL(config.cacheTTL);
|
|
4098
|
-
}
|
|
4099
|
-
}
|
|
4100
|
-
setUserId(userId) {
|
|
4101
|
-
if (this.userId !== userId) {
|
|
4102
|
-
this.userId = userId;
|
|
4103
|
-
void this.cache.invalidate();
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
async hasActiveSubscription(forceRefresh = false) {
|
|
4107
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4108
|
-
return status.hasActiveSubscription;
|
|
4109
|
-
}
|
|
4110
|
-
async getSubscription(forceRefresh = false) {
|
|
4111
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4112
|
-
return status.subscription;
|
|
4113
|
-
}
|
|
4114
|
-
async getEntitlements(forceRefresh = false) {
|
|
4115
|
-
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
4116
|
-
return status.entitlements;
|
|
4117
|
-
}
|
|
4118
|
-
async getSubscriptionStatus(forceRefresh = false) {
|
|
4119
|
-
if (!this.config) {
|
|
4120
|
-
return this.getEmptyStatus();
|
|
4121
|
-
}
|
|
4122
|
-
if (!forceRefresh) {
|
|
4123
|
-
const cached = await this.cache.get();
|
|
4124
|
-
if (cached && !cached.isStale) {
|
|
4125
|
-
return cached.data;
|
|
4126
|
-
}
|
|
4127
|
-
}
|
|
4128
|
-
try {
|
|
4129
|
-
const status = await this.fetchSubscriptionStatus();
|
|
4130
|
-
await this.cache.set(status);
|
|
4131
|
-
this.notifyListeners(status);
|
|
4132
|
-
return status;
|
|
4133
|
-
} catch (error) {
|
|
4134
|
-
this.log("Failed to fetch subscription status", error);
|
|
4135
|
-
const cached = await this.cache.get();
|
|
4136
|
-
if (cached) {
|
|
4137
|
-
return cached.data;
|
|
4138
|
-
}
|
|
4139
|
-
return this.getEmptyStatus();
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
async restorePurchases() {
|
|
4143
|
-
if (!this.config) {
|
|
4144
|
-
throw new SessionError(
|
|
4145
|
-
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
4146
|
-
"SubscriptionManager not initialized"
|
|
4147
|
-
);
|
|
4148
|
-
}
|
|
4149
|
-
await this.cache.invalidate();
|
|
4150
|
-
return this.getSubscriptionStatus(true);
|
|
4151
|
-
}
|
|
4152
|
-
async fetchSubscriptionStatus() {
|
|
4153
|
-
if (!this.config) {
|
|
4154
|
-
return this.getEmptyStatus();
|
|
4155
|
-
}
|
|
4156
|
-
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
4157
|
-
if (this.userId) {
|
|
4158
|
-
url.searchParams.set("userId", this.userId);
|
|
4159
|
-
}
|
|
4160
|
-
url.searchParams.set("platform", Platform8.OS);
|
|
4161
|
-
const response = await fetch(url.toString(), {
|
|
4162
|
-
method: "GET",
|
|
4163
|
-
headers: {
|
|
4164
|
-
"Content-Type": "application/json",
|
|
4165
|
-
"X-App-Key": this.config.appKey
|
|
4166
|
-
}
|
|
4167
|
-
});
|
|
4168
|
-
if (!response.ok) {
|
|
4169
|
-
throw new SessionError(
|
|
4170
|
-
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
4171
|
-
`Failed to fetch subscription status: ${response.status}`
|
|
4172
|
-
);
|
|
4173
|
-
}
|
|
4174
|
-
const data = await response.json();
|
|
4175
|
-
if (data.subscription) {
|
|
4176
|
-
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
4177
|
-
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
4178
|
-
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
4179
|
-
if (data.subscription.cancellationDate) {
|
|
4180
|
-
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
4181
|
-
}
|
|
4182
|
-
if (data.subscription.gracePeriodExpiresAt) {
|
|
4183
|
-
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
return data;
|
|
4187
|
-
}
|
|
4188
|
-
getEmptyStatus() {
|
|
4189
|
-
return {
|
|
4190
|
-
hasActiveSubscription: false,
|
|
4191
|
-
subscription: null,
|
|
4192
|
-
entitlements: []
|
|
4193
|
-
};
|
|
4194
|
-
}
|
|
4195
|
-
addListener(listener) {
|
|
4196
|
-
this.listeners.add(listener);
|
|
4197
|
-
return () => this.listeners.delete(listener);
|
|
4198
|
-
}
|
|
4199
|
-
notifyListeners(status) {
|
|
4200
|
-
for (const listener of this.listeners) {
|
|
4201
|
-
listener(status);
|
|
4202
|
-
}
|
|
4203
|
-
}
|
|
4204
|
-
async onPurchaseComplete() {
|
|
4205
|
-
await this.cache.invalidate();
|
|
4206
|
-
await this.getSubscriptionStatus(true);
|
|
4207
|
-
}
|
|
4208
|
-
log(message, data) {
|
|
4209
|
-
if (this.config?.debug) {
|
|
4210
|
-
console.warn(`[SubscriptionManager] ${message}`, data ?? "");
|
|
4211
|
-
}
|
|
4212
|
-
}
|
|
4213
|
-
};
|
|
4214
|
-
var subscriptionManager = new SubscriptionManagerClass();
|
|
4215
|
-
|
|
4216
|
-
// src/hooks/useSubscription.ts
|
|
4217
4390
|
function useSubscription() {
|
|
4218
4391
|
const [status, setStatus] = useState8(null);
|
|
4219
4392
|
const [isLoading, setIsLoading] = useState8(true);
|
|
@@ -4268,7 +4441,7 @@ function useSubscription() {
|
|
|
4268
4441
|
}
|
|
4269
4442
|
|
|
4270
4443
|
// src/PaywalloProvider.tsx
|
|
4271
|
-
import { useCallback as useCallback13, useEffect as useEffect7, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
|
|
4444
|
+
import { useCallback as useCallback13, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
|
|
4272
4445
|
import { Animated as Animated3, Dimensions as Dimensions4, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
|
|
4273
4446
|
|
|
4274
4447
|
// src/hooks/useCampaignPreload.ts
|
|
@@ -4330,10 +4503,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
4330
4503
|
function isCacheEntryValid(entry) {
|
|
4331
4504
|
return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
|
|
4332
4505
|
}
|
|
4333
|
-
function
|
|
4506
|
+
function getCacheEntry(cache, placement) {
|
|
4334
4507
|
const entry = cache.get(placement);
|
|
4335
4508
|
if (!isCacheEntryValid(entry)) return null;
|
|
4336
|
-
cache.delete(placement);
|
|
4337
4509
|
return entry ?? null;
|
|
4338
4510
|
}
|
|
4339
4511
|
|
|
@@ -4346,7 +4518,7 @@ function useCampaignPreload() {
|
|
|
4346
4518
|
[]
|
|
4347
4519
|
);
|
|
4348
4520
|
const consumePreloadedCampaign = useCallback9(
|
|
4349
|
-
(placement) =>
|
|
4521
|
+
(placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
|
|
4350
4522
|
[]
|
|
4351
4523
|
);
|
|
4352
4524
|
const preloadCampaign = useCallback9(
|
|
@@ -4394,6 +4566,7 @@ function useCampaignPreload() {
|
|
|
4394
4566
|
campaignId: campaign.campaignId,
|
|
4395
4567
|
variantKey: campaign.variantKey
|
|
4396
4568
|
});
|
|
4569
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4397
4570
|
return { success: true };
|
|
4398
4571
|
} catch (error) {
|
|
4399
4572
|
return {
|
|
@@ -4428,7 +4601,6 @@ function useProductLoader() {
|
|
|
4428
4601
|
for (const p of loaded) map.set(p.productId, p);
|
|
4429
4602
|
setProducts(map);
|
|
4430
4603
|
} catch (error) {
|
|
4431
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Product load failed:", error);
|
|
4432
4604
|
} finally {
|
|
4433
4605
|
setIsLoadingProducts(false);
|
|
4434
4606
|
}
|
|
@@ -4438,7 +4610,7 @@ function useProductLoader() {
|
|
|
4438
4610
|
|
|
4439
4611
|
// src/hooks/usePurchaseOrchestrator.ts
|
|
4440
4612
|
import { useCallback as useCallback11 } from "react";
|
|
4441
|
-
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
|
|
4613
|
+
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
|
|
4442
4614
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
4443
4615
|
const handlePreloadedPurchase = useCallback11(
|
|
4444
4616
|
async (productId) => {
|
|
@@ -4463,17 +4635,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4463
4635
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4464
4636
|
await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4465
4637
|
}
|
|
4638
|
+
invalidateSubscriptionCache();
|
|
4466
4639
|
setShowingPreloadedWebView(false);
|
|
4467
4640
|
clearPreloadedWebView();
|
|
4468
4641
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4469
4642
|
} catch (error) {
|
|
4470
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded purchase error:", error);
|
|
4471
4643
|
setShowingPreloadedWebView(false);
|
|
4472
4644
|
clearPreloadedWebView();
|
|
4473
4645
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4474
4646
|
}
|
|
4475
4647
|
},
|
|
4476
|
-
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
|
|
4648
|
+
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
|
|
4477
4649
|
);
|
|
4478
4650
|
const handlePreloadedRestore = useCallback11(async () => {
|
|
4479
4651
|
try {
|
|
@@ -4483,16 +4655,16 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4483
4655
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
4484
4656
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4485
4657
|
}
|
|
4658
|
+
invalidateSubscriptionCache();
|
|
4486
4659
|
setShowingPreloadedWebView(false);
|
|
4487
4660
|
clearPreloadedWebView();
|
|
4488
4661
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4489
4662
|
} catch (error) {
|
|
4490
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Preloaded restore error:", error);
|
|
4491
4663
|
setShowingPreloadedWebView(false);
|
|
4492
4664
|
clearPreloadedWebView();
|
|
4493
4665
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4494
4666
|
}
|
|
4495
|
-
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
|
|
4667
|
+
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
|
|
4496
4668
|
return { handlePreloadedPurchase, handlePreloadedRestore };
|
|
4497
4669
|
}
|
|
4498
4670
|
|
|
@@ -4504,7 +4676,6 @@ function toDomainSubscriptionInfo(sub) {
|
|
|
4504
4676
|
return {
|
|
4505
4677
|
id: "",
|
|
4506
4678
|
productId: sub.productId,
|
|
4507
|
-
// The hook's SubscriptionStatus is a subset of the domain's — cast is safe
|
|
4508
4679
|
status: sub.status,
|
|
4509
4680
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4510
4681
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4522,31 +4693,32 @@ function toDomainResponse(status) {
|
|
|
4522
4693
|
entitlements: []
|
|
4523
4694
|
};
|
|
4524
4695
|
}
|
|
4525
|
-
|
|
4696
|
+
function isSubscriptionStillActive(sub) {
|
|
4697
|
+
if (!sub) return false;
|
|
4698
|
+
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4699
|
+
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4700
|
+
return true;
|
|
4701
|
+
}
|
|
4702
|
+
async function resolveOfflineFromCache(distinctId) {
|
|
4526
4703
|
try {
|
|
4527
|
-
const cached = await subscriptionCache.get();
|
|
4704
|
+
const cached = await subscriptionCache.get(distinctId);
|
|
4528
4705
|
if (!cached) return false;
|
|
4529
4706
|
const sub = cached.data.subscription;
|
|
4530
|
-
|
|
4531
|
-
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4532
|
-
const hasValidExpiry = sub.expiresAt.getTime() === 0 || sub.expiresAt > /* @__PURE__ */ new Date();
|
|
4533
|
-
return hasValidExpiry;
|
|
4707
|
+
return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
|
|
4534
4708
|
} catch {
|
|
4535
4709
|
return false;
|
|
4536
4710
|
}
|
|
4537
4711
|
}
|
|
4538
4712
|
function isActiveCacheEntry(entry) {
|
|
4539
|
-
|
|
4540
|
-
if (!ACTIVE_STATUSES.includes(entry.data.status)) return false;
|
|
4541
|
-
if (entry.data.expiresAt && entry.data.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4542
|
-
return true;
|
|
4713
|
+
return isSubscriptionStillActive(entry.data);
|
|
4543
4714
|
}
|
|
4544
4715
|
function useSubscriptionSync() {
|
|
4545
4716
|
const [subscription, setSubscription] = useState11(null);
|
|
4546
4717
|
const cacheRef = useRef7(null);
|
|
4547
4718
|
const invalidateCache = useCallback12(() => {
|
|
4548
4719
|
cacheRef.current = null;
|
|
4549
|
-
|
|
4720
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4721
|
+
if (distinctId) void subscriptionCache.invalidate(distinctId);
|
|
4550
4722
|
}, []);
|
|
4551
4723
|
const fetchAndCache = useCallback12(async () => {
|
|
4552
4724
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4559,7 +4731,7 @@ function useSubscriptionSync() {
|
|
|
4559
4731
|
} : null;
|
|
4560
4732
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4561
4733
|
setSubscription(sub);
|
|
4562
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4734
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4563
4735
|
return sub;
|
|
4564
4736
|
}, []);
|
|
4565
4737
|
const hasActiveSubscription = useCallback12(async () => {
|
|
@@ -4571,23 +4743,23 @@ function useSubscriptionSync() {
|
|
|
4571
4743
|
return isActiveCacheEntry(cached);
|
|
4572
4744
|
}
|
|
4573
4745
|
try {
|
|
4574
|
-
const persisted = await subscriptionCache.get();
|
|
4575
|
-
if (persisted) {
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4746
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4747
|
+
if (persisted && !persisted.isStale) {
|
|
4748
|
+
const persistedSub = persisted.data.subscription;
|
|
4749
|
+
const sub = persistedSub ? {
|
|
4750
|
+
productId: persistedSub.productId,
|
|
4751
|
+
status: persistedSub.status,
|
|
4752
|
+
expiresAt: persistedSub.expiresAt ?? null,
|
|
4753
|
+
platform: persistedSub.platform,
|
|
4754
|
+
autoRenewEnabled: persistedSub.willRenew,
|
|
4755
|
+
inGracePeriod: persistedSub.status === "grace_period"
|
|
4756
|
+
} : null;
|
|
4757
|
+
const stillActive = isSubscriptionStillActive(sub);
|
|
4758
|
+
if (stillActive) {
|
|
4585
4759
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4586
4760
|
setSubscription(sub);
|
|
4587
|
-
return
|
|
4761
|
+
return true;
|
|
4588
4762
|
}
|
|
4589
|
-
void fetchAndCache();
|
|
4590
|
-
return persisted.data.hasActiveSubscription;
|
|
4591
4763
|
}
|
|
4592
4764
|
} catch {
|
|
4593
4765
|
}
|
|
@@ -4599,12 +4771,12 @@ function useSubscriptionSync() {
|
|
|
4599
4771
|
} : null;
|
|
4600
4772
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4601
4773
|
setSubscription(sub);
|
|
4602
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4774
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4603
4775
|
return status.hasActiveSubscription;
|
|
4604
4776
|
} catch {
|
|
4605
|
-
return await resolveOfflineFromCache();
|
|
4777
|
+
return await resolveOfflineFromCache(distinctId);
|
|
4606
4778
|
}
|
|
4607
|
-
}, [
|
|
4779
|
+
}, []);
|
|
4608
4780
|
const getSubscription = useCallback12(async () => {
|
|
4609
4781
|
const apiClient = PaywalloClient.getApiClient();
|
|
4610
4782
|
const distinctId = PaywalloClient.getDistinctId();
|
|
@@ -4615,7 +4787,7 @@ function useSubscriptionSync() {
|
|
|
4615
4787
|
return await fetchAndCache();
|
|
4616
4788
|
} catch {
|
|
4617
4789
|
try {
|
|
4618
|
-
const persisted = await subscriptionCache.get();
|
|
4790
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4619
4791
|
if (!persisted?.data.subscription) return null;
|
|
4620
4792
|
const s = persisted.data.subscription;
|
|
4621
4793
|
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
@@ -4646,13 +4818,38 @@ function PaywalloProvider({ children, config }) {
|
|
|
4646
4818
|
const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
|
|
4647
4819
|
const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
|
|
4648
4820
|
const clearPreloadedWebView = useCallback13(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4649
|
-
const
|
|
4821
|
+
const autoPreloadTriggeredRef = useRef8(false);
|
|
4822
|
+
const preloadedWebViewRef = useRef8(preloadedWebView);
|
|
4823
|
+
useEffect7(() => {
|
|
4824
|
+
preloadedWebViewRef.current = preloadedWebView;
|
|
4825
|
+
}, [preloadedWebView]);
|
|
4826
|
+
const triggerRepreload = useCallback13((placement) => {
|
|
4827
|
+
void PaywalloClient.preloadCampaign(placement).catch(() => {
|
|
4828
|
+
});
|
|
4829
|
+
}, []);
|
|
4830
|
+
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
4831
|
+
const SCREEN_HEIGHT3 = useMemo2(() => Dimensions4.get("window").height, []);
|
|
4832
|
+
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4833
|
+
const handlePreloadedWebViewReady = useCallback13(() => {
|
|
4834
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4835
|
+
console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
4836
|
+
}
|
|
4837
|
+
baseHandlePreloadedWebViewReady();
|
|
4838
|
+
}, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
4839
|
+
const handlePreloadedClose = useCallback13(() => {
|
|
4840
|
+
const placement = preloadedWebView?.placement;
|
|
4841
|
+
Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
|
|
4842
|
+
baseHandlePreloadedClose();
|
|
4843
|
+
if (placement) triggerRepreload(placement);
|
|
4844
|
+
});
|
|
4845
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
|
|
4650
4846
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4651
4847
|
preloadedWebView,
|
|
4652
4848
|
resolvePreloadedPaywall,
|
|
4653
4849
|
clearPreloadedWebView,
|
|
4654
4850
|
setShowingPreloadedWebView,
|
|
4655
|
-
presentedAtRef
|
|
4851
|
+
presentedAtRef,
|
|
4852
|
+
invalidateCache
|
|
4656
4853
|
);
|
|
4657
4854
|
const [isPreloadPurchasing, setIsPreloadPurchasing] = useState12(false);
|
|
4658
4855
|
const handlePreloadedPurchase = useCallback13(async (productId) => {
|
|
@@ -4665,13 +4862,19 @@ function PaywalloProvider({ children, config }) {
|
|
|
4665
4862
|
}, [rawPreloadedPurchase]);
|
|
4666
4863
|
useEffect7(() => {
|
|
4667
4864
|
initLocalization({ detectDevice: true });
|
|
4668
|
-
PaywalloClient.init(config).then(() => {
|
|
4865
|
+
PaywalloClient.init(config).then(async () => {
|
|
4669
4866
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
4670
4867
|
setIsInitialized(true);
|
|
4868
|
+
if (!autoPreloadTriggeredRef.current) {
|
|
4869
|
+
autoPreloadTriggeredRef.current = true;
|
|
4870
|
+
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
4871
|
+
if (placement) void preloadCampaign(placement).catch(() => {
|
|
4872
|
+
});
|
|
4873
|
+
}
|
|
4671
4874
|
}).catch((err) => {
|
|
4672
4875
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
4673
4876
|
});
|
|
4674
|
-
}, [config]);
|
|
4877
|
+
}, [config, preloadCampaign]);
|
|
4675
4878
|
const getPaywallConfig = useCallback13(async (p) => {
|
|
4676
4879
|
const api = PaywalloClient.getApiClient();
|
|
4677
4880
|
return api ? api.getPaywall(p) : null;
|
|
@@ -4770,28 +4973,54 @@ function PaywalloProvider({ children, config }) {
|
|
|
4770
4973
|
setActivePaywall(null);
|
|
4771
4974
|
}
|
|
4772
4975
|
}, [activePaywall]);
|
|
4976
|
+
const bridgePaywallPresenter = useCallback13((placement) => {
|
|
4977
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4978
|
+
console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
4979
|
+
}
|
|
4980
|
+
if (preloadedWebViewRef.current?.placement === placement) {
|
|
4981
|
+
if (preloadedWebViewRef.current.loaded) {
|
|
4982
|
+
return presentPreloadedWebView();
|
|
4983
|
+
}
|
|
4984
|
+
return new Promise((resolve) => {
|
|
4985
|
+
let elapsed = 0;
|
|
4986
|
+
const poll = () => {
|
|
4987
|
+
if (preloadedWebViewRef.current?.loaded) {
|
|
4988
|
+
resolve(presentPreloadedWebView());
|
|
4989
|
+
return;
|
|
4990
|
+
}
|
|
4991
|
+
elapsed += 100;
|
|
4992
|
+
if (elapsed >= 500) {
|
|
4993
|
+
resolve(presentCampaign(placement));
|
|
4994
|
+
return;
|
|
4995
|
+
}
|
|
4996
|
+
setTimeout(poll, 100);
|
|
4997
|
+
};
|
|
4998
|
+
setTimeout(poll, 100);
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
return presentCampaign(placement);
|
|
5002
|
+
}, [presentPreloadedWebView, presentCampaign]);
|
|
4773
5003
|
useEffect7(() => {
|
|
4774
5004
|
if (!isInitialized) return;
|
|
4775
5005
|
const onEmergency = async (id) => {
|
|
4776
5006
|
try {
|
|
4777
|
-
await
|
|
5007
|
+
await bridgePaywallPresenter(id);
|
|
4778
5008
|
} catch {
|
|
4779
5009
|
}
|
|
4780
5010
|
};
|
|
4781
|
-
PaywalloClient.registerPaywallPresenter(
|
|
5011
|
+
PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
|
|
4782
5012
|
PaywalloClient.registerSubscriptionGetter(getSubscription);
|
|
4783
5013
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
4784
5014
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
4785
5015
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
4786
|
-
}, [isInitialized,
|
|
4787
|
-
|
|
4788
|
-
if (activePaywall) setPreloadedWebView(null);
|
|
4789
|
-
}, [activePaywall, setPreloadedWebView]);
|
|
5016
|
+
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5017
|
+
const isReady = isInitialized && !isLoadingProducts;
|
|
4790
5018
|
const noOp = useCallback13(() => {
|
|
4791
5019
|
}, []);
|
|
4792
5020
|
const contextValue = useMemo2(() => ({
|
|
4793
5021
|
config: PaywalloClient.getConfig(),
|
|
4794
5022
|
isInitialized,
|
|
5023
|
+
isReady,
|
|
4795
5024
|
initError,
|
|
4796
5025
|
distinctId,
|
|
4797
5026
|
subscription,
|
|
@@ -4807,6 +5036,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4807
5036
|
refreshProducts
|
|
4808
5037
|
}), [
|
|
4809
5038
|
isInitialized,
|
|
5039
|
+
isReady,
|
|
4810
5040
|
initError,
|
|
4811
5041
|
distinctId,
|
|
4812
5042
|
subscription,
|
|
@@ -4821,20 +5051,16 @@ function PaywalloProvider({ children, config }) {
|
|
|
4821
5051
|
getPaywallConfig,
|
|
4822
5052
|
refreshProducts
|
|
4823
5053
|
]);
|
|
4824
|
-
|
|
4825
|
-
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4826
|
-
useEffect7(() => {
|
|
5054
|
+
useLayoutEffect(() => {
|
|
4827
5055
|
if (showingPreloadedWebView) {
|
|
4828
5056
|
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
4829
|
-
Animated3.timing(slideAnim, { toValue: 0, duration:
|
|
4830
|
-
} else {
|
|
4831
|
-
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
5057
|
+
Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
|
|
4832
5058
|
}
|
|
4833
5059
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
|
|
4834
5060
|
const preloadStyle = [
|
|
4835
5061
|
styles3.preloadOverlay,
|
|
4836
5062
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
4837
|
-
{ transform: [{ translateY:
|
|
5063
|
+
showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
|
|
4838
5064
|
];
|
|
4839
5065
|
return /* @__PURE__ */ jsxs2(PaywalloContext.Provider, { value: contextValue, children: [
|
|
4840
5066
|
children,
|
|
@@ -4847,6 +5073,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4847
5073
|
primaryProductId: preloadedWebView.primaryProductId,
|
|
4848
5074
|
secondaryProductId: preloadedWebView.secondaryProductId,
|
|
4849
5075
|
isPurchasing: isPreloadPurchasing,
|
|
5076
|
+
webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
|
|
4850
5077
|
onReady: handlePreloadedWebViewReady,
|
|
4851
5078
|
onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
|
|
4852
5079
|
onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
|
|
@@ -4869,8 +5096,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4869
5096
|
}
|
|
4870
5097
|
var styles3 = StyleSheet3.create({
|
|
4871
5098
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
4872
|
-
hidden: { zIndex: -1 },
|
|
4873
|
-
visible: { zIndex: 9999 }
|
|
5099
|
+
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5100
|
+
visible: { zIndex: 9999, opacity: 1 }
|
|
4874
5101
|
});
|
|
4875
5102
|
|
|
4876
5103
|
// src/utils/productVariableResolver.ts
|