@virex-tech/paywallo-sdk 1.1.3 → 1.4.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/PaywalloSdk.podspec +1 -0
- package/README.md +28 -53
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloFBBridgeModule.kt +84 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +4 -1
- package/dist/index.d.mts +84 -53
- package/dist/index.d.ts +84 -53
- package/dist/index.js +1069 -723
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1046 -695
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloFBBridge.m +23 -0
- package/ios/PaywalloFBBridge.swift +77 -0
- package/ios/PaywalloStoreKit.swift +0 -4
- package/ios/RCTBridgeTypes.swift +6 -0
- package/package.json +5 -19
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/utils/uuid.ts
|
|
12
|
+
var uuid_exports = {};
|
|
13
|
+
__export(uuid_exports, {
|
|
14
|
+
generateUUID: () => generateUUID
|
|
15
|
+
});
|
|
16
|
+
function generateUUID() {
|
|
17
|
+
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
18
|
+
return template.replace(/[xy]/g, (c) => {
|
|
19
|
+
const r = Math.random() * 16 | 0;
|
|
20
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
21
|
+
return v.toString(16);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
var init_uuid = __esm({
|
|
25
|
+
"src/utils/uuid.ts"() {
|
|
26
|
+
"use strict";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
1
30
|
// src/errors/PaywalloError.ts
|
|
2
31
|
var PaywalloError = class extends Error {
|
|
3
32
|
constructor(domain, code, message) {
|
|
@@ -102,6 +131,9 @@ function createPurchaseError(code, message) {
|
|
|
102
131
|
// src/domains/iap/NativeStoreKit.ts
|
|
103
132
|
import { NativeModules, Platform } from "react-native";
|
|
104
133
|
|
|
134
|
+
// src/domains/identity/IdentityManager.ts
|
|
135
|
+
import DeviceInfo from "react-native-device-info";
|
|
136
|
+
|
|
105
137
|
// src/domains/identity/IdentityError.ts
|
|
106
138
|
var IdentityError = class extends PaywalloError {
|
|
107
139
|
constructor(code, message) {
|
|
@@ -117,37 +149,17 @@ var IDENTITY_ERROR_CODES = {
|
|
|
117
149
|
IDENTIFY_FAILED: "IDENTITY_IDENTIFY_FAILED"
|
|
118
150
|
};
|
|
119
151
|
|
|
120
|
-
// src/
|
|
121
|
-
|
|
122
|
-
const template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
|
|
123
|
-
return template.replace(/[xy]/g, (c) => {
|
|
124
|
-
const r = Math.random() * 16 | 0;
|
|
125
|
-
const v = c === "x" ? r : r & 3 | 8;
|
|
126
|
-
return v.toString(16);
|
|
127
|
-
});
|
|
128
|
-
}
|
|
152
|
+
// src/domains/identity/IdentityManager.ts
|
|
153
|
+
init_uuid();
|
|
129
154
|
|
|
130
155
|
// src/core/storage/SecureStorage.ts
|
|
131
156
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
132
|
-
|
|
133
|
-
async function loadKeychain() {
|
|
134
|
-
if (_keychain !== void 0) return _keychain;
|
|
135
|
-
try {
|
|
136
|
-
const mod = await import("react-native-keychain");
|
|
137
|
-
_keychain = mod.default;
|
|
138
|
-
} catch {
|
|
139
|
-
_keychain = null;
|
|
140
|
-
}
|
|
141
|
-
return _keychain;
|
|
142
|
-
}
|
|
157
|
+
import Keychain from "react-native-keychain";
|
|
143
158
|
var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
|
|
144
159
|
var SecureStorage = class {
|
|
145
160
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
146
161
|
constructor(_debug = false) {
|
|
147
162
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Get a value, trying Keychain first (survives reinstalls on iOS), then AsyncStorage.
|
|
150
|
-
*/
|
|
151
163
|
async get(key) {
|
|
152
164
|
const keychainValue = await this.getFromKeychain(key);
|
|
153
165
|
if (keychainValue) {
|
|
@@ -163,9 +175,6 @@ var SecureStorage = class {
|
|
|
163
175
|
return null;
|
|
164
176
|
}
|
|
165
177
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Set a value in both Keychain (if available) and AsyncStorage.
|
|
168
|
-
*/
|
|
169
178
|
async set(key, value) {
|
|
170
179
|
try {
|
|
171
180
|
await Promise.all([
|
|
@@ -189,13 +198,11 @@ var SecureStorage = class {
|
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
200
|
hasKeychainSupport() {
|
|
192
|
-
return
|
|
201
|
+
return true;
|
|
193
202
|
}
|
|
194
203
|
async getFromKeychain(key) {
|
|
195
|
-
const keychain = await loadKeychain();
|
|
196
|
-
if (!keychain) return null;
|
|
197
204
|
try {
|
|
198
|
-
const result = await
|
|
205
|
+
const result = await Keychain.getGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
|
|
199
206
|
if (result && result.password) {
|
|
200
207
|
return result.password;
|
|
201
208
|
}
|
|
@@ -205,20 +212,16 @@ var SecureStorage = class {
|
|
|
205
212
|
}
|
|
206
213
|
}
|
|
207
214
|
async setToKeychain(key, value) {
|
|
208
|
-
const keychain = await loadKeychain();
|
|
209
|
-
if (!keychain) return false;
|
|
210
215
|
try {
|
|
211
|
-
await
|
|
216
|
+
await Keychain.setGenericPassword("panel", value, { service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
|
|
212
217
|
return true;
|
|
213
218
|
} catch {
|
|
214
219
|
return false;
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
async removeFromKeychain(key) {
|
|
218
|
-
const keychain = await loadKeychain();
|
|
219
|
-
if (!keychain) return false;
|
|
220
223
|
try {
|
|
221
|
-
await
|
|
224
|
+
await Keychain.resetGenericPassword({ service: `${KEYCHAIN_SERVICE_PREFIX}${key}` });
|
|
222
225
|
return true;
|
|
223
226
|
} catch {
|
|
224
227
|
return false;
|
|
@@ -229,11 +232,13 @@ var secureStorage = new SecureStorage();
|
|
|
229
232
|
|
|
230
233
|
// src/domains/identity/IdentityManager.ts
|
|
231
234
|
var DEVICE_ID_KEY = "device_id";
|
|
235
|
+
var ANON_ID_KEY = "anon_id";
|
|
232
236
|
var USER_EMAIL_KEY = "user_email";
|
|
233
237
|
var USER_PROPERTIES_KEY = "user_properties";
|
|
234
238
|
var IdentityManager = class {
|
|
235
239
|
constructor() {
|
|
236
240
|
this.deviceId = null;
|
|
241
|
+
this.anonId = null;
|
|
237
242
|
this.email = null;
|
|
238
243
|
this.properties = {};
|
|
239
244
|
this.apiClient = null;
|
|
@@ -250,33 +255,22 @@ var IdentityManager = class {
|
|
|
250
255
|
this.secureStorage = new SecureStorage(debug);
|
|
251
256
|
await this.loadPersistedState();
|
|
252
257
|
if (!this.deviceId) {
|
|
253
|
-
let DeviceInfo = null;
|
|
254
|
-
try {
|
|
255
|
-
const mod = await import("react-native-device-info");
|
|
256
|
-
DeviceInfo = mod.default;
|
|
257
|
-
} catch {
|
|
258
|
-
DeviceInfo = null;
|
|
259
|
-
}
|
|
260
258
|
try {
|
|
261
|
-
|
|
262
|
-
this.deviceId = await DeviceInfo.getUniqueId();
|
|
263
|
-
this.log("Got device ID from DeviceInfo:", this.deviceId);
|
|
264
|
-
} else {
|
|
265
|
-
this.deviceId = generateUUID();
|
|
266
|
-
this.log("DeviceInfo not installed, using generated UUID:", this.deviceId);
|
|
267
|
-
}
|
|
259
|
+
this.deviceId = await DeviceInfo.getUniqueId();
|
|
268
260
|
} catch {
|
|
269
261
|
this.deviceId = generateUUID();
|
|
270
|
-
this.log("DeviceInfo unavailable, using generated UUID:", this.deviceId);
|
|
271
262
|
}
|
|
272
263
|
const stored = await this.secureStorage.set(DEVICE_ID_KEY, this.deviceId);
|
|
273
264
|
if (!stored) {
|
|
274
|
-
this.log("Failed to persist device ID to secure storage, using fallback");
|
|
275
265
|
await this.secureStorage.set("device_id_fallback", this.deviceId).catch(() => {
|
|
276
|
-
this.log("Fallback also failed \u2014 device ID only in memory");
|
|
277
266
|
});
|
|
278
267
|
}
|
|
279
268
|
}
|
|
269
|
+
if (!this.anonId) {
|
|
270
|
+
this.anonId = "$paywallo_anon:" + generateUUID();
|
|
271
|
+
await this.secureStorage.set(ANON_ID_KEY, this.anonId).catch(() => {
|
|
272
|
+
});
|
|
273
|
+
}
|
|
280
274
|
this.initialized = true;
|
|
281
275
|
return this.deviceId ?? "";
|
|
282
276
|
}
|
|
@@ -300,21 +294,18 @@ var IdentityManager = class {
|
|
|
300
294
|
this.properties = { ...this.properties, ...properties };
|
|
301
295
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
302
296
|
}
|
|
303
|
-
if (this.apiClient && this.
|
|
297
|
+
if (this.apiClient && this.anonId) {
|
|
304
298
|
try {
|
|
305
|
-
await this.apiClient.identify(this.
|
|
306
|
-
|
|
307
|
-
} catch (error) {
|
|
308
|
-
this.log("Failed to update identity on server:", error);
|
|
299
|
+
await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
|
|
300
|
+
} catch {
|
|
309
301
|
}
|
|
310
302
|
}
|
|
311
303
|
}
|
|
312
304
|
getDistinctId() {
|
|
313
|
-
if (!this.initialized || !this.
|
|
314
|
-
if (this.debug) console.warn("[Paywallo Identity] getDistinctId called before initialization");
|
|
305
|
+
if (!this.initialized || !this.anonId) {
|
|
315
306
|
return "";
|
|
316
307
|
}
|
|
317
|
-
return this.
|
|
308
|
+
return this.anonId;
|
|
318
309
|
}
|
|
319
310
|
getDeviceId() {
|
|
320
311
|
return this.deviceId;
|
|
@@ -329,24 +320,22 @@ var IdentityManager = class {
|
|
|
329
320
|
this.ensureInitialized();
|
|
330
321
|
this.properties = { ...this.properties, ...properties };
|
|
331
322
|
await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
|
|
332
|
-
if (this.apiClient && this.
|
|
323
|
+
if (this.apiClient && this.anonId) {
|
|
333
324
|
try {
|
|
334
|
-
await this.apiClient.identify(this.
|
|
335
|
-
} catch
|
|
336
|
-
this.log("Failed to update properties on server:", error);
|
|
325
|
+
await this.apiClient.identify(this.anonId, this.properties, this.email ?? void 0, this.deviceId ?? void 0);
|
|
326
|
+
} catch {
|
|
337
327
|
}
|
|
338
328
|
}
|
|
339
329
|
}
|
|
340
330
|
async reset() {
|
|
331
|
+
const newAnonId = "$paywallo_anon:" + generateUUID();
|
|
332
|
+
this.anonId = newAnonId;
|
|
341
333
|
this.email = null;
|
|
342
334
|
this.properties = {};
|
|
343
|
-
this.initialized = false;
|
|
344
|
-
this.apiClient = null;
|
|
345
|
-
this.deviceId = null;
|
|
346
335
|
await Promise.all([
|
|
336
|
+
this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
|
|
347
337
|
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)
|
|
338
|
+
this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
|
|
350
339
|
]);
|
|
351
340
|
}
|
|
352
341
|
getState() {
|
|
@@ -363,21 +352,23 @@ var IdentityManager = class {
|
|
|
363
352
|
if (!this.secureStorage) {
|
|
364
353
|
return;
|
|
365
354
|
}
|
|
366
|
-
const [storedDeviceId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
355
|
+
const [storedDeviceId, storedAnonId, storedEmail, storedPropertiesJson] = await Promise.all([
|
|
367
356
|
this.secureStorage.get(DEVICE_ID_KEY),
|
|
357
|
+
this.secureStorage.get(ANON_ID_KEY),
|
|
368
358
|
this.secureStorage.get(USER_EMAIL_KEY),
|
|
369
359
|
this.secureStorage.get(USER_PROPERTIES_KEY)
|
|
370
360
|
]);
|
|
371
361
|
if (storedDeviceId) {
|
|
372
362
|
this.deviceId = storedDeviceId;
|
|
373
|
-
this.log("Loaded device ID from storage:", this.deviceId);
|
|
374
363
|
} else {
|
|
375
364
|
const fallbackDeviceId = await this.secureStorage.get("device_id_fallback");
|
|
376
365
|
if (fallbackDeviceId) {
|
|
377
366
|
this.deviceId = fallbackDeviceId;
|
|
378
|
-
this.log("Loaded device ID from fallback storage:", this.deviceId);
|
|
379
367
|
}
|
|
380
368
|
}
|
|
369
|
+
if (storedAnonId) {
|
|
370
|
+
this.anonId = storedAnonId;
|
|
371
|
+
}
|
|
381
372
|
if (storedEmail) {
|
|
382
373
|
this.email = storedEmail;
|
|
383
374
|
}
|
|
@@ -397,11 +388,6 @@ var IdentityManager = class {
|
|
|
397
388
|
);
|
|
398
389
|
}
|
|
399
390
|
}
|
|
400
|
-
log(...args) {
|
|
401
|
-
if (this.debug) {
|
|
402
|
-
console.warn("[Paywallo Identity]", ...args);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
391
|
};
|
|
406
392
|
var identityManager = new IdentityManager();
|
|
407
393
|
|
|
@@ -486,7 +472,6 @@ var nativeStoreKit = {
|
|
|
486
472
|
isAvailable,
|
|
487
473
|
async getProducts(productIds) {
|
|
488
474
|
if (!PaywalloStoreKitNative) {
|
|
489
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] StoreKit native module not available");
|
|
490
475
|
return [];
|
|
491
476
|
}
|
|
492
477
|
const nativeProducts = await PaywalloStoreKitNative.getProducts(productIds);
|
|
@@ -500,20 +485,41 @@ var nativeStoreKit = {
|
|
|
500
485
|
);
|
|
501
486
|
}
|
|
502
487
|
const distinctId = identityManager.getDistinctId();
|
|
503
|
-
const
|
|
504
|
-
if (!
|
|
505
|
-
|
|
488
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
489
|
+
if (distinctId && !uuidRegex.test(distinctId) && PaywalloClient.getConfig()?.debug) {
|
|
490
|
+
console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
|
|
491
|
+
}
|
|
492
|
+
try {
|
|
493
|
+
const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
|
|
494
|
+
if (!result || result.pending) {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
return mapNativePurchase(result);
|
|
498
|
+
} catch (err) {
|
|
499
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
500
|
+
if (message.toLowerCase().includes("cancel")) {
|
|
501
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.USER_CANCELLED, message, true);
|
|
502
|
+
}
|
|
503
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, message);
|
|
506
504
|
}
|
|
507
|
-
return mapNativePurchase(result);
|
|
508
505
|
},
|
|
509
506
|
async finishTransaction(transactionId) {
|
|
510
507
|
if (!PaywalloStoreKitNative) return;
|
|
511
|
-
|
|
508
|
+
try {
|
|
509
|
+
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction native error", err);
|
|
512
|
+
}
|
|
512
513
|
},
|
|
513
514
|
async getActiveTransactions() {
|
|
514
515
|
if (!PaywalloStoreKitNative) return [];
|
|
515
|
-
|
|
516
|
-
|
|
516
|
+
try {
|
|
517
|
+
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
518
|
+
return results.map(mapNativePurchase);
|
|
519
|
+
} catch (err) {
|
|
520
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] getActiveTransactions failed", err);
|
|
521
|
+
return [];
|
|
522
|
+
}
|
|
517
523
|
}
|
|
518
524
|
};
|
|
519
525
|
|
|
@@ -556,8 +562,8 @@ var IAPService = class {
|
|
|
556
562
|
this.productsCache.set(product.productId, product);
|
|
557
563
|
}
|
|
558
564
|
return products;
|
|
559
|
-
} catch (
|
|
560
|
-
if (
|
|
565
|
+
} catch (err) {
|
|
566
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts failed", err);
|
|
561
567
|
return [];
|
|
562
568
|
}
|
|
563
569
|
}
|
|
@@ -567,35 +573,47 @@ var IAPService = class {
|
|
|
567
573
|
getCachedProducts() {
|
|
568
574
|
return new Map(this.productsCache);
|
|
569
575
|
}
|
|
576
|
+
async validateWithRetry(attempt, maxRetries = 2, delayMs = 3e3) {
|
|
577
|
+
let lastError;
|
|
578
|
+
for (let i = 0; i <= maxRetries; i++) {
|
|
579
|
+
try {
|
|
580
|
+
return await attempt();
|
|
581
|
+
} catch (error) {
|
|
582
|
+
lastError = error;
|
|
583
|
+
if (i < maxRetries) await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
throw lastError;
|
|
587
|
+
}
|
|
570
588
|
async validateWithServer(purchase, productId, options) {
|
|
571
589
|
const apiClient = this.getApiClient();
|
|
572
590
|
const product = this.productsCache.get(productId);
|
|
573
591
|
const platform = Platform2.OS;
|
|
574
592
|
const distinctId = PaywalloClient.getDistinctId();
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
593
|
+
return this.validateWithRetry(
|
|
594
|
+
() => apiClient.validatePurchase(
|
|
595
|
+
platform,
|
|
596
|
+
purchase.receipt,
|
|
597
|
+
purchase.productId,
|
|
598
|
+
purchase.transactionId,
|
|
599
|
+
product?.priceValue ?? 0,
|
|
600
|
+
product?.currency ?? "USD",
|
|
601
|
+
this.getDeviceCountry(),
|
|
602
|
+
distinctId,
|
|
603
|
+
options?.paywallPlacement,
|
|
604
|
+
options?.variantKey
|
|
605
|
+
)
|
|
586
606
|
);
|
|
587
|
-
return validation;
|
|
588
607
|
}
|
|
589
608
|
async finishTransaction(transactionId) {
|
|
590
609
|
try {
|
|
591
610
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
592
|
-
} catch (
|
|
593
|
-
if (
|
|
611
|
+
} catch (err) {
|
|
612
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
|
|
594
613
|
}
|
|
595
614
|
}
|
|
596
615
|
async purchase(productId, options) {
|
|
597
616
|
if (!nativeStoreKit.isAvailable()) {
|
|
598
|
-
if (this.debug) console.warn("[Paywallo][Purchase] StoreKit not available");
|
|
599
617
|
return {
|
|
600
618
|
success: false,
|
|
601
619
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
@@ -606,27 +624,13 @@ var IAPService = class {
|
|
|
606
624
|
if (!purchase) {
|
|
607
625
|
return { success: false };
|
|
608
626
|
}
|
|
609
|
-
let validation = null;
|
|
610
|
-
try {
|
|
611
|
-
validation = await this.validateWithServer(purchase, productId, options);
|
|
612
|
-
} catch (validationError) {
|
|
613
|
-
if (this.debug) console.warn("[Paywallo][Purchase] Server validation FAILED:", validationError instanceof Error ? validationError.message : validationError);
|
|
614
|
-
return {
|
|
615
|
-
success: false,
|
|
616
|
-
error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validationError instanceof Error ? validationError.message : "Server validation failed")
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
if (!validation?.success) {
|
|
620
|
-
return {
|
|
621
|
-
success: false,
|
|
622
|
-
error: new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, validation?.error ? `Server validation: ${validation.error}` : "Server validation failed")
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
627
|
await this.finishTransaction(purchase.transactionId);
|
|
628
|
+
this.validateWithServer(purchase, productId, options).catch((err) => {
|
|
629
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] background validation failed", err);
|
|
630
|
+
});
|
|
626
631
|
return { success: true, purchase };
|
|
627
632
|
} catch (error) {
|
|
628
633
|
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
634
|
return { success: false, error: e };
|
|
631
635
|
}
|
|
632
636
|
}
|
|
@@ -661,15 +665,15 @@ var IAPService = class {
|
|
|
661
665
|
if (r.status === "fulfilled" && r.value.success) {
|
|
662
666
|
try {
|
|
663
667
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
664
|
-
} catch (
|
|
665
|
-
if (
|
|
668
|
+
} catch (err) {
|
|
669
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] finishTransaction failed", err);
|
|
666
670
|
}
|
|
667
671
|
validated.push(tx);
|
|
668
672
|
}
|
|
669
673
|
}
|
|
670
674
|
return validated;
|
|
671
|
-
} catch (
|
|
672
|
-
if (
|
|
675
|
+
} catch (err) {
|
|
676
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] restore failed", err);
|
|
673
677
|
return [];
|
|
674
678
|
}
|
|
675
679
|
}
|
|
@@ -699,7 +703,8 @@ function usePaywallTracking() {
|
|
|
699
703
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
700
704
|
...sessionId && { sessionId }
|
|
701
705
|
});
|
|
702
|
-
} catch {
|
|
706
|
+
} catch (err) {
|
|
707
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackDismissed failed", err);
|
|
703
708
|
}
|
|
704
709
|
}, []);
|
|
705
710
|
const trackProductSelected = useCallback((opts) => {
|
|
@@ -713,7 +718,8 @@ function usePaywallTracking() {
|
|
|
713
718
|
...opts.variantKey && { variantKey: opts.variantKey },
|
|
714
719
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
715
720
|
...sessionId && { sessionId }
|
|
716
|
-
}).catch(() => {
|
|
721
|
+
}).catch((err) => {
|
|
722
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackProductSelected failed", err);
|
|
717
723
|
});
|
|
718
724
|
}, []);
|
|
719
725
|
const trackPurchased = useCallback(async (opts) => {
|
|
@@ -729,7 +735,8 @@ function usePaywallTracking() {
|
|
|
729
735
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
730
736
|
...sessionId && { sessionId }
|
|
731
737
|
});
|
|
732
|
-
} catch {
|
|
738
|
+
} catch (err) {
|
|
739
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] trackPurchased failed", err);
|
|
733
740
|
}
|
|
734
741
|
}, []);
|
|
735
742
|
return { trackDismissed, trackProductSelected, trackPurchased };
|
|
@@ -737,7 +744,7 @@ function usePaywallTracking() {
|
|
|
737
744
|
|
|
738
745
|
// src/domains/paywall/usePaywallActions.ts
|
|
739
746
|
var SCREEN_HEIGHT = Dimensions.get("window").height;
|
|
740
|
-
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId) {
|
|
747
|
+
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
741
748
|
const [isPurchasing, setIsPurchasing] = useState(false);
|
|
742
749
|
const [isClosing, setIsClosing] = useState(false);
|
|
743
750
|
const slideAnim = useRef(new Animated.Value(0)).current;
|
|
@@ -746,7 +753,8 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
746
753
|
const handleClose = useCallback2(() => {
|
|
747
754
|
if (isClosing) return;
|
|
748
755
|
setIsClosing(true);
|
|
749
|
-
|
|
756
|
+
const animTarget = openAnim ?? slideAnim;
|
|
757
|
+
Animated.timing(animTarget, { toValue: SCREEN_HEIGHT, duration: 350, useNativeDriver: true }).start(() => {
|
|
750
758
|
if (paywallConfig) {
|
|
751
759
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
752
760
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -755,7 +763,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
755
763
|
setIsClosing(false);
|
|
756
764
|
onResult({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
757
765
|
});
|
|
758
|
-
}, [isClosing, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
766
|
+
}, [isClosing, openAnim, onResult, paywallConfig, placement, slideAnim, trackDismissed, variantKey, campaignId]);
|
|
759
767
|
const handlePurchase = useCallback2(async (productId) => {
|
|
760
768
|
if (isPurchasing) return;
|
|
761
769
|
setIsPurchasing(true);
|
|
@@ -773,7 +781,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
773
781
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
774
782
|
}
|
|
775
783
|
} catch (error) {
|
|
776
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Purchase error:", error);
|
|
777
784
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
778
785
|
} finally {
|
|
779
786
|
setIsPurchasing(false);
|
|
@@ -794,7 +801,6 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
794
801
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
795
802
|
}
|
|
796
803
|
} catch (error) {
|
|
797
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] Restore error:", error);
|
|
798
804
|
onResult({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
799
805
|
} finally {
|
|
800
806
|
setIsPurchasing(false);
|
|
@@ -870,7 +876,8 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
870
876
|
const map = /* @__PURE__ */ new Map();
|
|
871
877
|
for (const p of storeProducts) map.set(p.productId, p);
|
|
872
878
|
setProducts(map);
|
|
873
|
-
} catch {
|
|
879
|
+
} catch (err) {
|
|
880
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] loadProducts in paywall failed", err);
|
|
874
881
|
setProducts(/* @__PURE__ */ new Map());
|
|
875
882
|
}
|
|
876
883
|
} else {
|
|
@@ -889,7 +896,6 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
889
896
|
}
|
|
890
897
|
} catch (err) {
|
|
891
898
|
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
899
|
setError(e);
|
|
894
900
|
}
|
|
895
901
|
};
|
|
@@ -898,18 +904,103 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
898
904
|
return { paywallConfig, products, error };
|
|
899
905
|
}
|
|
900
906
|
|
|
907
|
+
// src/utils/localization.ts
|
|
908
|
+
import { NativeModules as NativeModules2, Platform as Platform3 } from "react-native";
|
|
909
|
+
var currentLanguage = "pt-BR";
|
|
910
|
+
var defaultLanguage = "pt-BR";
|
|
911
|
+
function setCurrentLanguage(language) {
|
|
912
|
+
currentLanguage = language;
|
|
913
|
+
}
|
|
914
|
+
function setDefaultLanguage(language) {
|
|
915
|
+
defaultLanguage = language;
|
|
916
|
+
}
|
|
917
|
+
function getCurrentLanguage() {
|
|
918
|
+
return currentLanguage;
|
|
919
|
+
}
|
|
920
|
+
function getDefaultLanguage() {
|
|
921
|
+
return defaultLanguage;
|
|
922
|
+
}
|
|
923
|
+
function getLocalizedString(text) {
|
|
924
|
+
if (!text) return "";
|
|
925
|
+
if (typeof text === "string") return text;
|
|
926
|
+
return text[currentLanguage] ?? text[defaultLanguage] ?? Object.values(text)[0] ?? "";
|
|
927
|
+
}
|
|
928
|
+
function detectDeviceLanguage() {
|
|
929
|
+
try {
|
|
930
|
+
if (Platform3.OS === "ios") {
|
|
931
|
+
const settings = NativeModules2.SettingsManager?.settings;
|
|
932
|
+
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
933
|
+
}
|
|
934
|
+
return NativeModules2.I18nManager?.localeIdentifier ?? "en-US";
|
|
935
|
+
} catch {
|
|
936
|
+
return "en-US";
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
var SDK_STRINGS = {
|
|
940
|
+
paywallErrorTitle: {
|
|
941
|
+
"pt-BR": "Algo deu errado",
|
|
942
|
+
"pt": "Algo deu errado",
|
|
943
|
+
"en": "Something went wrong",
|
|
944
|
+
"en-US": "Something went wrong",
|
|
945
|
+
"en-GB": "Something went wrong",
|
|
946
|
+
"es": "Algo sali\xF3 mal",
|
|
947
|
+
"es-419": "Algo sali\xF3 mal"
|
|
948
|
+
},
|
|
949
|
+
paywallRetryButton: {
|
|
950
|
+
"pt-BR": "Tentar novamente",
|
|
951
|
+
"pt": "Tentar novamente",
|
|
952
|
+
"en": "Try again",
|
|
953
|
+
"en-US": "Try again",
|
|
954
|
+
"en-GB": "Try again",
|
|
955
|
+
"es": "Reintentar",
|
|
956
|
+
"es-419": "Reintentar"
|
|
957
|
+
},
|
|
958
|
+
paywallCloseButton: {
|
|
959
|
+
"pt-BR": "Fechar",
|
|
960
|
+
"pt": "Fechar",
|
|
961
|
+
"en": "Close",
|
|
962
|
+
"en-US": "Close",
|
|
963
|
+
"en-GB": "Close",
|
|
964
|
+
"es": "Cerrar",
|
|
965
|
+
"es-419": "Cerrar"
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
function getSdkString(key) {
|
|
969
|
+
const map = SDK_STRINGS[key];
|
|
970
|
+
const lang = currentLanguage;
|
|
971
|
+
if (lang in map) return map[lang];
|
|
972
|
+
const base = lang.split("-")[0];
|
|
973
|
+
if (base && base in map) return map[base];
|
|
974
|
+
if (defaultLanguage in map) return map[defaultLanguage];
|
|
975
|
+
return Object.values(map)[0];
|
|
976
|
+
}
|
|
977
|
+
function initLocalization(options) {
|
|
978
|
+
if (options?.defaultLanguage) {
|
|
979
|
+
defaultLanguage = options.defaultLanguage;
|
|
980
|
+
}
|
|
981
|
+
if (options?.detectDevice !== false) {
|
|
982
|
+
const deviceLanguage = detectDeviceLanguage();
|
|
983
|
+
currentLanguage = deviceLanguage;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
901
987
|
// src/domains/paywall/PaywallWebView.tsx
|
|
902
988
|
import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState3 } from "react";
|
|
903
989
|
import {
|
|
904
990
|
Linking,
|
|
905
991
|
NativeEventEmitter,
|
|
906
|
-
NativeModules as
|
|
992
|
+
NativeModules as NativeModules3,
|
|
907
993
|
requireNativeComponent,
|
|
908
994
|
StyleSheet,
|
|
909
995
|
View
|
|
910
996
|
} from "react-native";
|
|
911
997
|
|
|
912
998
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
999
|
+
function deriveMessageId(message) {
|
|
1000
|
+
if (message.id) return message.id;
|
|
1001
|
+
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
1002
|
+
return `${message.type}:${payloadKey}`;
|
|
1003
|
+
}
|
|
913
1004
|
function parseWebViewMessage(raw) {
|
|
914
1005
|
try {
|
|
915
1006
|
const parsed = JSON.parse(raw);
|
|
@@ -933,7 +1024,7 @@ function buildPaywallInjectionScript(data) {
|
|
|
933
1024
|
secondaryProductId: data.secondaryProductId
|
|
934
1025
|
}
|
|
935
1026
|
};
|
|
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);}
|
|
1027
|
+
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
1028
|
}
|
|
938
1029
|
function buildPurchaseStateScript(isPurchasing) {
|
|
939
1030
|
return `if(window.receiveNativeMessage){window.receiveNativeMessage({type:"purchaseState",data:{isPurchasing:${isPurchasing}}});}true;`;
|
|
@@ -951,7 +1042,7 @@ function getNativeWebView() {
|
|
|
951
1042
|
var webViewEmitter = null;
|
|
952
1043
|
function getWebViewEmitter() {
|
|
953
1044
|
if (!webViewEmitter) {
|
|
954
|
-
webViewEmitter = new NativeEventEmitter(
|
|
1045
|
+
webViewEmitter = new NativeEventEmitter(NativeModules3.PaywalloWebViewModule);
|
|
955
1046
|
}
|
|
956
1047
|
return webViewEmitter;
|
|
957
1048
|
}
|
|
@@ -969,6 +1060,7 @@ function PaywallWebView({
|
|
|
969
1060
|
primaryProductId,
|
|
970
1061
|
secondaryProductId,
|
|
971
1062
|
isPurchasing,
|
|
1063
|
+
webUrl: webUrlProp,
|
|
972
1064
|
onPurchase,
|
|
973
1065
|
onClose,
|
|
974
1066
|
onRestore,
|
|
@@ -980,8 +1072,39 @@ function PaywallWebView({
|
|
|
980
1072
|
const [scriptToInject, setScriptToInject] = useState3(void 0);
|
|
981
1073
|
const onErrorRef = useRef3(onError);
|
|
982
1074
|
onErrorRef.current = onError;
|
|
1075
|
+
const onReadyRef = useRef3(onReady);
|
|
1076
|
+
onReadyRef.current = onReady;
|
|
983
1077
|
const NativeWebView = useMemo(() => getNativeWebView(), []);
|
|
984
|
-
const
|
|
1078
|
+
const [resolvedWebUrl, setResolvedWebUrl] = useState3(
|
|
1079
|
+
() => webUrlProp ?? PaywalloClient.getWebUrl()
|
|
1080
|
+
);
|
|
1081
|
+
useEffect2(() => {
|
|
1082
|
+
if (webUrlProp !== void 0) {
|
|
1083
|
+
setResolvedWebUrl(webUrlProp ?? null);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const url = PaywalloClient.getWebUrl();
|
|
1087
|
+
if (url) {
|
|
1088
|
+
setResolvedWebUrl(url);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const interval = setInterval(() => {
|
|
1092
|
+
const u = PaywalloClient.getWebUrl();
|
|
1093
|
+
if (u) {
|
|
1094
|
+
setResolvedWebUrl(u);
|
|
1095
|
+
clearInterval(interval);
|
|
1096
|
+
}
|
|
1097
|
+
}, 200);
|
|
1098
|
+
return () => clearInterval(interval);
|
|
1099
|
+
}, [webUrlProp]);
|
|
1100
|
+
useEffect2(() => {
|
|
1101
|
+
if (!resolvedWebUrl) {
|
|
1102
|
+
onErrorRef.current?.({
|
|
1103
|
+
code: -1,
|
|
1104
|
+
description: "Paywall URL not configured. SDK may not be initialized."
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
}, []);
|
|
985
1108
|
const buildAndSetInjectionScript = useCallback3(() => {
|
|
986
1109
|
if (!craftData || paywallDataSent.current) return;
|
|
987
1110
|
paywallDataSent.current = true;
|
|
@@ -992,19 +1115,21 @@ function PaywallWebView({
|
|
|
992
1115
|
secondaryProductId
|
|
993
1116
|
});
|
|
994
1117
|
setScriptToInject(script);
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const lastProcessedRef = useRef3({ type: "", time: 0 });
|
|
1118
|
+
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1119
|
+
const processedIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
998
1120
|
const handleParsedMessage = useCallback3(
|
|
999
1121
|
(message) => {
|
|
1000
|
-
const
|
|
1001
|
-
if (
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
lastProcessedRef.current = { type: message.type, time: now };
|
|
1122
|
+
const msgId = deriveMessageId(message);
|
|
1123
|
+
if (processedIdsRef.current.has(msgId)) return;
|
|
1124
|
+
processedIdsRef.current.add(msgId);
|
|
1125
|
+
setTimeout(() => processedIdsRef.current.delete(msgId), 5e3);
|
|
1005
1126
|
switch (message.type) {
|
|
1006
1127
|
case "ready":
|
|
1007
|
-
if (!paywallDataSent.current)
|
|
1128
|
+
if (!paywallDataSent.current) {
|
|
1129
|
+
buildAndSetInjectionScript();
|
|
1130
|
+
} else {
|
|
1131
|
+
onReadyRef.current?.();
|
|
1132
|
+
}
|
|
1008
1133
|
break;
|
|
1009
1134
|
case "purchase":
|
|
1010
1135
|
if (message.payload?.productId) onPurchase(message.payload.productId);
|
|
@@ -1095,7 +1220,8 @@ function PaywallWebView({
|
|
|
1095
1220
|
if (!paywallDataSent.current) return;
|
|
1096
1221
|
setScriptToInject(buildPurchaseStateScript(isPurchasing));
|
|
1097
1222
|
}, [isPurchasing]);
|
|
1098
|
-
|
|
1223
|
+
if (!resolvedWebUrl) return null;
|
|
1224
|
+
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1099
1225
|
return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsx(
|
|
1100
1226
|
NativeWebView,
|
|
1101
1227
|
{
|
|
@@ -1116,6 +1242,18 @@ var styles = StyleSheet.create({
|
|
|
1116
1242
|
// src/domains/paywall/PaywallModal.tsx
|
|
1117
1243
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1118
1244
|
var SCREEN_HEIGHT2 = Dimensions2.get("window").height;
|
|
1245
|
+
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1246
|
+
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1247
|
+
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1248
|
+
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1249
|
+
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1250
|
+
return /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
|
|
1251
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: title }),
|
|
1252
|
+
/* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: description }),
|
|
1253
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: retryLabel }) }),
|
|
1254
|
+
/* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: closeLabel }) })
|
|
1255
|
+
] }) });
|
|
1256
|
+
}
|
|
1119
1257
|
function PaywallModal({
|
|
1120
1258
|
placement,
|
|
1121
1259
|
visible,
|
|
@@ -1133,7 +1271,13 @@ function PaywallModal({
|
|
|
1133
1271
|
variantKey,
|
|
1134
1272
|
campaignId
|
|
1135
1273
|
);
|
|
1136
|
-
const
|
|
1274
|
+
const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
|
|
1275
|
+
const [modalVisible, setModalVisible] = useState4(false);
|
|
1276
|
+
const handleResult = useCallback4((result) => {
|
|
1277
|
+
setModalVisible(false);
|
|
1278
|
+
onResult(result);
|
|
1279
|
+
}, [onResult]);
|
|
1280
|
+
const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim);
|
|
1137
1281
|
const [webViewError, setWebViewError] = useState4(null);
|
|
1138
1282
|
const [retryCount, setRetryCount] = useState4(0);
|
|
1139
1283
|
const retryCountRef = useRef4(0);
|
|
@@ -1153,9 +1297,9 @@ function PaywallModal({
|
|
|
1153
1297
|
setWebViewError(null);
|
|
1154
1298
|
setRetryCount(0);
|
|
1155
1299
|
}, []);
|
|
1156
|
-
const openAnim = useRef4(new Animated2.Value(SCREEN_HEIGHT2)).current;
|
|
1157
1300
|
useEffect3(() => {
|
|
1158
1301
|
if (visible) {
|
|
1302
|
+
setModalVisible(true);
|
|
1159
1303
|
openAnim.setValue(SCREEN_HEIGHT2);
|
|
1160
1304
|
Animated2.timing(openAnim, {
|
|
1161
1305
|
toValue: 0,
|
|
@@ -1165,11 +1309,11 @@ function PaywallModal({
|
|
|
1165
1309
|
}).start();
|
|
1166
1310
|
}
|
|
1167
1311
|
}, [visible, openAnim]);
|
|
1168
|
-
if (!visible) return null;
|
|
1312
|
+
if (!modalVisible && !visible) return null;
|
|
1169
1313
|
return /* @__PURE__ */ jsx2(
|
|
1170
1314
|
Modal,
|
|
1171
1315
|
{
|
|
1172
|
-
visible,
|
|
1316
|
+
visible: modalVisible,
|
|
1173
1317
|
animationType: "none",
|
|
1174
1318
|
presentationStyle: "overFullScreen",
|
|
1175
1319
|
onRequestClose: handleClose,
|
|
@@ -1177,12 +1321,14 @@ function PaywallModal({
|
|
|
1177
1321
|
statusBarTranslucent: true,
|
|
1178
1322
|
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
1323
|
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
|
-
|
|
1324
|
+
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
|
|
1325
|
+
ErrorFallback,
|
|
1326
|
+
{
|
|
1327
|
+
description: webViewError.description,
|
|
1328
|
+
onRetry: handleRetry,
|
|
1329
|
+
onClose: handleClose
|
|
1330
|
+
}
|
|
1331
|
+
) : /* @__PURE__ */ jsx2(
|
|
1186
1332
|
PaywallWebView,
|
|
1187
1333
|
{
|
|
1188
1334
|
paywallId: paywallConfig.id,
|
|
@@ -1267,7 +1413,11 @@ var PaywallErrorBoundary = class extends Component {
|
|
|
1267
1413
|
return { hasError: true };
|
|
1268
1414
|
}
|
|
1269
1415
|
componentDidCatch(error) {
|
|
1270
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
1416
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] Paywall render error:", error);
|
|
1417
|
+
try {
|
|
1418
|
+
PaywalloClient.getApiClient()?.reportError("PAYWALL_RENDER_ERROR", error.message);
|
|
1419
|
+
} catch {
|
|
1420
|
+
}
|
|
1271
1421
|
}
|
|
1272
1422
|
render() {
|
|
1273
1423
|
return this.state.hasError ? null : this.props.children;
|
|
@@ -1290,7 +1440,7 @@ function usePaywallContext() {
|
|
|
1290
1440
|
|
|
1291
1441
|
// src/domains/paywall/usePaywallPresenter.ts
|
|
1292
1442
|
import { useCallback as useCallback5, useEffect as useEffect4, useRef as useRef5, useState as useState5 } from "react";
|
|
1293
|
-
function usePaywallPresenter(preloadedWebView,
|
|
1443
|
+
function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
1294
1444
|
const [showingPreloadedWebView, setShowingPreloadedWebView] = useState5(false);
|
|
1295
1445
|
const resolverRef = useRef5(null);
|
|
1296
1446
|
const presentedAtRef = useRef5(Date.now());
|
|
@@ -1339,9 +1489,8 @@ function usePaywallPresenter(preloadedWebView, clearPreloadedWebView, setPreload
|
|
|
1339
1489
|
};
|
|
1340
1490
|
void track();
|
|
1341
1491
|
setShowingPreloadedWebView(false);
|
|
1342
|
-
clearPreloadedWebView();
|
|
1343
1492
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: true, restored: false });
|
|
1344
|
-
}, [preloadedWebView,
|
|
1493
|
+
}, [preloadedWebView, resolvePreloadedPaywall]);
|
|
1345
1494
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1346
1495
|
}
|
|
1347
1496
|
|
|
@@ -1549,9 +1698,7 @@ var AffiliateManager = class {
|
|
|
1549
1698
|
}
|
|
1550
1699
|
}
|
|
1551
1700
|
log(...args) {
|
|
1552
|
-
if (this.debug)
|
|
1553
|
-
console.warn("[AffiliateManager]", ...args);
|
|
1554
|
-
}
|
|
1701
|
+
if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
|
|
1555
1702
|
}
|
|
1556
1703
|
};
|
|
1557
1704
|
var affiliateManager = new AffiliateManager();
|
|
@@ -1569,26 +1716,42 @@ var PaywalloAffiliate = {
|
|
|
1569
1716
|
};
|
|
1570
1717
|
|
|
1571
1718
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1572
|
-
var
|
|
1719
|
+
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1720
|
+
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1573
1721
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1722
|
+
function keyFor(distinctId) {
|
|
1723
|
+
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
1724
|
+
}
|
|
1574
1725
|
var SubscriptionCache = class {
|
|
1575
1726
|
constructor(ttl = DEFAULT_TTL) {
|
|
1576
|
-
this.memoryCache =
|
|
1727
|
+
this.memoryCache = /* @__PURE__ */ new Map();
|
|
1728
|
+
this.legacyCleanupDone = false;
|
|
1577
1729
|
this.ttl = ttl;
|
|
1578
1730
|
this.storage = new SecureStorage();
|
|
1579
1731
|
}
|
|
1580
|
-
async
|
|
1581
|
-
if (this.
|
|
1582
|
-
|
|
1732
|
+
async cleanupLegacyCache() {
|
|
1733
|
+
if (this.legacyCleanupDone) return;
|
|
1734
|
+
this.legacyCleanupDone = true;
|
|
1735
|
+
try {
|
|
1736
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1737
|
+
} catch {
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
async get(distinctId) {
|
|
1741
|
+
await this.cleanupLegacyCache();
|
|
1742
|
+
if (!distinctId) return null;
|
|
1743
|
+
const memEntry = this.memoryCache.get(distinctId);
|
|
1744
|
+
if (memEntry && !this.isExpired(memEntry)) {
|
|
1745
|
+
return memEntry;
|
|
1583
1746
|
}
|
|
1584
1747
|
try {
|
|
1585
|
-
const stored = await this.storage.get(
|
|
1748
|
+
const stored = await this.storage.get(keyFor(distinctId));
|
|
1586
1749
|
if (!stored) {
|
|
1587
1750
|
return null;
|
|
1588
1751
|
}
|
|
1589
1752
|
const parsed = JSON.parse(stored);
|
|
1590
1753
|
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(
|
|
1754
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1592
1755
|
return null;
|
|
1593
1756
|
}
|
|
1594
1757
|
const cached = parsed;
|
|
@@ -1617,32 +1780,39 @@ var SubscriptionCache = class {
|
|
|
1617
1780
|
}
|
|
1618
1781
|
cached.isStale = this.isExpired(cached);
|
|
1619
1782
|
if (!cached.isStale) {
|
|
1620
|
-
this.memoryCache
|
|
1783
|
+
this.memoryCache.set(distinctId, cached);
|
|
1621
1784
|
}
|
|
1622
1785
|
return cached;
|
|
1623
1786
|
} catch {
|
|
1624
1787
|
return null;
|
|
1625
1788
|
}
|
|
1626
1789
|
}
|
|
1627
|
-
async set(data) {
|
|
1790
|
+
async set(distinctId, data) {
|
|
1791
|
+
if (!distinctId) return;
|
|
1628
1792
|
const cached = {
|
|
1629
1793
|
data,
|
|
1630
1794
|
cachedAt: Date.now(),
|
|
1631
1795
|
isStale: false
|
|
1632
1796
|
};
|
|
1633
|
-
this.memoryCache
|
|
1797
|
+
this.memoryCache.set(distinctId, cached);
|
|
1634
1798
|
try {
|
|
1635
|
-
await this.storage.set(
|
|
1636
|
-
} catch
|
|
1637
|
-
if (__DEV__) console.warn("[SubscriptionCache] set() failed:", error instanceof Error ? error.message : String(error));
|
|
1799
|
+
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1800
|
+
} catch {
|
|
1638
1801
|
}
|
|
1639
1802
|
}
|
|
1640
|
-
async invalidate() {
|
|
1641
|
-
|
|
1803
|
+
async invalidate(distinctId) {
|
|
1804
|
+
if (!distinctId) return;
|
|
1805
|
+
this.memoryCache.delete(distinctId);
|
|
1642
1806
|
try {
|
|
1643
|
-
await this.storage.remove(
|
|
1644
|
-
} catch
|
|
1645
|
-
|
|
1807
|
+
await this.storage.remove(keyFor(distinctId));
|
|
1808
|
+
} catch {
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
async invalidateAll() {
|
|
1812
|
+
this.memoryCache.clear();
|
|
1813
|
+
try {
|
|
1814
|
+
await this.storage.remove(LEGACY_CACHE_KEY);
|
|
1815
|
+
} catch {
|
|
1646
1816
|
}
|
|
1647
1817
|
}
|
|
1648
1818
|
isExpired(cached) {
|
|
@@ -1654,60 +1824,214 @@ var SubscriptionCache = class {
|
|
|
1654
1824
|
};
|
|
1655
1825
|
var subscriptionCache = new SubscriptionCache();
|
|
1656
1826
|
|
|
1657
|
-
// src/
|
|
1658
|
-
import { Platform as
|
|
1827
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1828
|
+
import { Platform as Platform4 } from "react-native";
|
|
1659
1829
|
|
|
1660
|
-
// src/
|
|
1661
|
-
var
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1830
|
+
// src/domains/session/SessionError.ts
|
|
1831
|
+
var SessionError = class extends PaywalloError {
|
|
1832
|
+
constructor(code, message) {
|
|
1833
|
+
super("session", code, message);
|
|
1834
|
+
this.name = "SessionError";
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
var SESSION_ERROR_CODES = {
|
|
1838
|
+
NOT_INITIALIZED: "SESSION_NOT_INITIALIZED",
|
|
1839
|
+
START_FAILED: "SESSION_START_FAILED",
|
|
1840
|
+
END_FAILED: "SESSION_END_FAILED",
|
|
1841
|
+
RESTORE_FAILED: "SESSION_RESTORE_FAILED"
|
|
1842
|
+
};
|
|
1843
|
+
|
|
1844
|
+
// src/domains/subscription/SubscriptionManager.ts
|
|
1845
|
+
var SubscriptionManagerClass = class {
|
|
1846
|
+
constructor() {
|
|
1847
|
+
this.config = null;
|
|
1848
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
1849
|
+
this.userId = null;
|
|
1850
|
+
this.cache = new SubscriptionCache();
|
|
1851
|
+
}
|
|
1852
|
+
cacheKey() {
|
|
1853
|
+
return this.userId ?? "__anonymous__";
|
|
1854
|
+
}
|
|
1855
|
+
init(config) {
|
|
1856
|
+
this.config = config;
|
|
1857
|
+
if (config.cacheTTL) {
|
|
1858
|
+
this.cache.setTTL(config.cacheTTL);
|
|
1668
1859
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1860
|
+
}
|
|
1861
|
+
setUserId(userId) {
|
|
1862
|
+
if (this.userId !== userId) {
|
|
1863
|
+
this.userId = userId;
|
|
1864
|
+
void this.cache.invalidateAll();
|
|
1672
1865
|
}
|
|
1673
|
-
return DEFAULT_WEB_URL;
|
|
1674
|
-
} catch {
|
|
1675
|
-
return DEFAULT_WEB_URL;
|
|
1676
1866
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1867
|
+
async hasActiveSubscription(forceRefresh = false) {
|
|
1868
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1869
|
+
return status.hasActiveSubscription;
|
|
1870
|
+
}
|
|
1871
|
+
async getSubscription(forceRefresh = false) {
|
|
1872
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1873
|
+
return status.subscription;
|
|
1874
|
+
}
|
|
1875
|
+
async getEntitlements(forceRefresh = false) {
|
|
1876
|
+
const status = await this.getSubscriptionStatus(forceRefresh);
|
|
1877
|
+
return status.entitlements;
|
|
1878
|
+
}
|
|
1879
|
+
async getSubscriptionStatus(forceRefresh = false) {
|
|
1880
|
+
if (!this.config) {
|
|
1881
|
+
return this.getEmptyStatus();
|
|
1882
|
+
}
|
|
1883
|
+
if (!forceRefresh) {
|
|
1884
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1885
|
+
if (cached && !cached.isStale) {
|
|
1886
|
+
return cached.data;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
try {
|
|
1890
|
+
const status = await this.fetchSubscriptionStatus();
|
|
1891
|
+
await this.cache.set(this.cacheKey(), status);
|
|
1892
|
+
this.notifyListeners(status);
|
|
1893
|
+
return status;
|
|
1894
|
+
} catch (error) {
|
|
1895
|
+
this.log("Failed to fetch subscription status", error);
|
|
1896
|
+
const cached = await this.cache.get(this.cacheKey());
|
|
1897
|
+
if (cached) {
|
|
1898
|
+
return cached.data;
|
|
1899
|
+
}
|
|
1900
|
+
return this.getEmptyStatus();
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
async restorePurchases() {
|
|
1904
|
+
if (!this.config) {
|
|
1905
|
+
throw new SessionError(
|
|
1906
|
+
SESSION_ERROR_CODES.NOT_INITIALIZED,
|
|
1907
|
+
"SubscriptionManager not initialized"
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1911
|
+
return this.getSubscriptionStatus(true);
|
|
1912
|
+
}
|
|
1913
|
+
async fetchSubscriptionStatus() {
|
|
1914
|
+
if (!this.config) {
|
|
1915
|
+
return this.getEmptyStatus();
|
|
1916
|
+
}
|
|
1917
|
+
const url = new URL(`${this.config.serverUrl}/subscriptions/status`);
|
|
1918
|
+
if (this.userId) {
|
|
1919
|
+
url.searchParams.set("userId", this.userId);
|
|
1920
|
+
}
|
|
1921
|
+
url.searchParams.set("platform", Platform4.OS);
|
|
1922
|
+
const response = await fetch(url.toString(), {
|
|
1923
|
+
method: "GET",
|
|
1924
|
+
headers: {
|
|
1925
|
+
"Content-Type": "application/json",
|
|
1926
|
+
"X-App-Key": this.config.appKey
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
if (!response.ok) {
|
|
1930
|
+
throw new SessionError(
|
|
1931
|
+
SESSION_ERROR_CODES.RESTORE_FAILED,
|
|
1932
|
+
`Failed to fetch subscription status: ${response.status}`
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
const data = await response.json();
|
|
1936
|
+
if (data.subscription) {
|
|
1937
|
+
data.subscription.expiresAt = new Date(data.subscription.expiresAt);
|
|
1938
|
+
data.subscription.originalPurchaseDate = new Date(data.subscription.originalPurchaseDate);
|
|
1939
|
+
data.subscription.latestPurchaseDate = new Date(data.subscription.latestPurchaseDate);
|
|
1940
|
+
if (data.subscription.cancellationDate) {
|
|
1941
|
+
data.subscription.cancellationDate = new Date(data.subscription.cancellationDate);
|
|
1942
|
+
}
|
|
1943
|
+
if (data.subscription.gracePeriodExpiresAt) {
|
|
1944
|
+
data.subscription.gracePeriodExpiresAt = new Date(data.subscription.gracePeriodExpiresAt);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return data;
|
|
1948
|
+
}
|
|
1949
|
+
getEmptyStatus() {
|
|
1950
|
+
return {
|
|
1951
|
+
hasActiveSubscription: false,
|
|
1952
|
+
subscription: null,
|
|
1953
|
+
entitlements: []
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
addListener(listener) {
|
|
1957
|
+
this.listeners.add(listener);
|
|
1958
|
+
return () => this.listeners.delete(listener);
|
|
1959
|
+
}
|
|
1960
|
+
notifyListeners(status) {
|
|
1961
|
+
for (const listener of this.listeners) {
|
|
1962
|
+
listener(status);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
async onPurchaseComplete() {
|
|
1966
|
+
await this.cache.invalidate(this.cacheKey());
|
|
1967
|
+
await this.getSubscriptionStatus(true);
|
|
1968
|
+
}
|
|
1969
|
+
log(...args) {
|
|
1970
|
+
if (this.config?.debug) console.log("[Paywallo:Subscription]", ...args);
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
var subscriptionManager = new SubscriptionManagerClass();
|
|
1974
|
+
|
|
1975
|
+
// src/core/ApiClient.ts
|
|
1976
|
+
import { Platform as Platform5 } from "react-native";
|
|
1977
|
+
|
|
1978
|
+
// src/core/apiUrlUtils.ts
|
|
1979
|
+
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
1980
|
+
function deriveWebUrl(baseUrl) {
|
|
1981
|
+
try {
|
|
1982
|
+
const url = new URL(baseUrl);
|
|
1983
|
+
if (url.hostname.startsWith("192.168") || url.hostname === "localhost") {
|
|
1984
|
+
url.port = "3000";
|
|
1985
|
+
return url.toString().replace(/\/$/, "");
|
|
1986
|
+
}
|
|
1987
|
+
if (url.hostname.startsWith("api.")) {
|
|
1988
|
+
url.hostname = url.hostname.replace("api.", "app.");
|
|
1989
|
+
return url.toString().replace(/\/$/, "");
|
|
1990
|
+
}
|
|
1991
|
+
return DEFAULT_WEB_URL;
|
|
1992
|
+
} catch {
|
|
1993
|
+
return DEFAULT_WEB_URL;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
function buildConditionalFlagUrl(baseUrl, flagKey, context) {
|
|
1997
|
+
const url = new URL(`${baseUrl}/api/v1/conditional-flags/${flagKey}`);
|
|
1998
|
+
if (context.platform) url.searchParams.set("platform", context.platform);
|
|
1999
|
+
if (context.appVersion) url.searchParams.set("appVersion", context.appVersion);
|
|
2000
|
+
if (context.country) url.searchParams.set("country", context.country);
|
|
2001
|
+
if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
|
|
2002
|
+
return url.toString();
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// src/domains/iap/apiPurchaseMethods.ts
|
|
2006
|
+
async function validatePurchase(client, appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2007
|
+
const response = await client.post(
|
|
2008
|
+
`/purchases/validate/${appKey}`,
|
|
2009
|
+
{
|
|
2010
|
+
platform,
|
|
2011
|
+
receipt,
|
|
2012
|
+
productId,
|
|
2013
|
+
transactionId,
|
|
2014
|
+
priceLocal,
|
|
2015
|
+
currency,
|
|
2016
|
+
country,
|
|
2017
|
+
distinctId,
|
|
2018
|
+
...paywallPlacement !== void 0 && { paywallPlacement },
|
|
2019
|
+
...variantKey !== void 0 && { variantKey }
|
|
2020
|
+
},
|
|
2021
|
+
false
|
|
2022
|
+
);
|
|
2023
|
+
if (!response.ok) {
|
|
2024
|
+
throw new Error(`Server validation failed: HTTP ${response.status}`);
|
|
2025
|
+
}
|
|
2026
|
+
return response.data;
|
|
2027
|
+
}
|
|
2028
|
+
async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
1708
2029
|
const url = new URL(`${baseUrl}/purchases/status/${appKey}`);
|
|
1709
2030
|
url.searchParams.set("distinctId", distinctId);
|
|
1710
2031
|
const response = await client.get(url.toString());
|
|
2032
|
+
if (!response.ok) {
|
|
2033
|
+
throw new Error(`Subscription status check failed: HTTP ${response.status}`);
|
|
2034
|
+
}
|
|
1711
2035
|
return response.data;
|
|
1712
2036
|
}
|
|
1713
2037
|
async function getEmergencyPaywall(client) {
|
|
@@ -1887,10 +2211,8 @@ var HttpClient = class {
|
|
|
1887
2211
|
sleep(ms) {
|
|
1888
2212
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1889
2213
|
}
|
|
1890
|
-
log(
|
|
1891
|
-
if (this.config.debug)
|
|
1892
|
-
console.warn("[HttpClient]", message, data ?? "");
|
|
1893
|
-
}
|
|
2214
|
+
log(...args) {
|
|
2215
|
+
if (this.config.debug) console.log("[Paywallo:Http]", ...args);
|
|
1894
2216
|
}
|
|
1895
2217
|
setDebug(debug) {
|
|
1896
2218
|
this.config.debug = debug;
|
|
@@ -1916,8 +2238,6 @@ var NetworkMonitorClass = class {
|
|
|
1916
2238
|
this.config = DEFAULT_NETWORK_CONFIG;
|
|
1917
2239
|
this.listeners = /* @__PURE__ */ new Set();
|
|
1918
2240
|
this.currentState = "unknown";
|
|
1919
|
-
this.netInfoModule = null;
|
|
1920
|
-
this.netInfoSubscription = null;
|
|
1921
2241
|
this.appStateSubscription = null;
|
|
1922
2242
|
this.initialized = false;
|
|
1923
2243
|
this.checkIntervalId = null;
|
|
@@ -1933,13 +2253,6 @@ var NetworkMonitorClass = class {
|
|
|
1933
2253
|
void this.setupFallbackDetection();
|
|
1934
2254
|
this.setupAppStateListener();
|
|
1935
2255
|
}
|
|
1936
|
-
setupNetInfoListener() {
|
|
1937
|
-
if (!this.netInfoModule) return;
|
|
1938
|
-
this.netInfoSubscription = this.netInfoModule.addEventListener((state) => {
|
|
1939
|
-
const newState = this.netInfoStateToNetworkState(state);
|
|
1940
|
-
this.updateState(newState);
|
|
1941
|
-
});
|
|
1942
|
-
}
|
|
1943
2256
|
async setupFallbackDetection() {
|
|
1944
2257
|
await this.checkConnectivity();
|
|
1945
2258
|
this.checkIntervalId = setInterval(() => {
|
|
@@ -1967,15 +2280,6 @@ var NetworkMonitorClass = class {
|
|
|
1967
2280
|
this.updateState("offline");
|
|
1968
2281
|
}
|
|
1969
2282
|
}
|
|
1970
|
-
netInfoStateToNetworkState(state) {
|
|
1971
|
-
if (state.isConnected === null) {
|
|
1972
|
-
return "unknown";
|
|
1973
|
-
}
|
|
1974
|
-
if (!state.isConnected) {
|
|
1975
|
-
return "offline";
|
|
1976
|
-
}
|
|
1977
|
-
return "online";
|
|
1978
|
-
}
|
|
1979
2283
|
updateState(newState) {
|
|
1980
2284
|
const previousState = this.currentState;
|
|
1981
2285
|
if (previousState === newState) {
|
|
@@ -2018,10 +2322,6 @@ var NetworkMonitorClass = class {
|
|
|
2018
2322
|
return this.currentState;
|
|
2019
2323
|
}
|
|
2020
2324
|
dispose() {
|
|
2021
|
-
if (this.netInfoSubscription) {
|
|
2022
|
-
this.netInfoSubscription();
|
|
2023
|
-
this.netInfoSubscription = null;
|
|
2024
|
-
}
|
|
2025
2325
|
if (this.appStateSubscription) {
|
|
2026
2326
|
this.appStateSubscription.remove();
|
|
2027
2327
|
this.appStateSubscription = null;
|
|
@@ -2034,10 +2334,8 @@ var NetworkMonitorClass = class {
|
|
|
2034
2334
|
this.initialized = false;
|
|
2035
2335
|
this.currentState = "unknown";
|
|
2036
2336
|
}
|
|
2037
|
-
log(
|
|
2038
|
-
if (this.config.debug)
|
|
2039
|
-
console.warn("[NetworkMonitor]", message, data ?? "");
|
|
2040
|
-
}
|
|
2337
|
+
log(...args) {
|
|
2338
|
+
if (this.config.debug) console.log("[Paywallo:Network]", ...args);
|
|
2041
2339
|
}
|
|
2042
2340
|
setDebug(debug) {
|
|
2043
2341
|
this.config.debug = debug;
|
|
@@ -2047,6 +2345,7 @@ var networkMonitor = new NetworkMonitorClass();
|
|
|
2047
2345
|
|
|
2048
2346
|
// src/core/queue/OfflineQueue.ts
|
|
2049
2347
|
import AsyncStorage2 from "@react-native-async-storage/async-storage";
|
|
2348
|
+
init_uuid();
|
|
2050
2349
|
|
|
2051
2350
|
// src/core/queue/deduplication.ts
|
|
2052
2351
|
function normalizeForComparison(obj) {
|
|
@@ -2067,6 +2366,8 @@ function normalizeForComparison(obj) {
|
|
|
2067
2366
|
return "{" + pairs.join(",") + "}";
|
|
2068
2367
|
}
|
|
2069
2368
|
function findDuplicateIndex(queue, item) {
|
|
2369
|
+
const byId = queue.findIndex((existing) => existing.id === item.id);
|
|
2370
|
+
if (byId !== -1) return byId;
|
|
2070
2371
|
const normalizedBody = normalizeForComparison(item.body);
|
|
2071
2372
|
return queue.findIndex(
|
|
2072
2373
|
(existing) => existing.url === item.url && normalizeForComparison(existing.body) === normalizedBody
|
|
@@ -2135,9 +2436,9 @@ var OfflineQueueClass = class {
|
|
|
2135
2436
|
void this.saveToStorage();
|
|
2136
2437
|
}
|
|
2137
2438
|
}
|
|
2138
|
-
async enqueue(method, url, body, headers) {
|
|
2439
|
+
async enqueue(method, url, body, headers, id) {
|
|
2139
2440
|
const item = {
|
|
2140
|
-
id: generateUUID(),
|
|
2441
|
+
id: id ?? generateUUID(),
|
|
2141
2442
|
method,
|
|
2142
2443
|
url,
|
|
2143
2444
|
body,
|
|
@@ -2221,7 +2522,6 @@ var OfflineQueueClass = class {
|
|
|
2221
2522
|
this.emit({ type: "processing:end", queueSize: this.queue.length });
|
|
2222
2523
|
return { processed, failed };
|
|
2223
2524
|
}
|
|
2224
|
-
/** Returns true if the item was permanently removed (counted as failed). */
|
|
2225
2525
|
async handleItemFailure(item, error, permanent = false) {
|
|
2226
2526
|
if (permanent || item.attempts >= item.maxAttempts) {
|
|
2227
2527
|
await this.removeItem(item.id);
|
|
@@ -2283,10 +2583,8 @@ var OfflineQueueClass = class {
|
|
|
2283
2583
|
}
|
|
2284
2584
|
}
|
|
2285
2585
|
}
|
|
2286
|
-
log(
|
|
2287
|
-
if (this.config.debug)
|
|
2288
|
-
console.warn("[OfflineQueue]", message, data ?? "");
|
|
2289
|
-
}
|
|
2586
|
+
log(...args) {
|
|
2587
|
+
if (this.config.debug) console.log("[Paywallo:Queue]", ...args);
|
|
2290
2588
|
}
|
|
2291
2589
|
setDebug(debug) {
|
|
2292
2590
|
this.config.debug = debug;
|
|
@@ -2428,10 +2726,8 @@ var QueueProcessorClass = class {
|
|
|
2428
2726
|
this.httpClient = null;
|
|
2429
2727
|
this.initialized = false;
|
|
2430
2728
|
}
|
|
2431
|
-
log(
|
|
2432
|
-
if (this.config.debug)
|
|
2433
|
-
console.warn("[QueueProcessor]", message, data ?? "");
|
|
2434
|
-
}
|
|
2729
|
+
log(...args) {
|
|
2730
|
+
if (this.config.debug) console.log("[Paywallo:QueueProcessor]", ...args);
|
|
2435
2731
|
}
|
|
2436
2732
|
setDebug(debug) {
|
|
2437
2733
|
this.config.debug = debug;
|
|
@@ -2443,10 +2739,9 @@ var queueProcessor = new QueueProcessorClass();
|
|
|
2443
2739
|
var ApiCache = class {
|
|
2444
2740
|
constructor() {
|
|
2445
2741
|
this.CACHE_TTL = 3e5;
|
|
2446
|
-
// 5 minutes
|
|
2447
2742
|
this.CACHE_NULL_TTL = 3e4;
|
|
2448
|
-
// 30 seconds — for null/404 variants
|
|
2449
2743
|
this.MAX_SIZE = 200;
|
|
2744
|
+
this.STALE_WINDOW_MS = 864e5;
|
|
2450
2745
|
this.variantCache = /* @__PURE__ */ new Map();
|
|
2451
2746
|
this.paywallCache = /* @__PURE__ */ new Map();
|
|
2452
2747
|
this.campaignCache = /* @__PURE__ */ new Map();
|
|
@@ -2484,7 +2779,7 @@ var ApiCache = class {
|
|
|
2484
2779
|
}
|
|
2485
2780
|
getStaleVariant(key) {
|
|
2486
2781
|
const entry = this.variantCache.get(key);
|
|
2487
|
-
if (entry && Date.now() - entry.ts <
|
|
2782
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2488
2783
|
return void 0;
|
|
2489
2784
|
}
|
|
2490
2785
|
getPaywall(key) {
|
|
@@ -2496,42 +2791,42 @@ var ApiCache = class {
|
|
|
2496
2791
|
}
|
|
2497
2792
|
getStalePaywall(key) {
|
|
2498
2793
|
const entry = this.paywallCache.get(key);
|
|
2499
|
-
if (entry && Date.now() - entry.ts <
|
|
2794
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2500
2795
|
return void 0;
|
|
2501
2796
|
}
|
|
2502
2797
|
getCampaign(key) {
|
|
2503
2798
|
return this.getCached(this.campaignCache, key);
|
|
2504
2799
|
}
|
|
2505
|
-
setCampaign(key, value) {
|
|
2506
|
-
this.setCached(this.campaignCache, key, value);
|
|
2800
|
+
setCampaign(key, value, ttlOverride) {
|
|
2801
|
+
this.setCached(this.campaignCache, key, value, ttlOverride);
|
|
2507
2802
|
this.evictExpiredEntries(this.campaignCache);
|
|
2508
2803
|
}
|
|
2509
2804
|
getStaleCampaign(key) {
|
|
2510
2805
|
const entry = this.campaignCache.get(key);
|
|
2511
|
-
if (entry && Date.now() - entry.ts <
|
|
2806
|
+
if (entry && Date.now() - entry.ts < this.STALE_WINDOW_MS) return entry.value;
|
|
2512
2807
|
return void 0;
|
|
2513
2808
|
}
|
|
2514
2809
|
};
|
|
2515
2810
|
|
|
2516
2811
|
// src/core/ApiClient.ts
|
|
2517
|
-
var SDK_VERSION = "1.
|
|
2518
|
-
var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
|
|
2812
|
+
var SDK_VERSION = "1.4.0";
|
|
2519
2813
|
var _ApiClient = class _ApiClient {
|
|
2520
|
-
constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2814
|
+
constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2521
2815
|
this.cache = new ApiCache();
|
|
2522
2816
|
this.appKey = appKey;
|
|
2523
|
-
this.baseUrl =
|
|
2817
|
+
this.baseUrl = apiUrl;
|
|
2524
2818
|
this.webUrl = deriveWebUrl(this.baseUrl);
|
|
2525
2819
|
this.debug = debug;
|
|
2526
2820
|
this.environment = environment;
|
|
2527
2821
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2528
2822
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
2529
2823
|
}
|
|
2530
|
-
// ─── Infrastructure ──────────────────────────────────────────────────────────
|
|
2531
|
-
/** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
|
|
2532
2824
|
getHttpClient() {
|
|
2533
2825
|
return this.httpClient;
|
|
2534
2826
|
}
|
|
2827
|
+
getBaseUrl() {
|
|
2828
|
+
return this.baseUrl;
|
|
2829
|
+
}
|
|
2535
2830
|
getWebUrl() {
|
|
2536
2831
|
return this.webUrl;
|
|
2537
2832
|
}
|
|
@@ -2557,7 +2852,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2557
2852
|
async clearQueue() {
|
|
2558
2853
|
await offlineQueue.clear();
|
|
2559
2854
|
}
|
|
2560
|
-
// ─── Public HTTP facade (headers injected automatically) ─────────────────────
|
|
2561
2855
|
async get(path) {
|
|
2562
2856
|
return this.httpClient.get(path, { headers: { "X-App-Key": this.appKey } });
|
|
2563
2857
|
}
|
|
@@ -2571,29 +2865,29 @@ var _ApiClient = class _ApiClient {
|
|
|
2571
2865
|
message,
|
|
2572
2866
|
context,
|
|
2573
2867
|
sdkVersion: SDK_VERSION,
|
|
2574
|
-
platform:
|
|
2868
|
+
platform: Platform5.OS
|
|
2575
2869
|
}, true);
|
|
2576
|
-
} catch {
|
|
2870
|
+
} catch (err) {
|
|
2871
|
+
if (this.debug) console.warn("[Paywallo:ApiClient] reportError failed", err);
|
|
2577
2872
|
}
|
|
2578
2873
|
}
|
|
2579
|
-
// ─── Business methods ────────────────────────────────────────────────────────
|
|
2580
2874
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2581
2875
|
await this.postWithQueue("/api/v1/events", {
|
|
2582
2876
|
eventName,
|
|
2583
2877
|
distinctId,
|
|
2584
|
-
properties: { ...properties, platform:
|
|
2878
|
+
properties: { ...properties, platform: Platform5.OS },
|
|
2585
2879
|
timestamp: timestamp ?? Date.now(),
|
|
2586
2880
|
environment: this.environment.toLowerCase()
|
|
2587
2881
|
}, `event:${eventName}`);
|
|
2588
2882
|
}
|
|
2589
|
-
async identify(distinctId, properties, email) {
|
|
2590
|
-
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform:
|
|
2883
|
+
async identify(distinctId, properties, email, deviceId) {
|
|
2884
|
+
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
|
|
2591
2885
|
}
|
|
2592
2886
|
async startSession(distinctId, sessionId, platform) {
|
|
2593
2887
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2594
2888
|
distinctId,
|
|
2595
2889
|
sessionId,
|
|
2596
|
-
platform: platform ??
|
|
2890
|
+
platform: platform ?? Platform5.OS,
|
|
2597
2891
|
environment: this.environment.toLowerCase()
|
|
2598
2892
|
}, `session.start:${sessionId}`);
|
|
2599
2893
|
return { success: true };
|
|
@@ -2671,7 +2965,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2671
2965
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2672
2966
|
if (!result) {
|
|
2673
2967
|
this.log("No campaign found for placement:", placement);
|
|
2674
|
-
this.cache.setCampaign(key, null);
|
|
2968
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2675
2969
|
return null;
|
|
2676
2970
|
}
|
|
2677
2971
|
this.log("Got campaign:", placement, result.variantKey);
|
|
@@ -2684,6 +2978,32 @@ var _ApiClient = class _ApiClient {
|
|
|
2684
2978
|
return null;
|
|
2685
2979
|
}
|
|
2686
2980
|
}
|
|
2981
|
+
async getPrimaryCampaign(distinctId) {
|
|
2982
|
+
const key = `primary:${distinctId}`;
|
|
2983
|
+
const hit = this.cache.getCampaign(key);
|
|
2984
|
+
if (hit !== void 0) return hit;
|
|
2985
|
+
try {
|
|
2986
|
+
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2987
|
+
if (res.status === 404) {
|
|
2988
|
+
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2989
|
+
return null;
|
|
2990
|
+
}
|
|
2991
|
+
if (!res.ok) {
|
|
2992
|
+
this.log("Non-OK response for primary campaign:", res.status);
|
|
2993
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
2994
|
+
if (stale !== void 0) return stale;
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
this.log("Got primary campaign:", res.data?.campaignId);
|
|
2998
|
+
this.cache.setCampaign(key, res.data);
|
|
2999
|
+
return res.data;
|
|
3000
|
+
} catch (error) {
|
|
3001
|
+
this.log("Failed to get primary campaign:", error);
|
|
3002
|
+
const stale = this.cache.getStaleCampaign(key);
|
|
3003
|
+
if (stale !== void 0) return stale;
|
|
3004
|
+
return null;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
2687
3007
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2688
3008
|
const result = await validatePurchase(
|
|
2689
3009
|
this,
|
|
@@ -2752,12 +3072,6 @@ var _ApiClient = class _ApiClient {
|
|
|
2752
3072
|
return { value: false, flagKey };
|
|
2753
3073
|
}
|
|
2754
3074
|
}
|
|
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
3075
|
async getVariantFromCache(flagKey, distinctId) {
|
|
2762
3076
|
const key = `${flagKey}:${distinctId}`;
|
|
2763
3077
|
const hit = this.cache.getVariant(key);
|
|
@@ -2786,13 +3100,10 @@ var _ApiClient = class _ApiClient {
|
|
|
2786
3100
|
try {
|
|
2787
3101
|
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
2788
3102
|
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
2789
|
-
} catch {
|
|
3103
|
+
} catch (err) {
|
|
3104
|
+
this.log("writeFlagToStorage failed", err);
|
|
2790
3105
|
}
|
|
2791
3106
|
}
|
|
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
3107
|
async postWithQueue(url, payload, label) {
|
|
2797
3108
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
2798
3109
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -2812,10 +3123,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2812
3123
|
}
|
|
2813
3124
|
}
|
|
2814
3125
|
log(...args) {
|
|
2815
|
-
if (this.debug) console.
|
|
3126
|
+
if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
|
|
2816
3127
|
}
|
|
2817
3128
|
};
|
|
2818
|
-
// ─── Private helpers ─────────────────────────────────────────────────────────
|
|
2819
3129
|
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2820
3130
|
var ApiClient = _ApiClient;
|
|
2821
3131
|
|
|
@@ -2836,42 +3146,72 @@ var CAMPAIGN_ERROR_CODES = {
|
|
|
2836
3146
|
|
|
2837
3147
|
// src/domains/campaign/CampaignGateService.ts
|
|
2838
3148
|
var PRELOAD_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
3149
|
+
var PRELOAD_WAIT_MAX_MS = 2e3;
|
|
3150
|
+
var PRELOAD_WAIT_INTERVAL_MS = 100;
|
|
2839
3151
|
var CampaignGateService = class {
|
|
2840
3152
|
constructor() {
|
|
2841
3153
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3154
|
+
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
2842
3155
|
}
|
|
2843
3156
|
async getCampaign(apiClient, placement, context) {
|
|
2844
3157
|
const distinctId = identityManager.getDistinctId();
|
|
2845
3158
|
if (!distinctId) return null;
|
|
2846
3159
|
return apiClient.getCampaign(placement, distinctId, context);
|
|
2847
3160
|
}
|
|
3161
|
+
async waitForPreload(placement) {
|
|
3162
|
+
if (!this.pendingPreloads.has(placement)) return;
|
|
3163
|
+
const deadline = Date.now() + PRELOAD_WAIT_MAX_MS;
|
|
3164
|
+
await new Promise((resolve) => {
|
|
3165
|
+
const check = () => {
|
|
3166
|
+
if (!this.pendingPreloads.has(placement) || Date.now() >= deadline) {
|
|
3167
|
+
resolve();
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
setTimeout(check, PRELOAD_WAIT_INTERVAL_MS);
|
|
3171
|
+
};
|
|
3172
|
+
check();
|
|
3173
|
+
});
|
|
3174
|
+
}
|
|
2848
3175
|
async presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2849
|
-
|
|
3176
|
+
await this.waitForPreload(placement);
|
|
3177
|
+
const preloaded = this.getPreloadedCampaign(placement);
|
|
3178
|
+
const campaign = preloaded ?? await this.getCampaign(apiClient, placement, context);
|
|
2850
3179
|
if (!campaign) {
|
|
2851
3180
|
return { presented: false, purchased: false, cancelled: false, restored: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, "No campaign found") };
|
|
2852
3181
|
}
|
|
2853
|
-
if (!context?.forceShow
|
|
2854
|
-
|
|
3182
|
+
if (!context?.forceShow) {
|
|
3183
|
+
const isActive = await hasActive();
|
|
3184
|
+
if (isActive) {
|
|
3185
|
+
return { presented: false, purchased: false, cancelled: false, restored: false, skippedReason: "subscriber" };
|
|
3186
|
+
}
|
|
2855
3187
|
}
|
|
2856
3188
|
if (campaignPresenter) return campaignPresenter(campaign);
|
|
2857
3189
|
if (!paywallPresenter) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
2858
|
-
return { ...await paywallPresenter(
|
|
3190
|
+
return { ...await paywallPresenter(placement), campaignId: campaign.campaignId, variantKey: campaign.variantKey };
|
|
2859
3191
|
}
|
|
2860
3192
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
2861
|
-
|
|
3193
|
+
const isActive = await hasActive();
|
|
3194
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3195
|
+
if (isActive) return true;
|
|
2862
3196
|
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
2863
3197
|
return r.purchased || r.restored;
|
|
2864
3198
|
}
|
|
2865
3199
|
async preloadCampaign(apiClient, placement, context) {
|
|
3200
|
+
this.pendingPreloads.add(placement);
|
|
2866
3201
|
try {
|
|
2867
3202
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
2868
3203
|
if (!campaign?.paywall) {
|
|
2869
3204
|
return { success: false, error: new CampaignError(CAMPAIGN_ERROR_CODES.NOT_FOUND, `Campaign or paywall not found: ${placement}`) };
|
|
2870
3205
|
}
|
|
2871
3206
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3207
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
3208
|
+
console.info(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3209
|
+
}
|
|
2872
3210
|
return { success: true };
|
|
2873
3211
|
} catch (error) {
|
|
2874
3212
|
return { success: false, error: error instanceof Error ? error : new PaywalloError("panel", "PANEL_UNKNOWN", String(error)) };
|
|
3213
|
+
} finally {
|
|
3214
|
+
this.pendingPreloads.delete(placement);
|
|
2875
3215
|
}
|
|
2876
3216
|
}
|
|
2877
3217
|
getPreloadedCampaign(placement) {
|
|
@@ -2885,12 +3225,13 @@ var CampaignGateService = class {
|
|
|
2885
3225
|
}
|
|
2886
3226
|
clearPreloaded() {
|
|
2887
3227
|
this.preloadedCampaigns.clear();
|
|
3228
|
+
this.pendingPreloads.clear();
|
|
2888
3229
|
}
|
|
2889
3230
|
};
|
|
2890
3231
|
var campaignGateService = new CampaignGateService();
|
|
2891
3232
|
|
|
2892
3233
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
2893
|
-
import { Platform as
|
|
3234
|
+
import { Platform as Platform6 } from "react-native";
|
|
2894
3235
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
2895
3236
|
var AdvertisingIdManager = class {
|
|
2896
3237
|
constructor() {
|
|
@@ -2901,7 +3242,7 @@ var AdvertisingIdManager = class {
|
|
|
2901
3242
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
2902
3243
|
try {
|
|
2903
3244
|
const tracking = await import("expo-tracking-transparency");
|
|
2904
|
-
if (
|
|
3245
|
+
if (Platform6.OS === "ios") {
|
|
2905
3246
|
if (!tracking.isAvailable()) {
|
|
2906
3247
|
const idfv2 = await this.collectIdfv();
|
|
2907
3248
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -2920,7 +3261,7 @@ var AdvertisingIdManager = class {
|
|
|
2920
3261
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
2921
3262
|
return this.cached;
|
|
2922
3263
|
}
|
|
2923
|
-
if (
|
|
3264
|
+
if (Platform6.OS === "android") {
|
|
2924
3265
|
const androidStatus = await tracking.getTrackingPermissionsAsync();
|
|
2925
3266
|
const optedIn = androidStatus.granted;
|
|
2926
3267
|
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
@@ -2950,43 +3291,67 @@ var AdvertisingIdManager = class {
|
|
|
2950
3291
|
}
|
|
2951
3292
|
};
|
|
2952
3293
|
|
|
2953
|
-
// src/domains/identity/
|
|
3294
|
+
// src/domains/identity/InstallTracker.ts
|
|
3295
|
+
import { Dimensions as Dimensions3, Platform as Platform8 } from "react-native";
|
|
3296
|
+
import AsyncStorage3 from "@react-native-async-storage/async-storage";
|
|
3297
|
+
import RNDeviceInfo from "react-native-device-info";
|
|
3298
|
+
|
|
3299
|
+
// src/domains/identity/MetaBridge.ts
|
|
3300
|
+
import { NativeModules as NativeModules4 } from "react-native";
|
|
2954
3301
|
var TIMEOUT_MS = 2e3;
|
|
2955
|
-
var
|
|
2956
|
-
|
|
3302
|
+
var NativeBridge = NativeModules4.PaywalloFBBridge ?? null;
|
|
3303
|
+
var MetaBridge = class {
|
|
3304
|
+
async getAnonymousID() {
|
|
3305
|
+
if (!NativeBridge) return null;
|
|
2957
3306
|
try {
|
|
2958
3307
|
let timerId;
|
|
2959
|
-
const
|
|
3308
|
+
const timeout = new Promise((resolve) => {
|
|
2960
3309
|
timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
|
|
2961
3310
|
});
|
|
2962
|
-
const result = await Promise.race([
|
|
3311
|
+
const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
|
|
2963
3312
|
clearTimeout(timerId);
|
|
2964
3313
|
return result;
|
|
2965
3314
|
} catch {
|
|
2966
3315
|
return null;
|
|
2967
3316
|
}
|
|
2968
3317
|
}
|
|
2969
|
-
async
|
|
3318
|
+
async logEvent(name, params = {}) {
|
|
3319
|
+
if (!NativeBridge) return;
|
|
2970
3320
|
try {
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
3321
|
+
await NativeBridge.logEvent(name, params);
|
|
3322
|
+
} catch {
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
async logPurchase(amount, currency, params = {}) {
|
|
3326
|
+
if (!NativeBridge) return;
|
|
3327
|
+
try {
|
|
3328
|
+
await NativeBridge.logPurchase(amount, currency, params);
|
|
3329
|
+
} catch {
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
setUserID(userId) {
|
|
3333
|
+
if (!NativeBridge) return;
|
|
3334
|
+
try {
|
|
3335
|
+
NativeBridge.setUserID(userId);
|
|
3336
|
+
} catch {
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
flush() {
|
|
3340
|
+
if (!NativeBridge) return;
|
|
3341
|
+
try {
|
|
3342
|
+
NativeBridge.flush();
|
|
2974
3343
|
} catch {
|
|
2975
|
-
return null;
|
|
2976
3344
|
}
|
|
2977
3345
|
}
|
|
2978
3346
|
};
|
|
2979
|
-
var
|
|
2980
|
-
|
|
2981
|
-
// src/domains/identity/InstallTracker.ts
|
|
2982
|
-
import { Dimensions as Dimensions3, Platform as Platform6 } from "react-native";
|
|
3347
|
+
var metaBridge = new MetaBridge();
|
|
2983
3348
|
|
|
2984
3349
|
// src/domains/identity/InstallReferrerManager.ts
|
|
2985
|
-
import { Platform as
|
|
3350
|
+
import { Platform as Platform7 } from "react-native";
|
|
2986
3351
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
2987
3352
|
var InstallReferrerManager = class {
|
|
2988
3353
|
async getReferrer() {
|
|
2989
|
-
if (
|
|
3354
|
+
if (Platform7.OS !== "android") {
|
|
2990
3355
|
return this.emptyResult();
|
|
2991
3356
|
}
|
|
2992
3357
|
let timerId;
|
|
@@ -3047,20 +3412,7 @@ var installReferrerManager = new InstallReferrerManager();
|
|
|
3047
3412
|
|
|
3048
3413
|
// src/domains/session/SessionManager.ts
|
|
3049
3414
|
import { AppState as AppState2 } from "react-native";
|
|
3050
|
-
|
|
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
|
-
};
|
|
3415
|
+
init_uuid();
|
|
3064
3416
|
|
|
3065
3417
|
// src/domains/session/sessionTracking.ts
|
|
3066
3418
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
@@ -3290,9 +3642,7 @@ var SessionManager = class {
|
|
|
3290
3642
|
}
|
|
3291
3643
|
}
|
|
3292
3644
|
log(...args) {
|
|
3293
|
-
if (this.debug)
|
|
3294
|
-
console.warn("[Paywallo Session]", ...args);
|
|
3295
|
-
}
|
|
3645
|
+
if (this.debug) console.log("[Paywallo:Session]", ...args);
|
|
3296
3646
|
}
|
|
3297
3647
|
};
|
|
3298
3648
|
var sessionManager = new SessionManager();
|
|
@@ -3305,11 +3655,10 @@ var InstallTracker = class {
|
|
|
3305
3655
|
}
|
|
3306
3656
|
async trackIfNeeded(apiClient, requestATT = false) {
|
|
3307
3657
|
const storage = new SecureStorage(this.debug);
|
|
3308
|
-
const alreadyTracked = await storage.get("install_tracked");
|
|
3658
|
+
const alreadyTracked = await storage.get("@panel:install_tracked");
|
|
3309
3659
|
if (alreadyTracked) return;
|
|
3310
3660
|
let fallbackTracked = null;
|
|
3311
3661
|
try {
|
|
3312
|
-
const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
|
|
3313
3662
|
fallbackTracked = await AsyncStorage3.getItem("@paywallo:install_tracked");
|
|
3314
3663
|
} catch {
|
|
3315
3664
|
}
|
|
@@ -3318,13 +3667,7 @@ var InstallTracker = class {
|
|
|
3318
3667
|
const sessionId = sessionManager.getSessionId();
|
|
3319
3668
|
const installedAt = Date.now();
|
|
3320
3669
|
if (!distinctId) return;
|
|
3321
|
-
|
|
3322
|
-
try {
|
|
3323
|
-
const mod = await import("react-native-device-info");
|
|
3324
|
-
deviceInfo = mod.default;
|
|
3325
|
-
} catch {
|
|
3326
|
-
deviceInfo = null;
|
|
3327
|
-
}
|
|
3670
|
+
const deviceInfo = RNDeviceInfo;
|
|
3328
3671
|
let deviceData = {};
|
|
3329
3672
|
try {
|
|
3330
3673
|
const screen = Dimensions3.get("screen");
|
|
@@ -3336,6 +3679,38 @@ var InstallTracker = class {
|
|
|
3336
3679
|
const osVersion = deviceInfo?.getSystemVersion() || "unknown";
|
|
3337
3680
|
const appVersion = deviceInfo?.getVersion() || "unknown";
|
|
3338
3681
|
const buildNumber = deviceInfo?.getBuildNumber() || "0";
|
|
3682
|
+
let carrier = "unknown";
|
|
3683
|
+
try {
|
|
3684
|
+
carrier = await deviceInfo?.getCarrier?.() ?? "unknown";
|
|
3685
|
+
} catch {
|
|
3686
|
+
}
|
|
3687
|
+
let totalDisk = 0;
|
|
3688
|
+
try {
|
|
3689
|
+
totalDisk = Number(await deviceInfo?.getTotalDiskCapacity?.()) || 0;
|
|
3690
|
+
} catch {
|
|
3691
|
+
}
|
|
3692
|
+
let freeDisk = 0;
|
|
3693
|
+
try {
|
|
3694
|
+
freeDisk = Number(await deviceInfo?.getFreeDiskStorage?.()) || 0;
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
let totalRam = 0;
|
|
3698
|
+
try {
|
|
3699
|
+
totalRam = Number(await deviceInfo?.getTotalMemory?.()) || 0;
|
|
3700
|
+
} catch {
|
|
3701
|
+
}
|
|
3702
|
+
let brand = "unknown";
|
|
3703
|
+
try {
|
|
3704
|
+
brand = deviceInfo?.getBrand?.() ?? "unknown";
|
|
3705
|
+
} catch {
|
|
3706
|
+
}
|
|
3707
|
+
let installerPackage = null;
|
|
3708
|
+
try {
|
|
3709
|
+
if (Platform8.OS === "android") {
|
|
3710
|
+
installerPackage = await deviceInfo?.getInstallerPackageName?.() ?? null;
|
|
3711
|
+
}
|
|
3712
|
+
} catch {
|
|
3713
|
+
}
|
|
3339
3714
|
deviceData = {
|
|
3340
3715
|
deviceModel,
|
|
3341
3716
|
osVersion,
|
|
@@ -3344,25 +3719,40 @@ var InstallTracker = class {
|
|
|
3344
3719
|
...screenWidth > 0 && { screenWidth },
|
|
3345
3720
|
...screenHeight > 0 && { screenHeight },
|
|
3346
3721
|
timezone,
|
|
3347
|
-
locale
|
|
3722
|
+
locale,
|
|
3723
|
+
...carrier !== "unknown" && { carrier },
|
|
3724
|
+
...totalDisk > 0 && { totalDisk },
|
|
3725
|
+
...freeDisk > 0 && { freeDisk },
|
|
3726
|
+
...totalRam > 0 && { totalRam },
|
|
3727
|
+
...brand !== "unknown" && { brand },
|
|
3728
|
+
...installerPackage && { installerPackage }
|
|
3348
3729
|
};
|
|
3349
3730
|
} catch {
|
|
3350
|
-
if (this.debug) {
|
|
3351
|
-
console.warn("[Paywallo] Failed to collect device data for install event");
|
|
3352
|
-
}
|
|
3353
3731
|
}
|
|
3354
3732
|
const [fbAnonId, referrer, adIds] = await Promise.all([
|
|
3355
|
-
|
|
3733
|
+
metaBridge.getAnonymousID(),
|
|
3356
3734
|
installReferrerManager.getReferrer(),
|
|
3357
3735
|
this.advertisingIdManager.collect(requestATT)
|
|
3358
3736
|
]);
|
|
3359
|
-
|
|
3737
|
+
let effectiveAnonId = fbAnonId;
|
|
3738
|
+
if (!effectiveAnonId) {
|
|
3739
|
+
const stored = await storage.get("@panel:anon_id");
|
|
3740
|
+
if (stored) {
|
|
3741
|
+
effectiveAnonId = stored;
|
|
3742
|
+
} else {
|
|
3743
|
+
const { generateUUID: generateUUID2 } = await Promise.resolve().then(() => (init_uuid(), uuid_exports));
|
|
3744
|
+
effectiveAnonId = `PW_${generateUUID2()}`;
|
|
3745
|
+
await storage.set("@panel:anon_id", effectiveAnonId).catch(() => {
|
|
3746
|
+
});
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
try {
|
|
3360
3750
|
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3361
3751
|
installedAt,
|
|
3362
|
-
platform:
|
|
3752
|
+
platform: Platform8.OS,
|
|
3363
3753
|
...sessionId && { sessionId },
|
|
3364
3754
|
...deviceData,
|
|
3365
|
-
...
|
|
3755
|
+
...effectiveAnonId && { fbAnonId: effectiveAnonId },
|
|
3366
3756
|
...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
|
|
3367
3757
|
...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
|
|
3368
3758
|
...referrer.fbclid && { referrerFbclid: referrer.fbclid },
|
|
@@ -3374,19 +3764,47 @@ var InstallTracker = class {
|
|
|
3374
3764
|
...adIds.gaid && { gaid: adIds.gaid },
|
|
3375
3765
|
attStatus: adIds.attStatus
|
|
3376
3766
|
});
|
|
3377
|
-
|
|
3767
|
+
if (Platform8.OS === "ios") {
|
|
3768
|
+
void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId).catch(() => {
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
await storage.set("@panel:install_tracked", String(installedAt));
|
|
3378
3772
|
try {
|
|
3379
|
-
const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
|
|
3380
3773
|
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3381
3774
|
} catch {
|
|
3382
3775
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3776
|
+
} catch {
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
|
|
3780
|
+
const storage = new SecureStorage(this.debug);
|
|
3781
|
+
const alreadyMatched = await storage.get("@panel:deferred_match_done");
|
|
3782
|
+
if (alreadyMatched) return;
|
|
3783
|
+
const controller = new AbortController();
|
|
3784
|
+
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
3785
|
+
try {
|
|
3786
|
+
const baseUrl = apiClient.getBaseUrl();
|
|
3787
|
+
const appKey = apiClient.appKey;
|
|
3788
|
+
await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
|
|
3789
|
+
method: "POST",
|
|
3790
|
+
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
3791
|
+
body: JSON.stringify({
|
|
3792
|
+
idfv: adIds.idfv,
|
|
3793
|
+
deviceModel: deviceData.deviceModel,
|
|
3794
|
+
osVersion: deviceData.osVersion,
|
|
3795
|
+
screenWidth: deviceData.screenWidth,
|
|
3796
|
+
screenHeight: deviceData.screenHeight,
|
|
3797
|
+
timezone: deviceData.timezone,
|
|
3798
|
+
locale: deviceData.locale,
|
|
3799
|
+
fbAnonId: anonId
|
|
3800
|
+
}),
|
|
3801
|
+
signal: controller.signal
|
|
3802
|
+
});
|
|
3803
|
+
await storage.set("@panel:deferred_match_done", "1").catch(() => {
|
|
3804
|
+
});
|
|
3805
|
+
} catch {
|
|
3806
|
+
} finally {
|
|
3807
|
+
clearTimeout(timer);
|
|
3390
3808
|
}
|
|
3391
3809
|
}
|
|
3392
3810
|
};
|
|
@@ -3396,25 +3814,13 @@ function isValidEventName(name) {
|
|
|
3396
3814
|
if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3397
3815
|
return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
|
|
3398
3816
|
}
|
|
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
3817
|
var _PaywalloClientClass = class _PaywalloClientClass {
|
|
3414
3818
|
constructor() {
|
|
3415
3819
|
this.config = null;
|
|
3416
3820
|
this.apiClient = null;
|
|
3417
3821
|
this.initPromise = null;
|
|
3822
|
+
this.readyPromise = null;
|
|
3823
|
+
this.resolveReady = null;
|
|
3418
3824
|
this.paywallPresenter = null;
|
|
3419
3825
|
this.campaignPresenter = null;
|
|
3420
3826
|
this.subscriptionGetter = null;
|
|
@@ -3424,29 +3830,30 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3424
3830
|
this.pendingConfig = null;
|
|
3425
3831
|
this.networkCleanup = null;
|
|
3426
3832
|
this.initAttempts = 0;
|
|
3833
|
+
this.autoPreloadedPlacement = null;
|
|
3834
|
+
this.autoPreloadPlacementPromise = null;
|
|
3835
|
+
this.sessionFlagsMap = /* @__PURE__ */ new Map();
|
|
3427
3836
|
}
|
|
3428
3837
|
async init(config) {
|
|
3429
|
-
if (
|
|
3838
|
+
if (!this.readyPromise) {
|
|
3839
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3840
|
+
this.resolveReady = resolve;
|
|
3841
|
+
});
|
|
3842
|
+
}
|
|
3430
3843
|
if (this.config && this.apiClient) {
|
|
3431
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
|
|
3432
3844
|
return;
|
|
3433
3845
|
}
|
|
3434
3846
|
if (this.initPromise) {
|
|
3435
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
|
|
3436
3847
|
await this.initPromise;
|
|
3437
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
|
|
3438
3848
|
return;
|
|
3439
3849
|
}
|
|
3440
3850
|
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
3851
|
this.pendingConfig = config;
|
|
3443
3852
|
this.initAttempts = 0;
|
|
3444
3853
|
this.initPromise = this.initWithRetry(config);
|
|
3445
3854
|
try {
|
|
3446
3855
|
await this.initPromise;
|
|
3447
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() SUCCESS");
|
|
3448
3856
|
} catch (error) {
|
|
3449
|
-
if (config.debug) console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
|
|
3450
3857
|
this.initPromise = null;
|
|
3451
3858
|
this.config = null;
|
|
3452
3859
|
this.apiClient = null;
|
|
@@ -3455,6 +3862,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3455
3862
|
throw error;
|
|
3456
3863
|
}
|
|
3457
3864
|
}
|
|
3865
|
+
isReady() {
|
|
3866
|
+
return this.config !== null && this.apiClient !== null;
|
|
3867
|
+
}
|
|
3868
|
+
waitUntilReady() {
|
|
3869
|
+
if (this.isReady()) return Promise.resolve();
|
|
3870
|
+
if (!this.readyPromise) {
|
|
3871
|
+
this.readyPromise = new Promise((resolve) => {
|
|
3872
|
+
this.resolveReady = resolve;
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
return this.readyPromise;
|
|
3876
|
+
}
|
|
3458
3877
|
async initWithRetry(config) {
|
|
3459
3878
|
while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
|
|
3460
3879
|
try {
|
|
@@ -3469,7 +3888,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3469
3888
|
this.initAttempts++;
|
|
3470
3889
|
if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
|
|
3471
3890
|
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
3891
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3474
3892
|
}
|
|
3475
3893
|
}
|
|
@@ -3480,12 +3898,12 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3480
3898
|
this.networkCleanup = networkMonitor.addListener((state) => {
|
|
3481
3899
|
if (state !== "online") return;
|
|
3482
3900
|
if (this.config && this.apiClient) return;
|
|
3483
|
-
if (
|
|
3901
|
+
if (this.initPromise) return;
|
|
3484
3902
|
this.initPromise = this.initWithRetry(config);
|
|
3485
|
-
this.initPromise.
|
|
3486
|
-
if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
|
|
3487
|
-
}).catch(() => {
|
|
3903
|
+
this.initPromise.catch(() => {
|
|
3488
3904
|
this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
|
|
3905
|
+
}).finally(() => {
|
|
3906
|
+
if (!this.isReady()) this.initPromise = null;
|
|
3489
3907
|
});
|
|
3490
3908
|
});
|
|
3491
3909
|
}
|
|
@@ -3497,12 +3915,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3497
3915
|
}
|
|
3498
3916
|
reportInitFailure(config, error, reason) {
|
|
3499
3917
|
try {
|
|
3500
|
-
const tempClient = new ApiClient(config.appKey, config.debug);
|
|
3918
|
+
const tempClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug);
|
|
3501
3919
|
void tempClient.reportError("INIT_FAILED", reason, {
|
|
3502
3920
|
attempts: this.initAttempts,
|
|
3503
3921
|
error: error instanceof Error ? error.message : String(error)
|
|
3504
3922
|
});
|
|
3505
|
-
} catch {
|
|
3923
|
+
} catch (err) {
|
|
3924
|
+
if (config.debug) console.warn("[Paywallo] reportInitFailure error", err);
|
|
3506
3925
|
}
|
|
3507
3926
|
}
|
|
3508
3927
|
reportInitSuccess(config, attempts) {
|
|
@@ -3510,41 +3929,83 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3510
3929
|
if (this.apiClient) {
|
|
3511
3930
|
void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
|
|
3512
3931
|
}
|
|
3513
|
-
} catch {
|
|
3932
|
+
} catch (err) {
|
|
3933
|
+
if (config.debug) console.warn("[Paywallo] reportInitSuccess error", err);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
3937
|
+
try {
|
|
3938
|
+
const results = await apiClient.evaluateFlags(keys, distinctId);
|
|
3939
|
+
this.sessionFlagsMap.clear();
|
|
3940
|
+
for (const key of keys) {
|
|
3941
|
+
this.sessionFlagsMap.set(key, key in results ? results[key] : null);
|
|
3942
|
+
}
|
|
3943
|
+
} catch (error) {
|
|
3944
|
+
for (const key of keys) {
|
|
3945
|
+
this.sessionFlagsMap.set(key, null);
|
|
3946
|
+
}
|
|
3514
3947
|
}
|
|
3515
3948
|
}
|
|
3516
3949
|
async doInit(config) {
|
|
3517
3950
|
const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
|
|
3518
3951
|
const offlineQueueEnabled = config.offlineQueueEnabled !== false;
|
|
3519
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
|
|
3520
3952
|
await Promise.all([
|
|
3521
3953
|
networkMonitor.initialize({ debug: config.debug }),
|
|
3522
3954
|
offlineQueue.initialize({ debug: config.debug }).then(
|
|
3523
3955
|
() => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
|
|
3524
3956
|
)
|
|
3525
3957
|
]);
|
|
3526
|
-
|
|
3527
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
|
|
3528
|
-
this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3958
|
+
this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3529
3959
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3530
3960
|
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3531
|
-
|
|
3532
|
-
|
|
3961
|
+
subscriptionManager.init({
|
|
3962
|
+
serverUrl: this.apiClient.getBaseUrl(),
|
|
3963
|
+
appKey: config.appKey,
|
|
3964
|
+
cacheTTL: config.subscriptionCacheTTL,
|
|
3965
|
+
debug: config.debug
|
|
3966
|
+
});
|
|
3533
3967
|
await Promise.all([
|
|
3534
3968
|
identityManager.initialize(this.apiClient, config.debug),
|
|
3535
3969
|
sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
|
|
3536
3970
|
]);
|
|
3537
3971
|
const distinctId = identityManager.getDistinctId();
|
|
3538
|
-
if (config.debug) console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
|
|
3539
3972
|
if (config.autoStartSession !== false) {
|
|
3540
3973
|
sessionManager.startSession().catch(() => {
|
|
3541
|
-
if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
|
|
3542
3974
|
});
|
|
3543
3975
|
}
|
|
3544
3976
|
new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
|
|
3545
3977
|
});
|
|
3978
|
+
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3979
|
+
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3980
|
+
}
|
|
3546
3981
|
this.config = config;
|
|
3547
|
-
if (
|
|
3982
|
+
if (this.resolveReady) {
|
|
3983
|
+
this.resolveReady();
|
|
3984
|
+
this.resolveReady = null;
|
|
3985
|
+
}
|
|
3986
|
+
if (config.debug) console.info("[Paywallo INIT] complete");
|
|
3987
|
+
if (this.apiClient) {
|
|
3988
|
+
const apiClientRef = this.apiClient;
|
|
3989
|
+
if (config.autoPreloadCampaign) {
|
|
3990
|
+
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
3991
|
+
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
3992
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
3993
|
+
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
3994
|
+
});
|
|
3995
|
+
} else {
|
|
3996
|
+
this.autoPreloadPlacementPromise = apiClientRef.getPrimaryCampaign(distinctId).then((primary) => {
|
|
3997
|
+
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
3998
|
+
if (placement) {
|
|
3999
|
+
this.autoPreloadedPlacement = placement;
|
|
4000
|
+
if (config.debug) console.info(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
4001
|
+
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
4002
|
+
});
|
|
4003
|
+
return placement;
|
|
4004
|
+
}
|
|
4005
|
+
return null;
|
|
4006
|
+
}).catch(() => null);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
3548
4009
|
}
|
|
3549
4010
|
async identify(options) {
|
|
3550
4011
|
this.ensureInitialized();
|
|
@@ -3554,7 +4015,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3554
4015
|
const apiClient = this.getApiClientOrThrow();
|
|
3555
4016
|
const distinctId = identityManager.getDistinctId();
|
|
3556
4017
|
if (!distinctId) {
|
|
3557
|
-
if (this.config?.debug) console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
|
|
3558
4018
|
return;
|
|
3559
4019
|
}
|
|
3560
4020
|
if (!isValidEventName(eventName)) {
|
|
@@ -3562,25 +4022,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3562
4022
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
3563
4023
|
}
|
|
3564
4024
|
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
4025
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
3585
4026
|
...identityManager.getProperties(),
|
|
3586
4027
|
...options?.properties,
|
|
@@ -3591,7 +4032,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3591
4032
|
const apiClient = this.getApiClientOrThrow();
|
|
3592
4033
|
const distinctId = identityManager.getDistinctId();
|
|
3593
4034
|
if (!distinctId) {
|
|
3594
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3595
4035
|
return;
|
|
3596
4036
|
}
|
|
3597
4037
|
if (!Number.isInteger(index) || index < 0) {
|
|
@@ -3615,7 +4055,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3615
4055
|
const apiClient = this.getApiClientOrThrow();
|
|
3616
4056
|
const distinctId = identityManager.getDistinctId();
|
|
3617
4057
|
if (!distinctId) {
|
|
3618
|
-
if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
|
|
3619
4058
|
return;
|
|
3620
4059
|
}
|
|
3621
4060
|
if (!actionName || typeof actionName !== "string") {
|
|
@@ -3644,14 +4083,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3644
4083
|
try {
|
|
3645
4084
|
const distinctId = identityManager.getDistinctId();
|
|
3646
4085
|
if (!distinctId) {
|
|
3647
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
|
|
3648
4086
|
return { variant: null };
|
|
3649
4087
|
}
|
|
3650
|
-
|
|
3651
|
-
if (this.config?.debug) console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
|
|
3652
|
-
return result;
|
|
4088
|
+
return await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
3653
4089
|
} catch (error) {
|
|
3654
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
|
|
3655
4090
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
3656
4091
|
return { variant: null };
|
|
3657
4092
|
}
|
|
@@ -3667,7 +4102,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3667
4102
|
const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
|
|
3668
4103
|
return result.value;
|
|
3669
4104
|
} catch (error) {
|
|
3670
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
|
|
3671
4105
|
if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
|
|
3672
4106
|
return false;
|
|
3673
4107
|
}
|
|
@@ -3690,15 +4124,18 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3690
4124
|
async hasActiveSubscription() {
|
|
3691
4125
|
this.ensureInitialized();
|
|
3692
4126
|
try {
|
|
3693
|
-
if (this.activeChecker)
|
|
4127
|
+
if (this.activeChecker) {
|
|
4128
|
+
return await this.activeChecker();
|
|
4129
|
+
}
|
|
3694
4130
|
const distinctId = identityManager.getDistinctId();
|
|
3695
|
-
if (!distinctId || !this.apiClient)
|
|
4131
|
+
if (!distinctId || !this.apiClient) {
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
3696
4134
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
3697
4135
|
return status.hasActiveSubscription;
|
|
3698
|
-
} catch {
|
|
3699
|
-
if (this.
|
|
3700
|
-
|
|
3701
|
-
return resolveClientOfflineFromCache();
|
|
4136
|
+
} catch (error) {
|
|
4137
|
+
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4138
|
+
return false;
|
|
3702
4139
|
}
|
|
3703
4140
|
}
|
|
3704
4141
|
async getSubscription() {
|
|
@@ -3711,7 +4148,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3711
4148
|
if (!status.subscription) return null;
|
|
3712
4149
|
return { ...status.subscription, expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null };
|
|
3713
4150
|
} catch {
|
|
3714
|
-
if (this.config?.debug) console.warn("[Paywallo]", "getSubscription failed \u2014 returning null");
|
|
3715
4151
|
return null;
|
|
3716
4152
|
}
|
|
3717
4153
|
}
|
|
@@ -3722,20 +4158,24 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3722
4158
|
}
|
|
3723
4159
|
async reset() {
|
|
3724
4160
|
await identityManager.reset();
|
|
4161
|
+
await subscriptionCache.invalidateAll();
|
|
3725
4162
|
}
|
|
3726
4163
|
async fullReset() {
|
|
3727
4164
|
const wasDebug = this.config?.debug ?? false;
|
|
3728
4165
|
await sessionManager.endSession();
|
|
3729
4166
|
await identityManager.reset();
|
|
3730
|
-
await subscriptionCache.
|
|
4167
|
+
await subscriptionCache.invalidateAll();
|
|
3731
4168
|
queueProcessor.dispose();
|
|
3732
4169
|
offlineQueue.dispose();
|
|
3733
4170
|
networkMonitor.dispose();
|
|
3734
4171
|
campaignGateService.clearPreloaded();
|
|
3735
4172
|
this.cleanupNetworkRecovery();
|
|
4173
|
+
this.sessionFlagsMap.clear();
|
|
3736
4174
|
this.apiClient = null;
|
|
3737
4175
|
this.config = null;
|
|
3738
4176
|
this.initPromise = null;
|
|
4177
|
+
this.readyPromise = null;
|
|
4178
|
+
this.resolveReady = null;
|
|
3739
4179
|
this.pendingConfig = null;
|
|
3740
4180
|
this.initAttempts = 0;
|
|
3741
4181
|
this.paywallPresenter = null;
|
|
@@ -3744,7 +4184,13 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3744
4184
|
this.activeChecker = null;
|
|
3745
4185
|
this.restoreHandler = null;
|
|
3746
4186
|
this.lastOnboardingStepTimestamp = null;
|
|
3747
|
-
|
|
4187
|
+
this.autoPreloadedPlacement = null;
|
|
4188
|
+
this.autoPreloadPlacementPromise = null;
|
|
4189
|
+
}
|
|
4190
|
+
getSessionFlag(key) {
|
|
4191
|
+
if (!this.config || !this.apiClient) return null;
|
|
4192
|
+
const value = this.sessionFlagsMap.get(key);
|
|
4193
|
+
return value !== void 0 ? value : null;
|
|
3748
4194
|
}
|
|
3749
4195
|
getConfig() {
|
|
3750
4196
|
return this.config;
|
|
@@ -3833,6 +4279,15 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3833
4279
|
getPreloadedCampaign(placement) {
|
|
3834
4280
|
return campaignGateService.getPreloadedCampaign(placement);
|
|
3835
4281
|
}
|
|
4282
|
+
isPreloaded(placement) {
|
|
4283
|
+
return campaignGateService.getPreloadedCampaign(placement) !== null;
|
|
4284
|
+
}
|
|
4285
|
+
getAutoPreloadedPlacement() {
|
|
4286
|
+
return this.autoPreloadedPlacement;
|
|
4287
|
+
}
|
|
4288
|
+
waitForAutoPreloadedPlacement() {
|
|
4289
|
+
return this.autoPreloadPlacementPromise ?? Promise.resolve(null);
|
|
4290
|
+
}
|
|
3836
4291
|
isOnline() {
|
|
3837
4292
|
return networkMonitor.isOnline();
|
|
3838
4293
|
}
|
|
@@ -3862,48 +4317,6 @@ var PaywalloClient = new PaywalloClientClass();
|
|
|
3862
4317
|
import { createContext as createContext2 } from "react";
|
|
3863
4318
|
var PaywalloContext = createContext2(null);
|
|
3864
4319
|
|
|
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
4320
|
// src/hooks/usePaywallo.ts
|
|
3908
4321
|
import { useContext as useContext2 } from "react";
|
|
3909
4322
|
function usePaywallo() {
|
|
@@ -3929,7 +4342,8 @@ function mapToIAPProduct(product) {
|
|
|
3929
4342
|
type: product.type === "subscription" ? "subs" : "inapp",
|
|
3930
4343
|
subscriptionPeriodAndroid: product.subscriptionPeriod,
|
|
3931
4344
|
introductoryPrice: product.introductoryPrice,
|
|
3932
|
-
freeTrialPeriodAndroid: product.freeTrialPeriod
|
|
4345
|
+
freeTrialPeriodAndroid: product.freeTrialPeriod,
|
|
4346
|
+
trialDays: product.trialDays ?? 0
|
|
3933
4347
|
};
|
|
3934
4348
|
}
|
|
3935
4349
|
function mapToFormattedProduct(product) {
|
|
@@ -3944,6 +4358,7 @@ function mapToFormattedProduct(product) {
|
|
|
3944
4358
|
subscriptionPeriod: product.subscriptionPeriod,
|
|
3945
4359
|
introductoryPrice: product.introductoryPrice,
|
|
3946
4360
|
freeTrialPeriod: product.freeTrialPeriod,
|
|
4361
|
+
trialDays: product.trialDays ?? 0,
|
|
3947
4362
|
pricePerMonth: product.pricePerMonth,
|
|
3948
4363
|
pricePerWeek: product.pricePerWeek,
|
|
3949
4364
|
pricePerDay: product.pricePerDay
|
|
@@ -4029,23 +4444,31 @@ function usePurchase() {
|
|
|
4029
4444
|
if (result.success && result.purchase) {
|
|
4030
4445
|
setState("idle");
|
|
4031
4446
|
return {
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4447
|
+
status: "success",
|
|
4448
|
+
purchase: {
|
|
4449
|
+
productId: result.purchase.productId,
|
|
4450
|
+
transactionId: result.purchase.transactionId,
|
|
4451
|
+
transactionDate: result.purchase.transactionDate,
|
|
4452
|
+
transactionReceipt: result.purchase.receipt
|
|
4453
|
+
}
|
|
4036
4454
|
};
|
|
4037
4455
|
}
|
|
4038
4456
|
if (result.error) {
|
|
4039
|
-
|
|
4040
|
-
setError(purchaseError);
|
|
4457
|
+
setError(result.error);
|
|
4041
4458
|
setState("error");
|
|
4042
|
-
|
|
4459
|
+
return { status: "failed", error: result.error };
|
|
4043
4460
|
}
|
|
4044
4461
|
setState("idle");
|
|
4045
|
-
|
|
4462
|
+
return { status: "cancelled" };
|
|
4046
4463
|
} catch (err) {
|
|
4464
|
+
const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
|
|
4465
|
+
if (purchaseError.userCancelled) {
|
|
4466
|
+
setState("idle");
|
|
4467
|
+
return { status: "cancelled" };
|
|
4468
|
+
}
|
|
4469
|
+
setError(purchaseError);
|
|
4047
4470
|
setState("error");
|
|
4048
|
-
|
|
4471
|
+
return { status: "failed", error: purchaseError };
|
|
4049
4472
|
}
|
|
4050
4473
|
}, []);
|
|
4051
4474
|
const restore = useCallback7(async () => {
|
|
@@ -4073,7 +4496,6 @@ function usePurchase() {
|
|
|
4073
4496
|
purchase,
|
|
4074
4497
|
restore,
|
|
4075
4498
|
error,
|
|
4076
|
-
isLoading: state === "loading",
|
|
4077
4499
|
isPurchasing: state === "purchasing",
|
|
4078
4500
|
isRestoring: state === "restoring"
|
|
4079
4501
|
};
|
|
@@ -4081,139 +4503,6 @@ function usePurchase() {
|
|
|
4081
4503
|
|
|
4082
4504
|
// src/hooks/useSubscription.ts
|
|
4083
4505
|
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
4506
|
function useSubscription() {
|
|
4218
4507
|
const [status, setStatus] = useState8(null);
|
|
4219
4508
|
const [isLoading, setIsLoading] = useState8(true);
|
|
@@ -4248,6 +4537,9 @@ function useSubscription() {
|
|
|
4248
4537
|
void subscriptionManager.getSubscriptionStatus().then((s) => {
|
|
4249
4538
|
setStatus(s);
|
|
4250
4539
|
setIsLoading(false);
|
|
4540
|
+
}).catch((err) => {
|
|
4541
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
4542
|
+
setIsLoading(false);
|
|
4251
4543
|
});
|
|
4252
4544
|
const removeListener = subscriptionManager.addListener((newStatus) => {
|
|
4253
4545
|
setStatus(newStatus);
|
|
@@ -4268,7 +4560,7 @@ function useSubscription() {
|
|
|
4268
4560
|
}
|
|
4269
4561
|
|
|
4270
4562
|
// src/PaywalloProvider.tsx
|
|
4271
|
-
import { useCallback as useCallback13, useEffect as useEffect7, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
|
|
4563
|
+
import { useCallback as useCallback13, useEffect as useEffect7, useLayoutEffect, useMemo as useMemo2, useRef as useRef8, useState as useState12 } from "react";
|
|
4272
4564
|
import { Animated as Animated3, Dimensions as Dimensions4, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
|
|
4273
4565
|
|
|
4274
4566
|
// src/hooks/useCampaignPreload.ts
|
|
@@ -4330,10 +4622,9 @@ var CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
|
4330
4622
|
function isCacheEntryValid(entry) {
|
|
4331
4623
|
return !!entry && Date.now() - entry.preloadedAt < CACHE_TTL_MS;
|
|
4332
4624
|
}
|
|
4333
|
-
function
|
|
4625
|
+
function getCacheEntry(cache, placement) {
|
|
4334
4626
|
const entry = cache.get(placement);
|
|
4335
4627
|
if (!isCacheEntryValid(entry)) return null;
|
|
4336
|
-
cache.delete(placement);
|
|
4337
4628
|
return entry ?? null;
|
|
4338
4629
|
}
|
|
4339
4630
|
|
|
@@ -4346,7 +4637,7 @@ function useCampaignPreload() {
|
|
|
4346
4637
|
[]
|
|
4347
4638
|
);
|
|
4348
4639
|
const consumePreloadedCampaign = useCallback9(
|
|
4349
|
-
(placement) =>
|
|
4640
|
+
(placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
|
|
4350
4641
|
[]
|
|
4351
4642
|
);
|
|
4352
4643
|
const preloadCampaign = useCallback9(
|
|
@@ -4394,6 +4685,7 @@ function useCampaignPreload() {
|
|
|
4394
4685
|
campaignId: campaign.campaignId,
|
|
4395
4686
|
variantKey: campaign.variantKey
|
|
4396
4687
|
});
|
|
4688
|
+
if (PaywalloClient.getConfig()?.debug) console.info(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4397
4689
|
return { success: true };
|
|
4398
4690
|
} catch (error) {
|
|
4399
4691
|
return {
|
|
@@ -4427,8 +4719,8 @@ function useProductLoader() {
|
|
|
4427
4719
|
const map = /* @__PURE__ */ new Map();
|
|
4428
4720
|
for (const p of loaded) map.set(p.productId, p);
|
|
4429
4721
|
setProducts(map);
|
|
4430
|
-
} catch (
|
|
4431
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo]
|
|
4722
|
+
} catch (err) {
|
|
4723
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] refreshProducts failed", err);
|
|
4432
4724
|
} finally {
|
|
4433
4725
|
setIsLoadingProducts(false);
|
|
4434
4726
|
}
|
|
@@ -4438,7 +4730,7 @@ function useProductLoader() {
|
|
|
4438
4730
|
|
|
4439
4731
|
// src/hooks/usePurchaseOrchestrator.ts
|
|
4440
4732
|
import { useCallback as useCallback11 } from "react";
|
|
4441
|
-
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef) {
|
|
4733
|
+
function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
|
|
4442
4734
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
4443
4735
|
const handlePreloadedPurchase = useCallback11(
|
|
4444
4736
|
async (productId) => {
|
|
@@ -4463,17 +4755,18 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4463
4755
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "purchase", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4464
4756
|
await trackPurchased({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, productId, variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4465
4757
|
}
|
|
4758
|
+
invalidateSubscriptionCache();
|
|
4466
4759
|
setShowingPreloadedWebView(false);
|
|
4467
4760
|
clearPreloadedWebView();
|
|
4468
4761
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4469
4762
|
} catch (error) {
|
|
4470
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo]
|
|
4763
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded purchase failed", error);
|
|
4471
4764
|
setShowingPreloadedWebView(false);
|
|
4472
4765
|
clearPreloadedWebView();
|
|
4473
4766
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4474
4767
|
}
|
|
4475
4768
|
},
|
|
4476
|
-
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased]
|
|
4769
|
+
[preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
|
|
4477
4770
|
);
|
|
4478
4771
|
const handlePreloadedRestore = useCallback11(async () => {
|
|
4479
4772
|
try {
|
|
@@ -4483,16 +4776,17 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4483
4776
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
4484
4777
|
await trackDismissed({ placement: preloadedWebView.placement, paywallId: preloadedWebView.paywallId, durationSeconds, action: "restore", variantKey: preloadedWebView.variantKey, campaignId: preloadedWebView.campaignId });
|
|
4485
4778
|
}
|
|
4779
|
+
invalidateSubscriptionCache();
|
|
4486
4780
|
setShowingPreloadedWebView(false);
|
|
4487
4781
|
clearPreloadedWebView();
|
|
4488
4782
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4489
4783
|
} catch (error) {
|
|
4490
|
-
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo]
|
|
4784
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] preloaded restore failed", error);
|
|
4491
4785
|
setShowingPreloadedWebView(false);
|
|
4492
4786
|
clearPreloadedWebView();
|
|
4493
4787
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
4494
4788
|
}
|
|
4495
|
-
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed]);
|
|
4789
|
+
}, [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, invalidateSubscriptionCache]);
|
|
4496
4790
|
return { handlePreloadedPurchase, handlePreloadedRestore };
|
|
4497
4791
|
}
|
|
4498
4792
|
|
|
@@ -4504,7 +4798,6 @@ function toDomainSubscriptionInfo(sub) {
|
|
|
4504
4798
|
return {
|
|
4505
4799
|
id: "",
|
|
4506
4800
|
productId: sub.productId,
|
|
4507
|
-
// The hook's SubscriptionStatus is a subset of the domain's — cast is safe
|
|
4508
4801
|
status: sub.status,
|
|
4509
4802
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4510
4803
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4522,31 +4815,32 @@ function toDomainResponse(status) {
|
|
|
4522
4815
|
entitlements: []
|
|
4523
4816
|
};
|
|
4524
4817
|
}
|
|
4525
|
-
|
|
4818
|
+
function isSubscriptionStillActive(sub) {
|
|
4819
|
+
if (!sub) return false;
|
|
4820
|
+
if (!ACTIVE_STATUSES.includes(sub.status)) return false;
|
|
4821
|
+
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4822
|
+
return true;
|
|
4823
|
+
}
|
|
4824
|
+
async function resolveOfflineFromCache(distinctId) {
|
|
4526
4825
|
try {
|
|
4527
|
-
const cached = await subscriptionCache.get();
|
|
4826
|
+
const cached = await subscriptionCache.get(distinctId);
|
|
4528
4827
|
if (!cached) return false;
|
|
4529
4828
|
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;
|
|
4829
|
+
return isSubscriptionStillActive(sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null);
|
|
4534
4830
|
} catch {
|
|
4535
4831
|
return false;
|
|
4536
4832
|
}
|
|
4537
4833
|
}
|
|
4538
4834
|
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;
|
|
4835
|
+
return isSubscriptionStillActive(entry.data);
|
|
4543
4836
|
}
|
|
4544
4837
|
function useSubscriptionSync() {
|
|
4545
4838
|
const [subscription, setSubscription] = useState11(null);
|
|
4546
4839
|
const cacheRef = useRef7(null);
|
|
4547
4840
|
const invalidateCache = useCallback12(() => {
|
|
4548
4841
|
cacheRef.current = null;
|
|
4549
|
-
|
|
4842
|
+
const distinctId = PaywalloClient.getDistinctId();
|
|
4843
|
+
if (distinctId) void subscriptionCache.invalidate(distinctId);
|
|
4550
4844
|
}, []);
|
|
4551
4845
|
const fetchAndCache = useCallback12(async () => {
|
|
4552
4846
|
const apiClient = PaywalloClient.getApiClient();
|
|
@@ -4559,7 +4853,7 @@ function useSubscriptionSync() {
|
|
|
4559
4853
|
} : null;
|
|
4560
4854
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4561
4855
|
setSubscription(sub);
|
|
4562
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4856
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4563
4857
|
return sub;
|
|
4564
4858
|
}, []);
|
|
4565
4859
|
const hasActiveSubscription = useCallback12(async () => {
|
|
@@ -4571,23 +4865,23 @@ function useSubscriptionSync() {
|
|
|
4571
4865
|
return isActiveCacheEntry(cached);
|
|
4572
4866
|
}
|
|
4573
4867
|
try {
|
|
4574
|
-
const persisted = await subscriptionCache.get();
|
|
4575
|
-
if (persisted) {
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4868
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4869
|
+
if (persisted && !persisted.isStale) {
|
|
4870
|
+
const persistedSub = persisted.data.subscription;
|
|
4871
|
+
const sub = persistedSub ? {
|
|
4872
|
+
productId: persistedSub.productId,
|
|
4873
|
+
status: persistedSub.status,
|
|
4874
|
+
expiresAt: persistedSub.expiresAt ?? null,
|
|
4875
|
+
platform: persistedSub.platform,
|
|
4876
|
+
autoRenewEnabled: persistedSub.willRenew,
|
|
4877
|
+
inGracePeriod: persistedSub.status === "grace_period"
|
|
4878
|
+
} : null;
|
|
4879
|
+
const stillActive = isSubscriptionStillActive(sub);
|
|
4880
|
+
if (stillActive) {
|
|
4585
4881
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4586
4882
|
setSubscription(sub);
|
|
4587
|
-
return
|
|
4883
|
+
return true;
|
|
4588
4884
|
}
|
|
4589
|
-
void fetchAndCache();
|
|
4590
|
-
return persisted.data.hasActiveSubscription;
|
|
4591
4885
|
}
|
|
4592
4886
|
} catch {
|
|
4593
4887
|
}
|
|
@@ -4599,12 +4893,12 @@ function useSubscriptionSync() {
|
|
|
4599
4893
|
} : null;
|
|
4600
4894
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4601
4895
|
setSubscription(sub);
|
|
4602
|
-
void subscriptionCache.set(toDomainResponse(status));
|
|
4896
|
+
void subscriptionCache.set(distinctId, toDomainResponse(status));
|
|
4603
4897
|
return status.hasActiveSubscription;
|
|
4604
4898
|
} catch {
|
|
4605
|
-
return await resolveOfflineFromCache();
|
|
4899
|
+
return await resolveOfflineFromCache(distinctId);
|
|
4606
4900
|
}
|
|
4607
|
-
}, [
|
|
4901
|
+
}, []);
|
|
4608
4902
|
const getSubscription = useCallback12(async () => {
|
|
4609
4903
|
const apiClient = PaywalloClient.getApiClient();
|
|
4610
4904
|
const distinctId = PaywalloClient.getDistinctId();
|
|
@@ -4615,7 +4909,7 @@ function useSubscriptionSync() {
|
|
|
4615
4909
|
return await fetchAndCache();
|
|
4616
4910
|
} catch {
|
|
4617
4911
|
try {
|
|
4618
|
-
const persisted = await subscriptionCache.get();
|
|
4912
|
+
const persisted = await subscriptionCache.get(distinctId);
|
|
4619
4913
|
if (!persisted?.data.subscription) return null;
|
|
4620
4914
|
const s = persisted.data.subscription;
|
|
4621
4915
|
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
@@ -4646,13 +4940,38 @@ function PaywalloProvider({ children, config }) {
|
|
|
4646
4940
|
const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
|
|
4647
4941
|
const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
|
|
4648
4942
|
const clearPreloadedWebView = useCallback13(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4649
|
-
const
|
|
4943
|
+
const autoPreloadTriggeredRef = useRef8(false);
|
|
4944
|
+
const preloadedWebViewRef = useRef8(preloadedWebView);
|
|
4945
|
+
useEffect7(() => {
|
|
4946
|
+
preloadedWebViewRef.current = preloadedWebView;
|
|
4947
|
+
}, [preloadedWebView]);
|
|
4948
|
+
const triggerRepreload = useCallback13((placement) => {
|
|
4949
|
+
void PaywalloClient.preloadCampaign(placement).catch(() => {
|
|
4950
|
+
});
|
|
4951
|
+
}, []);
|
|
4952
|
+
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
4953
|
+
const SCREEN_HEIGHT3 = useMemo2(() => Dimensions4.get("window").height, []);
|
|
4954
|
+
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4955
|
+
const handlePreloadedWebViewReady = useCallback13(() => {
|
|
4956
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
4957
|
+
console.info(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
4958
|
+
}
|
|
4959
|
+
baseHandlePreloadedWebViewReady();
|
|
4960
|
+
}, [config?.debug, preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
4961
|
+
const handlePreloadedClose = useCallback13(() => {
|
|
4962
|
+
const placement = preloadedWebView?.placement;
|
|
4963
|
+
Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT3, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
|
|
4964
|
+
baseHandlePreloadedClose();
|
|
4965
|
+
if (placement) triggerRepreload(placement);
|
|
4966
|
+
});
|
|
4967
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT3]);
|
|
4650
4968
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4651
4969
|
preloadedWebView,
|
|
4652
4970
|
resolvePreloadedPaywall,
|
|
4653
4971
|
clearPreloadedWebView,
|
|
4654
4972
|
setShowingPreloadedWebView,
|
|
4655
|
-
presentedAtRef
|
|
4973
|
+
presentedAtRef,
|
|
4974
|
+
invalidateCache
|
|
4656
4975
|
);
|
|
4657
4976
|
const [isPreloadPurchasing, setIsPreloadPurchasing] = useState12(false);
|
|
4658
4977
|
const handlePreloadedPurchase = useCallback13(async (productId) => {
|
|
@@ -4665,13 +4984,19 @@ function PaywalloProvider({ children, config }) {
|
|
|
4665
4984
|
}, [rawPreloadedPurchase]);
|
|
4666
4985
|
useEffect7(() => {
|
|
4667
4986
|
initLocalization({ detectDevice: true });
|
|
4668
|
-
PaywalloClient.init(config).then(() => {
|
|
4987
|
+
PaywalloClient.init(config).then(async () => {
|
|
4669
4988
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
4670
4989
|
setIsInitialized(true);
|
|
4990
|
+
if (!autoPreloadTriggeredRef.current) {
|
|
4991
|
+
autoPreloadTriggeredRef.current = true;
|
|
4992
|
+
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
4993
|
+
if (placement) void preloadCampaign(placement).catch(() => {
|
|
4994
|
+
});
|
|
4995
|
+
}
|
|
4671
4996
|
}).catch((err) => {
|
|
4672
4997
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
4673
4998
|
});
|
|
4674
|
-
}, [config]);
|
|
4999
|
+
}, [config, preloadCampaign]);
|
|
4675
5000
|
const getPaywallConfig = useCallback13(async (p) => {
|
|
4676
5001
|
const api = PaywalloClient.getApiClient();
|
|
4677
5002
|
return api ? api.getPaywall(p) : null;
|
|
@@ -4770,28 +5095,54 @@ function PaywalloProvider({ children, config }) {
|
|
|
4770
5095
|
setActivePaywall(null);
|
|
4771
5096
|
}
|
|
4772
5097
|
}, [activePaywall]);
|
|
5098
|
+
const bridgePaywallPresenter = useCallback13((placement) => {
|
|
5099
|
+
if (PaywalloClient.getConfig()?.debug) {
|
|
5100
|
+
console.info(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
5101
|
+
}
|
|
5102
|
+
if (preloadedWebViewRef.current?.placement === placement) {
|
|
5103
|
+
if (preloadedWebViewRef.current.loaded) {
|
|
5104
|
+
return presentPreloadedWebView();
|
|
5105
|
+
}
|
|
5106
|
+
return new Promise((resolve) => {
|
|
5107
|
+
let elapsed = 0;
|
|
5108
|
+
const poll = () => {
|
|
5109
|
+
if (preloadedWebViewRef.current?.loaded) {
|
|
5110
|
+
resolve(presentPreloadedWebView());
|
|
5111
|
+
return;
|
|
5112
|
+
}
|
|
5113
|
+
elapsed += 100;
|
|
5114
|
+
if (elapsed >= 500) {
|
|
5115
|
+
resolve(presentCampaign(placement));
|
|
5116
|
+
return;
|
|
5117
|
+
}
|
|
5118
|
+
setTimeout(poll, 100);
|
|
5119
|
+
};
|
|
5120
|
+
setTimeout(poll, 100);
|
|
5121
|
+
});
|
|
5122
|
+
}
|
|
5123
|
+
return presentCampaign(placement);
|
|
5124
|
+
}, [presentPreloadedWebView, presentCampaign]);
|
|
4773
5125
|
useEffect7(() => {
|
|
4774
5126
|
if (!isInitialized) return;
|
|
4775
5127
|
const onEmergency = async (id) => {
|
|
4776
5128
|
try {
|
|
4777
|
-
await
|
|
5129
|
+
await bridgePaywallPresenter(id);
|
|
4778
5130
|
} catch {
|
|
4779
5131
|
}
|
|
4780
5132
|
};
|
|
4781
|
-
PaywalloClient.registerPaywallPresenter(
|
|
5133
|
+
PaywalloClient.registerPaywallPresenter(bridgePaywallPresenter);
|
|
4782
5134
|
PaywalloClient.registerSubscriptionGetter(getSubscription);
|
|
4783
5135
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
4784
5136
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
4785
5137
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
4786
|
-
}, [isInitialized,
|
|
4787
|
-
|
|
4788
|
-
if (activePaywall) setPreloadedWebView(null);
|
|
4789
|
-
}, [activePaywall, setPreloadedWebView]);
|
|
5138
|
+
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5139
|
+
const isReady = isInitialized && !isLoadingProducts;
|
|
4790
5140
|
const noOp = useCallback13(() => {
|
|
4791
5141
|
}, []);
|
|
4792
5142
|
const contextValue = useMemo2(() => ({
|
|
4793
5143
|
config: PaywalloClient.getConfig(),
|
|
4794
5144
|
isInitialized,
|
|
5145
|
+
isReady,
|
|
4795
5146
|
initError,
|
|
4796
5147
|
distinctId,
|
|
4797
5148
|
subscription,
|
|
@@ -4807,6 +5158,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4807
5158
|
refreshProducts
|
|
4808
5159
|
}), [
|
|
4809
5160
|
isInitialized,
|
|
5161
|
+
isReady,
|
|
4810
5162
|
initError,
|
|
4811
5163
|
distinctId,
|
|
4812
5164
|
subscription,
|
|
@@ -4821,20 +5173,16 @@ function PaywalloProvider({ children, config }) {
|
|
|
4821
5173
|
getPaywallConfig,
|
|
4822
5174
|
refreshProducts
|
|
4823
5175
|
]);
|
|
4824
|
-
|
|
4825
|
-
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT3)).current;
|
|
4826
|
-
useEffect7(() => {
|
|
5176
|
+
useLayoutEffect(() => {
|
|
4827
5177
|
if (showingPreloadedWebView) {
|
|
4828
5178
|
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
4829
|
-
Animated3.timing(slideAnim, { toValue: 0, duration:
|
|
4830
|
-
} else {
|
|
4831
|
-
slideAnim.setValue(SCREEN_HEIGHT3);
|
|
5179
|
+
Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
|
|
4832
5180
|
}
|
|
4833
5181
|
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT3]);
|
|
4834
5182
|
const preloadStyle = [
|
|
4835
5183
|
styles3.preloadOverlay,
|
|
4836
5184
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
4837
|
-
{ transform: [{ translateY:
|
|
5185
|
+
showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
|
|
4838
5186
|
];
|
|
4839
5187
|
return /* @__PURE__ */ jsxs2(PaywalloContext.Provider, { value: contextValue, children: [
|
|
4840
5188
|
children,
|
|
@@ -4847,6 +5195,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
4847
5195
|
primaryProductId: preloadedWebView.primaryProductId,
|
|
4848
5196
|
secondaryProductId: preloadedWebView.secondaryProductId,
|
|
4849
5197
|
isPurchasing: isPreloadPurchasing,
|
|
5198
|
+
webUrl: isInitialized ? PaywalloClient.getWebUrl() : null,
|
|
4850
5199
|
onReady: handlePreloadedWebViewReady,
|
|
4851
5200
|
onClose: showingPreloadedWebView ? handlePreloadedClose : noOp,
|
|
4852
5201
|
onPurchase: showingPreloadedWebView ? handlePreloadedPurchase : noOp,
|
|
@@ -4869,8 +5218,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4869
5218
|
}
|
|
4870
5219
|
var styles3 = StyleSheet3.create({
|
|
4871
5220
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
4872
|
-
hidden: { zIndex: -1 },
|
|
4873
|
-
visible: { zIndex: 9999 }
|
|
5221
|
+
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5222
|
+
visible: { zIndex: 9999, opacity: 1 }
|
|
4874
5223
|
});
|
|
4875
5224
|
|
|
4876
5225
|
// src/utils/productVariableResolver.ts
|
|
@@ -5009,6 +5358,7 @@ export {
|
|
|
5009
5358
|
IDENTITY_ERROR_CODES,
|
|
5010
5359
|
IdentityError,
|
|
5011
5360
|
IdentityManager,
|
|
5361
|
+
MetaBridge,
|
|
5012
5362
|
PAYWALL_ERROR_CODES,
|
|
5013
5363
|
PURCHASE_ERROR_CODES,
|
|
5014
5364
|
PaywallContext,
|
|
@@ -5036,6 +5386,7 @@ export {
|
|
|
5036
5386
|
hasVariables,
|
|
5037
5387
|
identityManager,
|
|
5038
5388
|
initLocalization,
|
|
5389
|
+
metaBridge,
|
|
5039
5390
|
nativeStoreKit,
|
|
5040
5391
|
networkMonitor,
|
|
5041
5392
|
offlineQueue,
|