@virex-tech/paywallo-sdk 1.5.0 → 1.5.2
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 +2 -3
- package/README.md +58 -37
- package/android/build.gradle +4 -1
- package/android/consumer-rules.pro +24 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloDeviceModule.kt +148 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloFBBridgeModule.kt +10 -7
- package/android/src/main/java/com/paywallo/sdk/PaywalloSdkPackage.kt +3 -1
- package/android/src/main/java/com/paywallo/sdk/PaywalloStorageModule.kt +106 -0
- package/android/src/main/java/com/paywallo/sdk/PaywalloStoreKitModule.kt +13 -2
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebView.kt +39 -3
- package/android/src/main/java/com/paywallo/sdk/PaywalloWebViewEvent.kt +20 -0
- package/dist/index.d.mts +18 -14
- package/dist/index.d.ts +18 -14
- package/dist/index.js +727 -469
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +693 -435
- package/dist/index.mjs.map +1 -1
- package/ios/PaywalloDevice.m +8 -0
- package/ios/PaywalloDevice.swift +91 -0
- package/ios/PaywalloFBBridge.swift +51 -0
- package/ios/PaywalloStorage.m +35 -0
- package/ios/PaywalloStorage.swift +171 -0
- package/ios/PaywalloStoreKit.swift +16 -2
- package/ios/PaywalloWebView.m +20 -0
- package/package.json +12 -11
package/dist/index.js
CHANGED
|
@@ -144,19 +144,20 @@ var CLIENT_ERROR_CODES = {
|
|
|
144
144
|
MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
|
|
145
145
|
INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
|
|
146
146
|
PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
|
|
147
|
+
INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
|
|
147
148
|
UNKNOWN: "CLIENT_UNKNOWN"
|
|
148
149
|
};
|
|
149
150
|
|
|
150
151
|
// src/domains/paywall/PaywallModal.tsx
|
|
151
152
|
var import_react5 = require("react");
|
|
152
|
-
var
|
|
153
|
+
var import_react_native8 = require("react-native");
|
|
153
154
|
|
|
154
155
|
// src/domains/paywall/usePaywallActions.ts
|
|
155
156
|
var import_react2 = require("react");
|
|
156
|
-
var
|
|
157
|
+
var import_react_native5 = require("react-native");
|
|
157
158
|
|
|
158
159
|
// src/domains/iap/IAPService.ts
|
|
159
|
-
var
|
|
160
|
+
var import_react_native4 = require("react-native");
|
|
160
161
|
|
|
161
162
|
// src/domains/iap/PurchaseError.ts
|
|
162
163
|
var PurchaseError = class extends PaywalloError {
|
|
@@ -201,10 +202,7 @@ function createPurchaseError(code, message) {
|
|
|
201
202
|
}
|
|
202
203
|
|
|
203
204
|
// src/domains/iap/NativeStoreKit.ts
|
|
204
|
-
var
|
|
205
|
-
|
|
206
|
-
// src/domains/identity/IdentityManager.ts
|
|
207
|
-
var import_react_native_device_info = __toESM(require("react-native-device-info"));
|
|
205
|
+
var import_react_native3 = require("react-native");
|
|
208
206
|
|
|
209
207
|
// src/domains/identity/IdentityError.ts
|
|
210
208
|
var IdentityError = class extends PaywalloError {
|
|
@@ -224,13 +222,141 @@ var IDENTITY_ERROR_CODES = {
|
|
|
224
222
|
// src/domains/identity/IdentityManager.ts
|
|
225
223
|
init_uuid();
|
|
226
224
|
|
|
225
|
+
// src/utils/NativeDeviceInfo.ts
|
|
226
|
+
var import_react_native = require("react-native");
|
|
227
|
+
var FALLBACK = {
|
|
228
|
+
deviceId: "unknown",
|
|
229
|
+
model: "unknown",
|
|
230
|
+
modelId: "unknown",
|
|
231
|
+
systemVersion: "0.0.0",
|
|
232
|
+
appVersion: "0.0.0",
|
|
233
|
+
buildNumber: "0",
|
|
234
|
+
bundleId: "unknown",
|
|
235
|
+
brand: "unknown",
|
|
236
|
+
systemName: "unknown",
|
|
237
|
+
totalDisk: 0,
|
|
238
|
+
freeDisk: 0,
|
|
239
|
+
totalRam: 0,
|
|
240
|
+
carrier: "unknown",
|
|
241
|
+
installerPackage: null,
|
|
242
|
+
idfv: null
|
|
243
|
+
};
|
|
244
|
+
var _debug = false;
|
|
245
|
+
var warnedOnce = false;
|
|
246
|
+
var cachedDeviceInfo = null;
|
|
247
|
+
function setNativeDeviceInfoDebug(debug) {
|
|
248
|
+
_debug = debug;
|
|
249
|
+
}
|
|
250
|
+
function isDeviceInfoResult(value) {
|
|
251
|
+
if (typeof value !== "object" || value === null) return false;
|
|
252
|
+
const v = value;
|
|
253
|
+
return typeof v["deviceId"] === "string" && typeof v["model"] === "string" && typeof v["systemVersion"] === "string" && typeof v["appVersion"] === "string" && typeof v["bundleId"] === "string";
|
|
254
|
+
}
|
|
255
|
+
function getNativeModule() {
|
|
256
|
+
const mod = import_react_native.NativeModules.PaywalloDevice;
|
|
257
|
+
if (!mod) {
|
|
258
|
+
if (!warnedOnce) {
|
|
259
|
+
warnedOnce = true;
|
|
260
|
+
console.warn("[Paywallo] NativeModules.PaywalloDevice not found \u2014 falling back to defaults");
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
return mod;
|
|
265
|
+
}
|
|
266
|
+
async function getDeviceInfo() {
|
|
267
|
+
if (cachedDeviceInfo !== null) return cachedDeviceInfo;
|
|
268
|
+
const mod = getNativeModule();
|
|
269
|
+
if (!mod) return FALLBACK;
|
|
270
|
+
const raw = await mod.getDeviceInfo();
|
|
271
|
+
if (!isDeviceInfoResult(raw)) return FALLBACK;
|
|
272
|
+
cachedDeviceInfo = raw;
|
|
273
|
+
return raw;
|
|
274
|
+
}
|
|
275
|
+
async function getInstallReferrer() {
|
|
276
|
+
const mod = getNativeModule();
|
|
277
|
+
if (!mod) return null;
|
|
278
|
+
try {
|
|
279
|
+
return await mod.getInstallReferrer();
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function isAvailable() {
|
|
285
|
+
return import_react_native.NativeModules.PaywalloDevice != null;
|
|
286
|
+
}
|
|
287
|
+
function clearCache() {
|
|
288
|
+
cachedDeviceInfo = null;
|
|
289
|
+
}
|
|
290
|
+
var nativeDeviceInfo = {
|
|
291
|
+
getDeviceInfo,
|
|
292
|
+
getInstallReferrer,
|
|
293
|
+
isAvailable,
|
|
294
|
+
clearCache
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// src/core/storage/NativeStorage.ts
|
|
298
|
+
var import_react_native2 = require("react-native");
|
|
299
|
+
var NativeModule = import_react_native2.NativeModules.PaywalloStorage;
|
|
300
|
+
var _debug2 = false;
|
|
301
|
+
var _warnedUnavailable = false;
|
|
302
|
+
function setNativeStorageDebug(debug) {
|
|
303
|
+
_debug2 = debug;
|
|
304
|
+
}
|
|
305
|
+
function isAvailable2() {
|
|
306
|
+
return NativeModule != null;
|
|
307
|
+
}
|
|
308
|
+
function warnIfUnavailable() {
|
|
309
|
+
if (NativeModule != null) return true;
|
|
310
|
+
if (!_warnedUnavailable) {
|
|
311
|
+
_warnedUnavailable = true;
|
|
312
|
+
if (_debug2) console.warn("[Paywallo] PaywalloStorage native module not available \u2014 storage will not persist");
|
|
313
|
+
}
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
function getModule() {
|
|
317
|
+
if (!warnIfUnavailable()) return null;
|
|
318
|
+
return NativeModule;
|
|
319
|
+
}
|
|
320
|
+
async function get(key) {
|
|
321
|
+
return getModule()?.get(key) ?? null;
|
|
322
|
+
}
|
|
323
|
+
async function set(key, value) {
|
|
324
|
+
return getModule()?.set(key, value) ?? false;
|
|
325
|
+
}
|
|
326
|
+
async function remove(key) {
|
|
327
|
+
return getModule()?.remove(key) ?? false;
|
|
328
|
+
}
|
|
329
|
+
async function secureGet(key) {
|
|
330
|
+
return getModule()?.secureGet(key) ?? null;
|
|
331
|
+
}
|
|
332
|
+
async function secureSet(key, value) {
|
|
333
|
+
return getModule()?.secureSet(key, value) ?? false;
|
|
334
|
+
}
|
|
335
|
+
async function secureRemove(key) {
|
|
336
|
+
return getModule()?.secureRemove(key) ?? false;
|
|
337
|
+
}
|
|
338
|
+
async function legacySecureGet(key) {
|
|
339
|
+
const mod = getModule();
|
|
340
|
+
if (!mod?.legacySecureGet) return null;
|
|
341
|
+
return mod.legacySecureGet(key);
|
|
342
|
+
}
|
|
343
|
+
var nativeStorage = {
|
|
344
|
+
isAvailable: isAvailable2,
|
|
345
|
+
get,
|
|
346
|
+
set,
|
|
347
|
+
remove,
|
|
348
|
+
secureGet,
|
|
349
|
+
secureSet,
|
|
350
|
+
secureRemove,
|
|
351
|
+
legacySecureGet
|
|
352
|
+
};
|
|
353
|
+
|
|
227
354
|
// src/core/storage/SecureStorage.ts
|
|
228
|
-
var
|
|
229
|
-
var
|
|
230
|
-
var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
|
|
355
|
+
var KEYCHAIN_KEY_PREFIX = "com.paywallo.sdk.";
|
|
356
|
+
var REGULAR_KEY_PREFIX = "@paywallo:";
|
|
231
357
|
var SecureStorage = class {
|
|
232
358
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
233
|
-
constructor(
|
|
359
|
+
constructor(_debug3 = false) {
|
|
234
360
|
}
|
|
235
361
|
async get(key) {
|
|
236
362
|
const keychainValue = await this.getFromKeychain(key);
|
|
@@ -238,9 +364,10 @@ var SecureStorage = class {
|
|
|
238
364
|
return keychainValue;
|
|
239
365
|
}
|
|
240
366
|
try {
|
|
241
|
-
const value = await
|
|
367
|
+
const value = await nativeStorage.get(`${REGULAR_KEY_PREFIX}${key}`);
|
|
242
368
|
if (value) {
|
|
243
369
|
await this.setToKeychain(key, value);
|
|
370
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0);
|
|
244
371
|
}
|
|
245
372
|
return value;
|
|
246
373
|
} catch {
|
|
@@ -248,20 +375,13 @@ var SecureStorage = class {
|
|
|
248
375
|
}
|
|
249
376
|
}
|
|
250
377
|
async set(key, value) {
|
|
251
|
-
|
|
252
|
-
await Promise.all([
|
|
253
|
-
import_async_storage.default.setItem(`@paywallo:${key}`, value),
|
|
254
|
-
this.setToKeychain(key, value)
|
|
255
|
-
]);
|
|
256
|
-
return true;
|
|
257
|
-
} catch {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
378
|
+
return this.setToKeychain(key, value);
|
|
260
379
|
}
|
|
261
380
|
async remove(key) {
|
|
262
381
|
try {
|
|
263
382
|
await Promise.all([
|
|
264
|
-
|
|
383
|
+
// Also remove the legacy plaintext copy if it exists (migration cleanup).
|
|
384
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0),
|
|
265
385
|
this.removeFromKeychain(key)
|
|
266
386
|
]);
|
|
267
387
|
return true;
|
|
@@ -270,31 +390,25 @@ var SecureStorage = class {
|
|
|
270
390
|
}
|
|
271
391
|
}
|
|
272
392
|
hasKeychainSupport() {
|
|
273
|
-
return
|
|
393
|
+
return nativeStorage.isAvailable();
|
|
274
394
|
}
|
|
275
395
|
async getFromKeychain(key) {
|
|
276
396
|
try {
|
|
277
|
-
|
|
278
|
-
if (result && result.password) {
|
|
279
|
-
return result.password;
|
|
280
|
-
}
|
|
281
|
-
return null;
|
|
397
|
+
return await nativeStorage.secureGet(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
282
398
|
} catch {
|
|
283
399
|
return null;
|
|
284
400
|
}
|
|
285
401
|
}
|
|
286
402
|
async setToKeychain(key, value) {
|
|
287
403
|
try {
|
|
288
|
-
await
|
|
289
|
-
return true;
|
|
404
|
+
return await nativeStorage.secureSet(`${KEYCHAIN_KEY_PREFIX}${key}`, value);
|
|
290
405
|
} catch {
|
|
291
406
|
return false;
|
|
292
407
|
}
|
|
293
408
|
}
|
|
294
409
|
async removeFromKeychain(key) {
|
|
295
410
|
try {
|
|
296
|
-
await
|
|
297
|
-
return true;
|
|
411
|
+
return await nativeStorage.secureRemove(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
298
412
|
} catch {
|
|
299
413
|
return false;
|
|
300
414
|
}
|
|
@@ -302,6 +416,57 @@ var SecureStorage = class {
|
|
|
302
416
|
};
|
|
303
417
|
var secureStorage = new SecureStorage();
|
|
304
418
|
|
|
419
|
+
// src/core/storage/StorageMigration.ts
|
|
420
|
+
var MIGRATION_FLAG_KEY = "@paywallo:_migration_v152_done";
|
|
421
|
+
var LEGACY_SECURE_KEYS = [
|
|
422
|
+
"device_id",
|
|
423
|
+
"device_id_fallback",
|
|
424
|
+
"anon_id",
|
|
425
|
+
"user_email",
|
|
426
|
+
"user_properties"
|
|
427
|
+
];
|
|
428
|
+
var KEYCHAIN_KEY_PREFIX2 = "com.paywallo.sdk.";
|
|
429
|
+
async function runStorageMigrationIfNeeded() {
|
|
430
|
+
if (!nativeStorage.isAvailable()) return;
|
|
431
|
+
const alreadyDone = await nativeStorage.get(MIGRATION_FLAG_KEY);
|
|
432
|
+
if (alreadyDone === "1") return;
|
|
433
|
+
await migrateAsyncStorage();
|
|
434
|
+
await migrateKeychainLegacy();
|
|
435
|
+
await nativeStorage.set(MIGRATION_FLAG_KEY, "1");
|
|
436
|
+
}
|
|
437
|
+
async function migrateAsyncStorage() {
|
|
438
|
+
try {
|
|
439
|
+
const mod = await import("@react-native-async-storage/async-storage");
|
|
440
|
+
const AsyncStorage = mod.default;
|
|
441
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
442
|
+
const paywallo = allKeys.filter((k) => k.startsWith("@paywallo:"));
|
|
443
|
+
if (paywallo.length === 0) return;
|
|
444
|
+
const pairs = await AsyncStorage.multiGet(paywallo);
|
|
445
|
+
for (const [key, value] of pairs) {
|
|
446
|
+
if (!value) continue;
|
|
447
|
+
const existing = await nativeStorage.get(key);
|
|
448
|
+
if (!existing) {
|
|
449
|
+
await nativeStorage.set(key, value);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
async function migrateKeychainLegacy() {
|
|
456
|
+
for (const key of LEGACY_SECURE_KEYS) {
|
|
457
|
+
try {
|
|
458
|
+
const legacyValue = await nativeStorage.legacySecureGet(`${KEYCHAIN_KEY_PREFIX2}${key}`);
|
|
459
|
+
if (!legacyValue) continue;
|
|
460
|
+
const newSlotKey = `${KEYCHAIN_KEY_PREFIX2}${key}`;
|
|
461
|
+
const existing = await nativeStorage.secureGet(newSlotKey);
|
|
462
|
+
if (!existing) {
|
|
463
|
+
await nativeStorage.secureSet(newSlotKey, legacyValue);
|
|
464
|
+
}
|
|
465
|
+
} catch {
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
305
470
|
// src/domains/identity/IdentityManager.ts
|
|
306
471
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
307
472
|
var DEVICE_ID_KEY = "device_id";
|
|
@@ -318,18 +483,31 @@ var IdentityManager = class {
|
|
|
318
483
|
this.secureStorage = null;
|
|
319
484
|
this.debug = false;
|
|
320
485
|
this.initialized = false;
|
|
486
|
+
this.initPromise = null;
|
|
321
487
|
}
|
|
322
488
|
async initialize(apiClient, debug = false) {
|
|
323
489
|
if (this.initialized) {
|
|
324
490
|
return this.deviceId ?? "";
|
|
325
491
|
}
|
|
492
|
+
if (this.initPromise) {
|
|
493
|
+
return this.initPromise;
|
|
494
|
+
}
|
|
495
|
+
this.initPromise = this._doInitialize(apiClient, debug).finally(() => {
|
|
496
|
+
this.initPromise = null;
|
|
497
|
+
});
|
|
498
|
+
return this.initPromise;
|
|
499
|
+
}
|
|
500
|
+
async _doInitialize(apiClient, debug) {
|
|
326
501
|
this.apiClient = apiClient;
|
|
327
502
|
this.debug = debug;
|
|
328
503
|
this.secureStorage = new SecureStorage(debug);
|
|
504
|
+
await runStorageMigrationIfNeeded();
|
|
329
505
|
await this.loadPersistedState();
|
|
330
506
|
if (!this.deviceId) {
|
|
331
507
|
try {
|
|
332
|
-
|
|
508
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
509
|
+
const deviceId = info.deviceId;
|
|
510
|
+
this.deviceId = !deviceId || deviceId === "unknown" ? generateUUID() : deviceId;
|
|
333
511
|
} catch {
|
|
334
512
|
this.deviceId = generateUUID();
|
|
335
513
|
}
|
|
@@ -361,9 +539,7 @@ var IdentityManager = class {
|
|
|
361
539
|
}
|
|
362
540
|
if (email) {
|
|
363
541
|
if (!EMAIL_REGEX.test(email)) {
|
|
364
|
-
if (this.debug)
|
|
365
|
-
console.warn("[Paywallo] Invalid email format, skipping email field");
|
|
366
|
-
}
|
|
542
|
+
if (this.debug) console.warn("[Paywallo] Invalid email format, skipping email field");
|
|
367
543
|
email = void 0;
|
|
368
544
|
} else {
|
|
369
545
|
this.email = email;
|
|
@@ -472,8 +648,8 @@ var IdentityManager = class {
|
|
|
472
648
|
var identityManager = new IdentityManager();
|
|
473
649
|
|
|
474
650
|
// src/domains/iap/NativeStoreKit.ts
|
|
475
|
-
var PaywalloStoreKitNative =
|
|
476
|
-
function
|
|
651
|
+
var PaywalloStoreKitNative = import_react_native3.NativeModules.PaywalloStoreKit ?? null;
|
|
652
|
+
function isAvailable3() {
|
|
477
653
|
return PaywalloStoreKitNative != null;
|
|
478
654
|
}
|
|
479
655
|
function mapNativeProduct(native) {
|
|
@@ -545,11 +721,11 @@ function mapNativePurchase(native) {
|
|
|
545
721
|
transactionId: native.transactionId,
|
|
546
722
|
transactionDate: native.transactionDate,
|
|
547
723
|
receipt: native.receipt,
|
|
548
|
-
platform:
|
|
724
|
+
platform: import_react_native3.Platform.OS
|
|
549
725
|
};
|
|
550
726
|
}
|
|
551
727
|
var nativeStoreKit = {
|
|
552
|
-
isAvailable,
|
|
728
|
+
isAvailable: isAvailable3,
|
|
553
729
|
async getProducts(productIds) {
|
|
554
730
|
if (!PaywalloStoreKitNative) {
|
|
555
731
|
return [];
|
|
@@ -566,8 +742,8 @@ var nativeStoreKit = {
|
|
|
566
742
|
}
|
|
567
743
|
const distinctId = identityManager.getDistinctId();
|
|
568
744
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
569
|
-
if (distinctId && !uuidRegex.test(distinctId)
|
|
570
|
-
console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
|
|
745
|
+
if (distinctId && !uuidRegex.test(distinctId)) {
|
|
746
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
|
|
571
747
|
}
|
|
572
748
|
try {
|
|
573
749
|
const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
|
|
@@ -588,7 +764,7 @@ var nativeStoreKit = {
|
|
|
588
764
|
try {
|
|
589
765
|
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
590
766
|
} catch (err) {
|
|
591
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
767
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction native error", err);
|
|
592
768
|
}
|
|
593
769
|
},
|
|
594
770
|
async getActiveTransactions() {
|
|
@@ -597,7 +773,7 @@ var nativeStoreKit = {
|
|
|
597
773
|
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
598
774
|
return results.map(mapNativePurchase);
|
|
599
775
|
} catch (err) {
|
|
600
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
776
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] getActiveTransactions failed", err);
|
|
601
777
|
return [];
|
|
602
778
|
}
|
|
603
779
|
}
|
|
@@ -608,6 +784,7 @@ var IAPService = class {
|
|
|
608
784
|
constructor(apiClient) {
|
|
609
785
|
this.productsCache = /* @__PURE__ */ new Map();
|
|
610
786
|
this.apiClient = null;
|
|
787
|
+
this.purchaseInFlight = null;
|
|
611
788
|
this.apiClient = apiClient ?? null;
|
|
612
789
|
}
|
|
613
790
|
get debug() {
|
|
@@ -643,7 +820,7 @@ var IAPService = class {
|
|
|
643
820
|
}
|
|
644
821
|
return products;
|
|
645
822
|
} catch (err) {
|
|
646
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
823
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts failed", err);
|
|
647
824
|
return [];
|
|
648
825
|
}
|
|
649
826
|
}
|
|
@@ -668,7 +845,7 @@ var IAPService = class {
|
|
|
668
845
|
async validateWithServer(purchase, productId, options) {
|
|
669
846
|
const apiClient = this.getApiClient();
|
|
670
847
|
const product = this.productsCache.get(productId);
|
|
671
|
-
const platform =
|
|
848
|
+
const platform = import_react_native4.Platform.OS;
|
|
672
849
|
const distinctId = PaywalloClient.getDistinctId();
|
|
673
850
|
return this.validateWithRetry(
|
|
674
851
|
() => apiClient.validatePurchase(
|
|
@@ -689,7 +866,7 @@ var IAPService = class {
|
|
|
689
866
|
try {
|
|
690
867
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
691
868
|
} catch (err) {
|
|
692
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
869
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
693
870
|
}
|
|
694
871
|
}
|
|
695
872
|
async purchase(productId, options) {
|
|
@@ -699,27 +876,36 @@ var IAPService = class {
|
|
|
699
876
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
700
877
|
};
|
|
701
878
|
}
|
|
879
|
+
if (this.purchaseInFlight) {
|
|
880
|
+
return {
|
|
881
|
+
success: false,
|
|
882
|
+
error: new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, "A purchase is already in progress")
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
this.purchaseInFlight = productId;
|
|
702
886
|
const debug = PaywalloClient.getConfig()?.debug;
|
|
703
|
-
if (debug) console.
|
|
887
|
+
if (debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
704
888
|
try {
|
|
705
889
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
706
890
|
if (!purchase) {
|
|
707
|
-
if (debug) console.
|
|
891
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
|
|
708
892
|
return { success: false };
|
|
709
893
|
}
|
|
710
|
-
if (debug) console.
|
|
894
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
|
|
711
895
|
await this.finishTransaction(purchase.transactionId);
|
|
712
|
-
if (debug) console.
|
|
896
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
713
897
|
this.validateWithServer(purchase, productId, options).then(() => {
|
|
714
|
-
if (debug) console.
|
|
898
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
715
899
|
}).catch((err) => {
|
|
716
|
-
if (debug) console.
|
|
900
|
+
if (debug) console.error("[Paywallo PURCHASE] background validation failed", err);
|
|
717
901
|
});
|
|
718
902
|
return { success: true, purchase };
|
|
719
903
|
} catch (error) {
|
|
720
|
-
if (debug) console.
|
|
904
|
+
if (debug) console.error(`[Paywallo PURCHASE] ${productId} failed`, error);
|
|
721
905
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
722
906
|
return { success: false, error: e };
|
|
907
|
+
} finally {
|
|
908
|
+
this.purchaseInFlight = null;
|
|
723
909
|
}
|
|
724
910
|
}
|
|
725
911
|
async restore() {
|
|
@@ -730,7 +916,7 @@ var IAPService = class {
|
|
|
730
916
|
const transactions = await nativeStoreKit.getActiveTransactions();
|
|
731
917
|
const apiClient = this.getApiClient();
|
|
732
918
|
const distinctId = PaywalloClient.getDistinctId();
|
|
733
|
-
const platform =
|
|
919
|
+
const platform = import_react_native4.Platform.OS;
|
|
734
920
|
const results = await Promise.allSettled(
|
|
735
921
|
transactions.map((tx) => {
|
|
736
922
|
const product = this.productsCache.get(tx.productId);
|
|
@@ -754,14 +940,14 @@ var IAPService = class {
|
|
|
754
940
|
try {
|
|
755
941
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
756
942
|
} catch (err) {
|
|
757
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
943
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
758
944
|
}
|
|
759
945
|
validated.push(tx);
|
|
760
946
|
}
|
|
761
947
|
}
|
|
762
948
|
return validated;
|
|
763
949
|
} catch (err) {
|
|
764
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
950
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] restore failed", err);
|
|
765
951
|
return [];
|
|
766
952
|
}
|
|
767
953
|
}
|
|
@@ -792,7 +978,7 @@ function usePaywallTracking() {
|
|
|
792
978
|
...sessionId && { sessionId }
|
|
793
979
|
});
|
|
794
980
|
} catch (err) {
|
|
795
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
981
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackDismissed failed", err);
|
|
796
982
|
}
|
|
797
983
|
}, []);
|
|
798
984
|
const trackProductSelected = (0, import_react.useCallback)((opts) => {
|
|
@@ -807,7 +993,7 @@ function usePaywallTracking() {
|
|
|
807
993
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
808
994
|
...sessionId && { sessionId }
|
|
809
995
|
}).catch((err) => {
|
|
810
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
996
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackProductSelected failed", err);
|
|
811
997
|
});
|
|
812
998
|
}, []);
|
|
813
999
|
const trackPurchased = (0, import_react.useCallback)(async (opts) => {
|
|
@@ -824,25 +1010,24 @@ function usePaywallTracking() {
|
|
|
824
1010
|
...sessionId && { sessionId }
|
|
825
1011
|
});
|
|
826
1012
|
} catch (err) {
|
|
827
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
1013
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackPurchased failed", err);
|
|
828
1014
|
}
|
|
829
1015
|
}, []);
|
|
830
1016
|
return { trackDismissed, trackProductSelected, trackPurchased };
|
|
831
1017
|
}
|
|
832
1018
|
|
|
833
1019
|
// src/domains/paywall/usePaywallActions.ts
|
|
834
|
-
var SCREEN_HEIGHT = import_react_native3.Dimensions.get("window").height;
|
|
835
1020
|
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
836
1021
|
const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
|
|
837
1022
|
const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
|
|
838
|
-
const slideAnim = (0, import_react2.useRef)(new
|
|
1023
|
+
const slideAnim = (0, import_react2.useRef)(new import_react_native5.Animated.Value(0)).current;
|
|
839
1024
|
const presentedAtRef = (0, import_react2.useRef)(Date.now());
|
|
840
1025
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
841
1026
|
const handleClose = (0, import_react2.useCallback)(() => {
|
|
842
1027
|
if (isClosing) return;
|
|
843
1028
|
setIsClosing(true);
|
|
844
1029
|
const animTarget = openAnim ?? slideAnim;
|
|
845
|
-
|
|
1030
|
+
import_react_native5.Animated.timing(animTarget, { toValue: import_react_native5.Dimensions.get("window").height, duration: 350, useNativeDriver: true }).start(() => {
|
|
846
1031
|
if (paywallConfig) {
|
|
847
1032
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
848
1033
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -931,6 +1116,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
931
1116
|
trackingDoneRef.current = false;
|
|
932
1117
|
return;
|
|
933
1118
|
}
|
|
1119
|
+
let cancelled = false;
|
|
934
1120
|
const load = async () => {
|
|
935
1121
|
setError(null);
|
|
936
1122
|
try {
|
|
@@ -951,27 +1137,30 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
951
1137
|
if (!fetched) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_FOUND, `Paywall not found for placement: ${placement}`);
|
|
952
1138
|
config = fetched;
|
|
953
1139
|
}
|
|
1140
|
+
if (cancelled) return;
|
|
954
1141
|
setPaywallConfig(config);
|
|
955
1142
|
const productIds = [];
|
|
956
1143
|
if (config.primaryProductId) productIds.push(config.primaryProductId);
|
|
957
1144
|
if (config.secondaryProductId) productIds.push(config.secondaryProductId);
|
|
958
1145
|
const snapProducts = preloadedProductsRef.current;
|
|
959
1146
|
if (snapProducts && snapProducts.size > 0) {
|
|
960
|
-
setProducts(snapProducts);
|
|
1147
|
+
if (!cancelled) setProducts(snapProducts);
|
|
961
1148
|
} else if (productIds.length > 0) {
|
|
962
1149
|
try {
|
|
963
1150
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1151
|
+
if (!cancelled) {
|
|
1152
|
+
const map = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const p of storeProducts) map.set(p.productId, p);
|
|
1154
|
+
setProducts(map);
|
|
1155
|
+
}
|
|
967
1156
|
} catch (err) {
|
|
968
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
969
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1157
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts in paywall failed", err);
|
|
1158
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
970
1159
|
}
|
|
971
1160
|
} else {
|
|
972
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1161
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
973
1162
|
}
|
|
974
|
-
if (!trackingDoneRef.current) {
|
|
1163
|
+
if (!trackingDoneRef.current && !cancelled) {
|
|
975
1164
|
trackingDoneRef.current = true;
|
|
976
1165
|
const sessionId = PaywalloClient.getSessionId();
|
|
977
1166
|
await apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
|
|
@@ -983,17 +1172,21 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
983
1172
|
});
|
|
984
1173
|
}
|
|
985
1174
|
} catch (err) {
|
|
1175
|
+
if (cancelled) return;
|
|
986
1176
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
987
1177
|
setError(e);
|
|
988
1178
|
}
|
|
989
1179
|
};
|
|
990
1180
|
void load();
|
|
1181
|
+
return () => {
|
|
1182
|
+
cancelled = true;
|
|
1183
|
+
};
|
|
991
1184
|
}, [placement, visible, variantKey, campaignId]);
|
|
992
1185
|
return { paywallConfig, products, error };
|
|
993
1186
|
}
|
|
994
1187
|
|
|
995
1188
|
// src/utils/localization.ts
|
|
996
|
-
var
|
|
1189
|
+
var import_react_native6 = require("react-native");
|
|
997
1190
|
var currentLanguage = "pt-BR";
|
|
998
1191
|
var defaultLanguage = "pt-BR";
|
|
999
1192
|
function setCurrentLanguage(language) {
|
|
@@ -1015,11 +1208,11 @@ function getLocalizedString(text) {
|
|
|
1015
1208
|
}
|
|
1016
1209
|
function detectDeviceLanguage() {
|
|
1017
1210
|
try {
|
|
1018
|
-
if (
|
|
1019
|
-
const settings =
|
|
1211
|
+
if (import_react_native6.Platform.OS === "ios") {
|
|
1212
|
+
const settings = import_react_native6.NativeModules.SettingsManager?.settings;
|
|
1020
1213
|
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
1021
1214
|
}
|
|
1022
|
-
return
|
|
1215
|
+
return import_react_native6.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
|
|
1023
1216
|
} catch {
|
|
1024
1217
|
return "en-US";
|
|
1025
1218
|
}
|
|
@@ -1055,9 +1248,8 @@ var SDK_STRINGS = {
|
|
|
1055
1248
|
};
|
|
1056
1249
|
function getSdkString(key) {
|
|
1057
1250
|
const map = SDK_STRINGS[key];
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
const base = lang.split("-")[0];
|
|
1251
|
+
if (currentLanguage in map) return map[currentLanguage];
|
|
1252
|
+
const base = currentLanguage.split("-")[0];
|
|
1061
1253
|
if (base && base in map) return map[base];
|
|
1062
1254
|
if (defaultLanguage in map) return map[defaultLanguage];
|
|
1063
1255
|
return Object.values(map)[0];
|
|
@@ -1074,9 +1266,17 @@ function initLocalization(options) {
|
|
|
1074
1266
|
|
|
1075
1267
|
// src/domains/paywall/PaywallWebView.tsx
|
|
1076
1268
|
var import_react4 = require("react");
|
|
1077
|
-
var
|
|
1269
|
+
var import_react_native7 = require("react-native");
|
|
1078
1270
|
|
|
1079
1271
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
1272
|
+
var ALLOWED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
1273
|
+
"purchase",
|
|
1274
|
+
"close",
|
|
1275
|
+
"restore",
|
|
1276
|
+
"select-product",
|
|
1277
|
+
"open-url",
|
|
1278
|
+
"ready"
|
|
1279
|
+
]);
|
|
1080
1280
|
function deriveMessageId(message) {
|
|
1081
1281
|
if (message.id) return message.id;
|
|
1082
1282
|
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
@@ -1085,10 +1285,20 @@ function deriveMessageId(message) {
|
|
|
1085
1285
|
function parseWebViewMessage(raw) {
|
|
1086
1286
|
try {
|
|
1087
1287
|
const parsed = JSON.parse(raw);
|
|
1088
|
-
if (typeof parsed?.type !== "string") {
|
|
1288
|
+
if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
|
|
1089
1289
|
return null;
|
|
1090
1290
|
}
|
|
1091
|
-
|
|
1291
|
+
const payload = parsed.payload;
|
|
1292
|
+
const safePayload = payload !== null && typeof payload === "object" ? {
|
|
1293
|
+
productId: typeof payload.productId === "string" ? String(payload.productId) : void 0,
|
|
1294
|
+
url: typeof payload.url === "string" ? String(payload.url) : void 0,
|
|
1295
|
+
timestamp: typeof payload.timestamp === "number" ? Number(payload.timestamp) : void 0
|
|
1296
|
+
} : void 0;
|
|
1297
|
+
return {
|
|
1298
|
+
type: parsed.type,
|
|
1299
|
+
id: typeof parsed.id === "string" ? parsed.id : void 0,
|
|
1300
|
+
payload: safePayload
|
|
1301
|
+
};
|
|
1092
1302
|
} catch {
|
|
1093
1303
|
return null;
|
|
1094
1304
|
}
|
|
@@ -1116,14 +1326,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
1116
1326
|
var NativeWebViewComponent = null;
|
|
1117
1327
|
function getNativeWebView() {
|
|
1118
1328
|
if (!NativeWebViewComponent) {
|
|
1119
|
-
NativeWebViewComponent = (0,
|
|
1329
|
+
NativeWebViewComponent = (0, import_react_native7.requireNativeComponent)("PaywalloWebView");
|
|
1120
1330
|
}
|
|
1121
1331
|
return NativeWebViewComponent;
|
|
1122
1332
|
}
|
|
1123
1333
|
var webViewEmitter = null;
|
|
1124
1334
|
function getWebViewEmitter() {
|
|
1125
1335
|
if (!webViewEmitter) {
|
|
1126
|
-
webViewEmitter = new
|
|
1336
|
+
webViewEmitter = new import_react_native7.NativeEventEmitter(import_react_native7.NativeModules.PaywalloWebViewModule);
|
|
1127
1337
|
}
|
|
1128
1338
|
return webViewEmitter;
|
|
1129
1339
|
}
|
|
@@ -1198,12 +1408,24 @@ function PaywallWebView({
|
|
|
1198
1408
|
setScriptToInject(script);
|
|
1199
1409
|
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1200
1410
|
const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
|
|
1411
|
+
const dedupeTimersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
|
|
1412
|
+
(0, import_react4.useEffect)(() => {
|
|
1413
|
+
return () => {
|
|
1414
|
+
for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
|
|
1415
|
+
dedupeTimersRef.current.clear();
|
|
1416
|
+
processedIdsRef.current.clear();
|
|
1417
|
+
};
|
|
1418
|
+
}, []);
|
|
1201
1419
|
const handleParsedMessage = (0, import_react4.useCallback)(
|
|
1202
1420
|
(message) => {
|
|
1203
1421
|
const msgId = deriveMessageId(message);
|
|
1204
1422
|
if (processedIdsRef.current.has(msgId)) return;
|
|
1205
1423
|
processedIdsRef.current.add(msgId);
|
|
1206
|
-
setTimeout(() =>
|
|
1424
|
+
const timerId = setTimeout(() => {
|
|
1425
|
+
processedIdsRef.current.delete(msgId);
|
|
1426
|
+
dedupeTimersRef.current.delete(msgId);
|
|
1427
|
+
}, 5e3);
|
|
1428
|
+
dedupeTimersRef.current.set(msgId, timerId);
|
|
1207
1429
|
switch (message.type) {
|
|
1208
1430
|
case "ready":
|
|
1209
1431
|
if (!paywallDataSent.current) {
|
|
@@ -1224,8 +1446,8 @@ function PaywallWebView({
|
|
|
1224
1446
|
case "open-url":
|
|
1225
1447
|
if (message.payload?.url) {
|
|
1226
1448
|
const url = message.payload.url;
|
|
1227
|
-
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
|
|
1228
|
-
void
|
|
1449
|
+
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url) || /^itms-apps:/i.test(url) || /^itms-services:/i.test(url) || /^market:/i.test(url)) {
|
|
1450
|
+
void import_react_native7.Linking.openURL(url);
|
|
1229
1451
|
}
|
|
1230
1452
|
}
|
|
1231
1453
|
break;
|
|
@@ -1303,7 +1525,7 @@ function PaywallWebView({
|
|
|
1303
1525
|
}, [isPurchasing]);
|
|
1304
1526
|
if (!resolvedWebUrl) return null;
|
|
1305
1527
|
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1306
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1528
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native7.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1307
1529
|
NativeWebView,
|
|
1308
1530
|
{
|
|
1309
1531
|
sourceUrl: paywallUrl,
|
|
@@ -1315,24 +1537,23 @@ function PaywallWebView({
|
|
|
1315
1537
|
}
|
|
1316
1538
|
) });
|
|
1317
1539
|
}
|
|
1318
|
-
var styles =
|
|
1540
|
+
var styles = import_react_native7.StyleSheet.create({
|
|
1319
1541
|
container: { flex: 1 },
|
|
1320
1542
|
webview: { flex: 1, backgroundColor: "transparent" }
|
|
1321
1543
|
});
|
|
1322
1544
|
|
|
1323
1545
|
// src/domains/paywall/PaywallModal.tsx
|
|
1324
1546
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1325
|
-
var SCREEN_HEIGHT2 = import_react_native6.Dimensions.get("window").height;
|
|
1326
1547
|
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1327
1548
|
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1328
1549
|
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1329
1550
|
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1330
1551
|
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1331
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1332
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1333
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1334
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1335
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native8.View, { style: styles2.fallbackCard, children: [
|
|
1553
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.fallbackTitle, children: title }),
|
|
1554
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.fallbackDescription, children: description }),
|
|
1555
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.retryButtonText, children: retryLabel }) }),
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.closeButtonText, children: closeLabel }) })
|
|
1336
1557
|
] }) });
|
|
1337
1558
|
}
|
|
1338
1559
|
function PaywallModal({
|
|
@@ -1352,7 +1573,8 @@ function PaywallModal({
|
|
|
1352
1573
|
variantKey,
|
|
1353
1574
|
campaignId
|
|
1354
1575
|
);
|
|
1355
|
-
const
|
|
1576
|
+
const screenHeight = import_react_native8.Dimensions.get("window").height;
|
|
1577
|
+
const openAnim = (0, import_react5.useRef)(new import_react_native8.Animated.Value(screenHeight)).current;
|
|
1356
1578
|
const [modalVisible, setModalVisible] = (0, import_react5.useState)(false);
|
|
1357
1579
|
const handleResult = (0, import_react5.useCallback)((result) => {
|
|
1358
1580
|
setModalVisible(false);
|
|
@@ -1381,18 +1603,18 @@ function PaywallModal({
|
|
|
1381
1603
|
(0, import_react5.useEffect)(() => {
|
|
1382
1604
|
if (visible) {
|
|
1383
1605
|
setModalVisible(true);
|
|
1384
|
-
openAnim.setValue(
|
|
1385
|
-
|
|
1606
|
+
openAnim.setValue(import_react_native8.Dimensions.get("window").height);
|
|
1607
|
+
import_react_native8.Animated.timing(openAnim, {
|
|
1386
1608
|
toValue: 0,
|
|
1387
1609
|
duration: 550,
|
|
1388
|
-
easing:
|
|
1610
|
+
easing: import_react_native8.Easing.out(import_react_native8.Easing.cubic),
|
|
1389
1611
|
useNativeDriver: true
|
|
1390
1612
|
}).start();
|
|
1391
1613
|
}
|
|
1392
1614
|
}, [visible, openAnim]);
|
|
1393
1615
|
if (!modalVisible && !visible) return null;
|
|
1394
1616
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1395
|
-
|
|
1617
|
+
import_react_native8.Modal,
|
|
1396
1618
|
{
|
|
1397
1619
|
visible: modalVisible,
|
|
1398
1620
|
animationType: "none",
|
|
@@ -1400,8 +1622,8 @@ function PaywallModal({
|
|
|
1400
1622
|
onRequestClose: handleClose,
|
|
1401
1623
|
transparent: true,
|
|
1402
1624
|
statusBarTranslucent: true,
|
|
1403
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1404
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1625
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native8.View, { style: styles2.container, children: [
|
|
1626
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native8.Text, { style: styles2.errorText, children: error.message }) }),
|
|
1405
1627
|
!error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1406
1628
|
ErrorFallback,
|
|
1407
1629
|
{
|
|
@@ -1429,7 +1651,7 @@ function PaywallModal({
|
|
|
1429
1651
|
}
|
|
1430
1652
|
);
|
|
1431
1653
|
}
|
|
1432
|
-
var styles2 =
|
|
1654
|
+
var styles2 = import_react_native8.StyleSheet.create({
|
|
1433
1655
|
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1434
1656
|
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1435
1657
|
container: { flex: 1, backgroundColor: "#fff" },
|
|
@@ -1531,6 +1753,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1531
1753
|
resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
|
|
1532
1754
|
resolverRef.current = null;
|
|
1533
1755
|
}
|
|
1756
|
+
setShowingPreloadedWebView(false);
|
|
1534
1757
|
};
|
|
1535
1758
|
}, []);
|
|
1536
1759
|
const presentPreloadedWebView = (0, import_react8.useCallback)(() => {
|
|
@@ -1576,8 +1799,9 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1576
1799
|
}
|
|
1577
1800
|
|
|
1578
1801
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1579
|
-
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1802
|
+
var LEGACY_CACHE_KEY = "@panel:subscription_cache";
|
|
1580
1803
|
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1804
|
+
var CACHE_INDEX_KEY = "subscription_cache:__index__";
|
|
1581
1805
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1582
1806
|
function keyFor(distinctId) {
|
|
1583
1807
|
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
@@ -1657,9 +1881,30 @@ var SubscriptionCache = class {
|
|
|
1657
1881
|
this.memoryCache.set(distinctId, cached);
|
|
1658
1882
|
try {
|
|
1659
1883
|
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1884
|
+
await this.addToIndex(distinctId);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async addToIndex(distinctId) {
|
|
1889
|
+
try {
|
|
1890
|
+
const raw = await this.storage.get(CACHE_INDEX_KEY);
|
|
1891
|
+
const ids = raw ? JSON.parse(raw) : [];
|
|
1892
|
+
if (!ids.includes(distinctId)) {
|
|
1893
|
+
ids.push(distinctId);
|
|
1894
|
+
await this.storage.set(CACHE_INDEX_KEY, JSON.stringify(ids));
|
|
1895
|
+
}
|
|
1660
1896
|
} catch {
|
|
1661
1897
|
}
|
|
1662
1898
|
}
|
|
1899
|
+
async readIndex() {
|
|
1900
|
+
try {
|
|
1901
|
+
const raw = await this.storage.get(CACHE_INDEX_KEY);
|
|
1902
|
+
if (!raw) return [];
|
|
1903
|
+
return JSON.parse(raw);
|
|
1904
|
+
} catch {
|
|
1905
|
+
return [];
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1663
1908
|
async invalidate(distinctId) {
|
|
1664
1909
|
if (!distinctId) return;
|
|
1665
1910
|
this.memoryCache.delete(distinctId);
|
|
@@ -1669,11 +1914,15 @@ var SubscriptionCache = class {
|
|
|
1669
1914
|
}
|
|
1670
1915
|
}
|
|
1671
1916
|
async invalidateAll() {
|
|
1917
|
+
const memKeys = Array.from(this.memoryCache.keys());
|
|
1918
|
+
const indexKeys = await this.readIndex();
|
|
1919
|
+
const allKeys = Array.from(/* @__PURE__ */ new Set([...memKeys, ...indexKeys]));
|
|
1672
1920
|
this.memoryCache.clear();
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1921
|
+
await Promise.allSettled([
|
|
1922
|
+
this.storage.remove(LEGACY_CACHE_KEY),
|
|
1923
|
+
this.storage.remove(CACHE_INDEX_KEY),
|
|
1924
|
+
...allKeys.map((k) => this.storage.remove(keyFor(k)))
|
|
1925
|
+
]);
|
|
1677
1926
|
}
|
|
1678
1927
|
isExpired(cached) {
|
|
1679
1928
|
return Date.now() - cached.cachedAt > this.ttl;
|
|
@@ -1829,7 +2078,7 @@ var SubscriptionManagerClass = class {
|
|
|
1829
2078
|
var subscriptionManager = new SubscriptionManagerClass();
|
|
1830
2079
|
|
|
1831
2080
|
// src/core/ApiClient.ts
|
|
1832
|
-
var
|
|
2081
|
+
var import_react_native11 = require("react-native");
|
|
1833
2082
|
|
|
1834
2083
|
// src/core/apiUrlUtils.ts
|
|
1835
2084
|
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
@@ -1877,7 +2126,7 @@ async function validatePurchase(client, appKey, platform, receipt, productId, tr
|
|
|
1877
2126
|
false
|
|
1878
2127
|
);
|
|
1879
2128
|
if (!response.ok) {
|
|
1880
|
-
throw new
|
|
2129
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, `Server validation failed: HTTP ${response.status}`);
|
|
1881
2130
|
}
|
|
1882
2131
|
return response.data;
|
|
1883
2132
|
}
|
|
@@ -1886,7 +2135,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
|
1886
2135
|
url.searchParams.set("distinctId", distinctId);
|
|
1887
2136
|
const response = await client.get(url.toString());
|
|
1888
2137
|
if (!response.ok) {
|
|
1889
|
-
throw new
|
|
2138
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.NETWORK_ERROR, `Subscription status check failed: HTTP ${response.status}`);
|
|
1890
2139
|
}
|
|
1891
2140
|
return response.data;
|
|
1892
2141
|
}
|
|
@@ -1928,7 +2177,7 @@ var HttpClient = class {
|
|
|
1928
2177
|
constructor(baseUrl, config) {
|
|
1929
2178
|
this.config = {
|
|
1930
2179
|
baseUrl,
|
|
1931
|
-
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
|
|
2180
|
+
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout ?? 1e4,
|
|
1932
2181
|
retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
|
|
1933
2182
|
debug: config?.debug ?? false
|
|
1934
2183
|
};
|
|
@@ -1958,7 +2207,7 @@ var HttpClient = class {
|
|
|
1958
2207
|
if (this.shouldRetry(response.status, retryConfig)) {
|
|
1959
2208
|
if (state.attempt < retryConfig.maxRetries) {
|
|
1960
2209
|
state.attempt++;
|
|
1961
|
-
const delay = this.
|
|
2210
|
+
const delay = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
|
|
1962
2211
|
await this.sleep(delay);
|
|
1963
2212
|
continue;
|
|
1964
2213
|
}
|
|
@@ -2012,7 +2261,7 @@ var HttpClient = class {
|
|
|
2012
2261
|
};
|
|
2013
2262
|
} catch (error) {
|
|
2014
2263
|
this.log("Request failed", {
|
|
2015
|
-
url,
|
|
2264
|
+
url: this.redactUrl(url),
|
|
2016
2265
|
error: error instanceof Error ? error.message : String(error)
|
|
2017
2266
|
});
|
|
2018
2267
|
throw error;
|
|
@@ -2025,6 +2274,14 @@ var HttpClient = class {
|
|
|
2025
2274
|
}
|
|
2026
2275
|
buildUrl(path) {
|
|
2027
2276
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
2277
|
+
if (path.startsWith("http://")) {
|
|
2278
|
+
const isLocalDev = /^http:\/\/(localhost|127\.0\.0\.1|192\.168\.\d{1,3}\.\d{1,3})/.test(path);
|
|
2279
|
+
if (!isLocalDev) {
|
|
2280
|
+
const sanitizedPath = path.split("?")[0];
|
|
2281
|
+
this.log("Blocked http:// request \u2014 only HTTPS is allowed in production:", sanitizedPath);
|
|
2282
|
+
throw new ClientError(CLIENT_ERROR_CODES.INSECURE_REQUEST, `Insecure HTTP request blocked: ${sanitizedPath}`);
|
|
2283
|
+
}
|
|
2284
|
+
}
|
|
2028
2285
|
return path;
|
|
2029
2286
|
}
|
|
2030
2287
|
return `${this.config.baseUrl}${path}`;
|
|
@@ -2064,9 +2321,28 @@ var HttpClient = class {
|
|
|
2064
2321
|
const jitter = Math.random() * 0.3 * exponentialDelay;
|
|
2065
2322
|
return Math.min(exponentialDelay + jitter, config.maxDelay);
|
|
2066
2323
|
}
|
|
2324
|
+
calculateDelayForResponse(attempt, headers, config) {
|
|
2325
|
+
const retryAfter = headers["retry-after"];
|
|
2326
|
+
if (retryAfter) {
|
|
2327
|
+
const seconds = Number(retryAfter);
|
|
2328
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
2329
|
+
return Math.min(seconds * 1e3, config.maxDelay);
|
|
2330
|
+
}
|
|
2331
|
+
const date = new Date(retryAfter);
|
|
2332
|
+
if (!Number.isNaN(date.getTime())) {
|
|
2333
|
+
const ms = date.getTime() - Date.now();
|
|
2334
|
+
if (ms > 0) return Math.min(ms, config.maxDelay);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
return this.calculateDelay(attempt, config);
|
|
2338
|
+
}
|
|
2067
2339
|
sleep(ms) {
|
|
2068
2340
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2069
2341
|
}
|
|
2342
|
+
/** Strips path segments that look like app keys (alphanumeric, 20–64 chars) from URLs before logging. */
|
|
2343
|
+
redactUrl(url) {
|
|
2344
|
+
return url.replace(/\/([a-zA-Z0-9_-]{20,64})(\?|$|\/)/g, "/[REDACTED]$2");
|
|
2345
|
+
}
|
|
2070
2346
|
log(...args) {
|
|
2071
2347
|
if (this.config.debug) console.log("[Paywallo:Http]", ...args);
|
|
2072
2348
|
}
|
|
@@ -2079,7 +2355,7 @@ var HttpClient = class {
|
|
|
2079
2355
|
};
|
|
2080
2356
|
|
|
2081
2357
|
// src/core/network/NetworkMonitor.ts
|
|
2082
|
-
var
|
|
2358
|
+
var import_react_native9 = require("react-native");
|
|
2083
2359
|
|
|
2084
2360
|
// src/core/network/types.ts
|
|
2085
2361
|
var DEFAULT_NETWORK_CONFIG = {
|
|
@@ -2116,9 +2392,19 @@ var NetworkMonitorClass = class {
|
|
|
2116
2392
|
}, this.config.checkInterval);
|
|
2117
2393
|
}
|
|
2118
2394
|
setupAppStateListener() {
|
|
2119
|
-
this.appStateSubscription =
|
|
2395
|
+
this.appStateSubscription = import_react_native9.AppState.addEventListener("change", (state) => {
|
|
2120
2396
|
if (state === "active") {
|
|
2397
|
+
if (!this.checkIntervalId) {
|
|
2398
|
+
this.checkIntervalId = setInterval(() => {
|
|
2399
|
+
void this.checkConnectivity();
|
|
2400
|
+
}, this.config.checkInterval);
|
|
2401
|
+
}
|
|
2121
2402
|
void this.checkConnectivity();
|
|
2403
|
+
} else if (state === "background" || state === "inactive") {
|
|
2404
|
+
if (this.checkIntervalId) {
|
|
2405
|
+
clearInterval(this.checkIntervalId);
|
|
2406
|
+
this.checkIntervalId = null;
|
|
2407
|
+
}
|
|
2122
2408
|
}
|
|
2123
2409
|
});
|
|
2124
2410
|
}
|
|
@@ -2200,7 +2486,6 @@ var NetworkMonitorClass = class {
|
|
|
2200
2486
|
var networkMonitor = new NetworkMonitorClass();
|
|
2201
2487
|
|
|
2202
2488
|
// src/core/queue/OfflineQueue.ts
|
|
2203
|
-
var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
2204
2489
|
init_uuid();
|
|
2205
2490
|
|
|
2206
2491
|
// src/core/queue/deduplication.ts
|
|
@@ -2262,7 +2547,7 @@ var OfflineQueueClass = class {
|
|
|
2262
2547
|
}
|
|
2263
2548
|
async loadFromStorage() {
|
|
2264
2549
|
try {
|
|
2265
|
-
const stored = await
|
|
2550
|
+
const stored = await nativeStorage.get(this.config.storageKey);
|
|
2266
2551
|
if (stored) {
|
|
2267
2552
|
this.queue = JSON.parse(stored);
|
|
2268
2553
|
}
|
|
@@ -2272,7 +2557,7 @@ var OfflineQueueClass = class {
|
|
|
2272
2557
|
}
|
|
2273
2558
|
async saveToStorage() {
|
|
2274
2559
|
try {
|
|
2275
|
-
await
|
|
2560
|
+
await nativeStorage.set(this.config.storageKey, JSON.stringify(this.queue));
|
|
2276
2561
|
return true;
|
|
2277
2562
|
} catch (error) {
|
|
2278
2563
|
this.log("Failed to save queue to storage", error);
|
|
@@ -2287,7 +2572,7 @@ var OfflineQueueClass = class {
|
|
|
2287
2572
|
cleanupExpired() {
|
|
2288
2573
|
const now = Date.now();
|
|
2289
2574
|
const before = this.queue.length;
|
|
2290
|
-
this.queue = this.queue.filter((item) =>
|
|
2575
|
+
this.queue = this.queue.filter((item) => now - item.timestamp < this.config.maxAge);
|
|
2291
2576
|
if (before !== this.queue.length) {
|
|
2292
2577
|
void this.saveToStorage();
|
|
2293
2578
|
}
|
|
@@ -2310,10 +2595,16 @@ var OfflineQueueClass = class {
|
|
|
2310
2595
|
return this.queue[existingIndex];
|
|
2311
2596
|
}
|
|
2312
2597
|
if (this.queue.length >= this.config.maxItems) {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2598
|
+
let evictIndex = 0;
|
|
2599
|
+
for (let i = 1; i < this.queue.length; i++) {
|
|
2600
|
+
const candidate = this.queue[i];
|
|
2601
|
+
const current = this.queue[evictIndex];
|
|
2602
|
+
if (candidate.attempts > current.attempts || candidate.attempts === current.attempts && candidate.timestamp < current.timestamp) {
|
|
2603
|
+
evictIndex = i;
|
|
2604
|
+
}
|
|
2316
2605
|
}
|
|
2606
|
+
const [removed] = this.queue.splice(evictIndex, 1);
|
|
2607
|
+
this.emit({ type: "item:evicted", item: removed, queueSize: this.queue.length });
|
|
2317
2608
|
}
|
|
2318
2609
|
this.queue.push(item);
|
|
2319
2610
|
await this.saveToStorage();
|
|
@@ -2593,7 +2884,6 @@ var QueueProcessorClass = class {
|
|
|
2593
2884
|
id: item.id,
|
|
2594
2885
|
url: item.url,
|
|
2595
2886
|
method: item.method,
|
|
2596
|
-
headers: item.headers,
|
|
2597
2887
|
attempts: item.attempts
|
|
2598
2888
|
});
|
|
2599
2889
|
const response = await this.httpClient.request(item.url, {
|
|
@@ -2611,7 +2901,7 @@ var QueueProcessorClass = class {
|
|
|
2611
2901
|
id: item.id,
|
|
2612
2902
|
url: item.url,
|
|
2613
2903
|
status: response.status,
|
|
2614
|
-
|
|
2904
|
+
hasAppKey: item.headers?.["X-App-Key"] != null
|
|
2615
2905
|
});
|
|
2616
2906
|
return { success: false, permanent: true, status: response.status };
|
|
2617
2907
|
}
|
|
@@ -2733,15 +3023,96 @@ var ApiCache = class {
|
|
|
2733
3023
|
}
|
|
2734
3024
|
};
|
|
2735
3025
|
|
|
3026
|
+
// src/core/EventBatcher.ts
|
|
3027
|
+
var import_react_native10 = require("react-native");
|
|
3028
|
+
var BATCH_MAX_SIZE = 10;
|
|
3029
|
+
var BATCH_FLUSH_MS = 5e3;
|
|
3030
|
+
var EventBatcher = class {
|
|
3031
|
+
constructor(post, debug = false) {
|
|
3032
|
+
this.batch = [];
|
|
3033
|
+
this.timer = null;
|
|
3034
|
+
this.post = post;
|
|
3035
|
+
this.debug = debug;
|
|
3036
|
+
}
|
|
3037
|
+
async track(eventName, distinctId, environment, properties, timestamp) {
|
|
3038
|
+
const EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
|
|
3039
|
+
if (!EVENT_NAME_REGEX.test(eventName)) {
|
|
3040
|
+
if (this.debug) console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
|
|
3041
|
+
}
|
|
3042
|
+
const event = {
|
|
3043
|
+
eventName,
|
|
3044
|
+
distinctId,
|
|
3045
|
+
properties: { ...properties, platform: import_react_native10.Platform.OS },
|
|
3046
|
+
timestamp: timestamp ?? Date.now(),
|
|
3047
|
+
environment: environment.toLowerCase()
|
|
3048
|
+
};
|
|
3049
|
+
this.batch.push(event);
|
|
3050
|
+
if (this.batch.length >= BATCH_MAX_SIZE) {
|
|
3051
|
+
if (this.timer !== null) {
|
|
3052
|
+
clearTimeout(this.timer);
|
|
3053
|
+
this.timer = null;
|
|
3054
|
+
}
|
|
3055
|
+
await this.flush();
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
3059
|
+
this.timer = setTimeout(() => {
|
|
3060
|
+
this.timer = null;
|
|
3061
|
+
void this.flush();
|
|
3062
|
+
}, BATCH_FLUSH_MS);
|
|
3063
|
+
}
|
|
3064
|
+
async flush() {
|
|
3065
|
+
if (this.batch.length === 0) return;
|
|
3066
|
+
const batch = this.batch.splice(0, this.batch.length);
|
|
3067
|
+
if (this.timer !== null) {
|
|
3068
|
+
clearTimeout(this.timer);
|
|
3069
|
+
this.timer = null;
|
|
3070
|
+
}
|
|
3071
|
+
try {
|
|
3072
|
+
await this.post("/api/v1/events/batch", { events: batch }, `event_batch:${batch.length}`);
|
|
3073
|
+
} catch {
|
|
3074
|
+
for (const event of batch) {
|
|
3075
|
+
await this.post("/api/v1/events", event, `event:${event.eventName}`);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
dispose() {
|
|
3080
|
+
if (this.timer !== null) {
|
|
3081
|
+
clearTimeout(this.timer);
|
|
3082
|
+
this.timer = null;
|
|
3083
|
+
}
|
|
3084
|
+
this.batch = [];
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
|
|
3088
|
+
// src/core/FlagStorage.ts
|
|
3089
|
+
var FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3090
|
+
async function readFlagFromStorage(flagKey, distinctId) {
|
|
3091
|
+
try {
|
|
3092
|
+
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3093
|
+
if (!raw) return null;
|
|
3094
|
+
const parsed = JSON.parse(raw);
|
|
3095
|
+
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3096
|
+
const { variant, payload, cachedAt } = parsed;
|
|
3097
|
+
if (Date.now() - cachedAt > FLAG_MAX_AGE_MS) return null;
|
|
3098
|
+
return { variant, payload };
|
|
3099
|
+
} catch {
|
|
3100
|
+
return null;
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
async function writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3104
|
+
try {
|
|
3105
|
+
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3106
|
+
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3107
|
+
} catch {
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
|
|
2736
3111
|
// src/core/ApiClient.ts
|
|
2737
|
-
var SDK_VERSION = "1.5.
|
|
2738
|
-
var
|
|
3112
|
+
var SDK_VERSION = "1.5.2";
|
|
3113
|
+
var ApiClient = class {
|
|
2739
3114
|
constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2740
3115
|
this.cache = new ApiCache();
|
|
2741
|
-
this.eventBatch = [];
|
|
2742
|
-
this.batchTimer = null;
|
|
2743
|
-
this.BATCH_MAX_SIZE = 10;
|
|
2744
|
-
this.BATCH_FLUSH_MS = 5e3;
|
|
2745
3116
|
this.appKey = appKey;
|
|
2746
3117
|
this.baseUrl = apiUrl;
|
|
2747
3118
|
this.webUrl = deriveWebUrl(this.baseUrl);
|
|
@@ -2749,6 +3120,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2749
3120
|
this.environment = environment;
|
|
2750
3121
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2751
3122
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
3123
|
+
this.batcher = new EventBatcher((url, payload, label) => this.postWithQueue(url, payload, label), debug);
|
|
2752
3124
|
}
|
|
2753
3125
|
getHttpClient() {
|
|
2754
3126
|
return this.httpClient;
|
|
@@ -2759,6 +3131,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2759
3131
|
getWebUrl() {
|
|
2760
3132
|
return this.webUrl;
|
|
2761
3133
|
}
|
|
3134
|
+
getAppKey() {
|
|
3135
|
+
return this.appKey;
|
|
3136
|
+
}
|
|
2762
3137
|
getEnvironment() {
|
|
2763
3138
|
return this.environment;
|
|
2764
3139
|
}
|
|
@@ -2794,79 +3169,33 @@ var _ApiClient = class _ApiClient {
|
|
|
2794
3169
|
message,
|
|
2795
3170
|
context,
|
|
2796
3171
|
sdkVersion: SDK_VERSION,
|
|
2797
|
-
platform:
|
|
3172
|
+
platform: import_react_native11.Platform.OS
|
|
2798
3173
|
}, true);
|
|
2799
3174
|
} catch (err) {
|
|
2800
|
-
if (this.debug) console.
|
|
3175
|
+
if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
|
|
2801
3176
|
}
|
|
2802
3177
|
}
|
|
2803
3178
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2804
|
-
|
|
2805
|
-
if (!EVENT_NAME_REGEX.test(eventName)) {
|
|
2806
|
-
if (this.debug) {
|
|
2807
|
-
console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
const event = {
|
|
2811
|
-
eventName,
|
|
2812
|
-
distinctId,
|
|
2813
|
-
properties: { ...properties, platform: import_react_native8.Platform.OS },
|
|
2814
|
-
timestamp: timestamp ?? Date.now(),
|
|
2815
|
-
environment: this.environment.toLowerCase()
|
|
2816
|
-
};
|
|
2817
|
-
this.eventBatch.push(event);
|
|
2818
|
-
if (this.eventBatch.length >= this.BATCH_MAX_SIZE) {
|
|
2819
|
-
if (this.batchTimer !== null) {
|
|
2820
|
-
clearTimeout(this.batchTimer);
|
|
2821
|
-
this.batchTimer = null;
|
|
2822
|
-
}
|
|
2823
|
-
await this.flushEventBatch();
|
|
2824
|
-
return;
|
|
2825
|
-
}
|
|
2826
|
-
if (this.batchTimer !== null) {
|
|
2827
|
-
clearTimeout(this.batchTimer);
|
|
2828
|
-
}
|
|
2829
|
-
this.batchTimer = setTimeout(() => {
|
|
2830
|
-
this.batchTimer = null;
|
|
2831
|
-
void this.flushEventBatch();
|
|
2832
|
-
}, this.BATCH_FLUSH_MS);
|
|
3179
|
+
await this.batcher.track(eventName, distinctId, this.environment, properties, timestamp);
|
|
2833
3180
|
}
|
|
2834
3181
|
async flushEventBatch() {
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
this.batchTimer = null;
|
|
2840
|
-
}
|
|
2841
|
-
try {
|
|
2842
|
-
await this.postWithQueue(
|
|
2843
|
-
"/api/v1/events/batch",
|
|
2844
|
-
{ events: batch },
|
|
2845
|
-
`event_batch:${batch.length}`
|
|
2846
|
-
);
|
|
2847
|
-
} catch (error) {
|
|
2848
|
-
this.log("Batch send failed, falling back to individual sends:", error);
|
|
2849
|
-
for (const event of batch) {
|
|
2850
|
-
await this.postWithQueue(
|
|
2851
|
-
"/api/v1/events",
|
|
2852
|
-
event,
|
|
2853
|
-
`event:${event.eventName}`
|
|
2854
|
-
);
|
|
2855
|
-
}
|
|
2856
|
-
}
|
|
3182
|
+
await this.batcher.flush();
|
|
3183
|
+
}
|
|
3184
|
+
dispose() {
|
|
3185
|
+
this.batcher.dispose();
|
|
2857
3186
|
}
|
|
2858
3187
|
async identify(distinctId, properties, email, deviceId) {
|
|
2859
|
-
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform:
|
|
3188
|
+
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: import_react_native11.Platform.OS, ...deviceId && { deviceId } }, "identify");
|
|
2860
3189
|
}
|
|
2861
3190
|
async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
|
|
2862
3191
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2863
3192
|
distinctId,
|
|
2864
3193
|
sessionId,
|
|
2865
|
-
platform: platform ??
|
|
3194
|
+
platform: platform ?? import_react_native11.Platform.OS,
|
|
2866
3195
|
environment: this.environment.toLowerCase(),
|
|
2867
3196
|
appVersion: appVersion ?? "unknown",
|
|
2868
3197
|
deviceModel: deviceModel ?? "unknown",
|
|
2869
|
-
osVersion: osVersion ?? String(
|
|
3198
|
+
osVersion: osVersion ?? String(import_react_native11.Platform.Version)
|
|
2870
3199
|
}, `session.start:${sessionId}`);
|
|
2871
3200
|
return { success: true };
|
|
2872
3201
|
}
|
|
@@ -2878,7 +3207,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2878
3207
|
const key = `${flagKey}:${distinctId}`;
|
|
2879
3208
|
const hit = this.cache.getVariant(key);
|
|
2880
3209
|
if (hit !== void 0) return hit;
|
|
2881
|
-
const persisted = await
|
|
3210
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
2882
3211
|
if (persisted !== null) {
|
|
2883
3212
|
this.cache.setVariant(key, persisted);
|
|
2884
3213
|
return persisted;
|
|
@@ -2886,25 +3215,20 @@ var _ApiClient = class _ApiClient {
|
|
|
2886
3215
|
try {
|
|
2887
3216
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2888
3217
|
if (res.status === 404) {
|
|
2889
|
-
this.log("Flag not found (404):", flagKey);
|
|
2890
3218
|
const v = { variant: null };
|
|
2891
3219
|
this.cache.setVariant(key, v, this.cache.nullTTL);
|
|
2892
3220
|
return v;
|
|
2893
3221
|
}
|
|
2894
3222
|
if (!res.ok) {
|
|
2895
|
-
this.log("Non-OK response for variant:", flagKey, res.status);
|
|
2896
3223
|
const stale = this.cache.getStaleVariant(key);
|
|
2897
|
-
|
|
2898
|
-
return { variant: null };
|
|
3224
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
2899
3225
|
}
|
|
2900
3226
|
this.cache.setVariant(key, res.data);
|
|
2901
|
-
await
|
|
3227
|
+
await writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2902
3228
|
return res.data;
|
|
2903
|
-
} catch
|
|
2904
|
-
this.log("Failed to get variant:", flagKey, error);
|
|
3229
|
+
} catch {
|
|
2905
3230
|
const stale = this.cache.getStaleVariant(key);
|
|
2906
|
-
|
|
2907
|
-
return { variant: null };
|
|
3231
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
2908
3232
|
}
|
|
2909
3233
|
}
|
|
2910
3234
|
async getPaywall(placement) {
|
|
@@ -2913,29 +3237,22 @@ var _ApiClient = class _ApiClient {
|
|
|
2913
3237
|
try {
|
|
2914
3238
|
const res = await this.get(`/api/v1/paywalls/${placement}`);
|
|
2915
3239
|
if (res.status === 404) {
|
|
2916
|
-
this.log("Paywall not found (404):", placement);
|
|
2917
3240
|
this.cache.setPaywall(placement, null, this.cache.nullTTL);
|
|
2918
3241
|
return null;
|
|
2919
3242
|
}
|
|
2920
3243
|
if (!res.ok) {
|
|
2921
|
-
this.log("Non-OK response for paywall:", placement, res.status);
|
|
2922
3244
|
const stale = this.cache.getStalePaywall(placement);
|
|
2923
|
-
|
|
2924
|
-
return null;
|
|
3245
|
+
return stale !== void 0 ? stale : null;
|
|
2925
3246
|
}
|
|
2926
3247
|
this.cache.setPaywall(placement, res.data);
|
|
2927
3248
|
return res.data;
|
|
2928
|
-
} catch
|
|
2929
|
-
this.log("Failed to get paywall:", placement, error);
|
|
3249
|
+
} catch {
|
|
2930
3250
|
const stale = this.cache.getStalePaywall(placement);
|
|
2931
|
-
|
|
2932
|
-
return null;
|
|
3251
|
+
return stale !== void 0 ? stale : null;
|
|
2933
3252
|
}
|
|
2934
3253
|
}
|
|
2935
3254
|
async getEmergencyPaywall() {
|
|
2936
|
-
|
|
2937
|
-
this.log("Got emergency paywall:", result.enabled);
|
|
2938
|
-
return result;
|
|
3255
|
+
return getEmergencyPaywall(this);
|
|
2939
3256
|
}
|
|
2940
3257
|
async getCampaign(placement, distinctId, context) {
|
|
2941
3258
|
const key = `${placement}:${distinctId}`;
|
|
@@ -2944,18 +3261,14 @@ var _ApiClient = class _ApiClient {
|
|
|
2944
3261
|
try {
|
|
2945
3262
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2946
3263
|
if (!result) {
|
|
2947
|
-
this.log("No campaign found for placement:", placement);
|
|
2948
3264
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2949
3265
|
return null;
|
|
2950
3266
|
}
|
|
2951
|
-
this.log("Got campaign:", placement, result.variantKey);
|
|
2952
3267
|
this.cache.setCampaign(key, result);
|
|
2953
3268
|
return result;
|
|
2954
|
-
} catch
|
|
2955
|
-
this.log("Failed to get campaign:", placement, error);
|
|
3269
|
+
} catch {
|
|
2956
3270
|
const stale = this.cache.getStaleCampaign(key);
|
|
2957
|
-
|
|
2958
|
-
return null;
|
|
3271
|
+
return stale !== void 0 ? stale : null;
|
|
2959
3272
|
}
|
|
2960
3273
|
}
|
|
2961
3274
|
async getPrimaryCampaign(distinctId) {
|
|
@@ -2965,63 +3278,34 @@ var _ApiClient = class _ApiClient {
|
|
|
2965
3278
|
try {
|
|
2966
3279
|
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2967
3280
|
if (res.status === 404) {
|
|
2968
|
-
this.log("Primary campaign not found (404)");
|
|
2969
3281
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2970
3282
|
return null;
|
|
2971
3283
|
}
|
|
2972
3284
|
if (!res.ok) {
|
|
2973
|
-
this.log("Non-OK response for primary campaign:", res.status);
|
|
2974
3285
|
const stale = this.cache.getStaleCampaign(key);
|
|
2975
|
-
|
|
2976
|
-
return null;
|
|
3286
|
+
return stale !== void 0 ? stale : null;
|
|
2977
3287
|
}
|
|
2978
|
-
this.log("Got primary campaign:", res.data?.campaignId);
|
|
2979
3288
|
this.cache.setCampaign(key, res.data);
|
|
2980
3289
|
return res.data;
|
|
2981
|
-
} catch
|
|
2982
|
-
this.log("Failed to get primary campaign:", error);
|
|
3290
|
+
} catch {
|
|
2983
3291
|
const stale = this.cache.getStaleCampaign(key);
|
|
2984
|
-
|
|
2985
|
-
return null;
|
|
3292
|
+
return stale !== void 0 ? stale : null;
|
|
2986
3293
|
}
|
|
2987
3294
|
}
|
|
2988
3295
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
2989
|
-
|
|
2990
|
-
this,
|
|
2991
|
-
this.appKey,
|
|
2992
|
-
platform,
|
|
2993
|
-
receipt,
|
|
2994
|
-
productId,
|
|
2995
|
-
transactionId,
|
|
2996
|
-
priceLocal,
|
|
2997
|
-
currency,
|
|
2998
|
-
country,
|
|
2999
|
-
distinctId,
|
|
3000
|
-
paywallPlacement,
|
|
3001
|
-
variantKey
|
|
3002
|
-
);
|
|
3003
|
-
this.log("Purchase validated:", result.success);
|
|
3004
|
-
return result;
|
|
3296
|
+
return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
|
|
3005
3297
|
}
|
|
3006
3298
|
async getSubscriptionStatus(distinctId) {
|
|
3007
|
-
|
|
3008
|
-
this.log("Subscription status:", result.hasActiveSubscription);
|
|
3009
|
-
return result;
|
|
3299
|
+
return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
|
|
3010
3300
|
}
|
|
3011
3301
|
async evaluateFlags(keys, distinctId) {
|
|
3012
3302
|
const query = keys.map(encodeURIComponent).join(",");
|
|
3013
|
-
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
3014
3303
|
try {
|
|
3015
|
-
const res = await this.httpClient.get(
|
|
3304
|
+
const res = await this.httpClient.get(`/api/v1/flags/evaluate?keys=${query}`, {
|
|
3016
3305
|
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
3017
3306
|
});
|
|
3018
|
-
if (!res.ok) {
|
|
3019
|
-
|
|
3020
|
-
return {};
|
|
3021
|
-
}
|
|
3022
|
-
const raw = res.data;
|
|
3023
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
3024
|
-
const record = raw;
|
|
3307
|
+
if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
|
|
3308
|
+
const record = res.data;
|
|
3025
3309
|
const result = {};
|
|
3026
3310
|
for (const key of Object.keys(record)) {
|
|
3027
3311
|
const value = record[key];
|
|
@@ -3029,11 +3313,10 @@ var _ApiClient = class _ApiClient {
|
|
|
3029
3313
|
result[key] = variant;
|
|
3030
3314
|
const flagVariant = { variant };
|
|
3031
3315
|
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
3032
|
-
await
|
|
3316
|
+
await writeFlagToStorage(key, distinctId, flagVariant);
|
|
3033
3317
|
}
|
|
3034
3318
|
return result;
|
|
3035
|
-
} catch
|
|
3036
|
-
this.log("Failed to evaluateFlags:", error);
|
|
3319
|
+
} catch {
|
|
3037
3320
|
return {};
|
|
3038
3321
|
}
|
|
3039
3322
|
}
|
|
@@ -3041,15 +3324,9 @@ var _ApiClient = class _ApiClient {
|
|
|
3041
3324
|
try {
|
|
3042
3325
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
3043
3326
|
const response = await this.get(url);
|
|
3044
|
-
if (response.status === 404) return { value: false, flagKey };
|
|
3045
|
-
if (!response.ok) {
|
|
3046
|
-
this.log("Non-OK response for conditional flag:", flagKey, response.status);
|
|
3047
|
-
return { value: false, flagKey };
|
|
3048
|
-
}
|
|
3049
|
-
this.log("Got conditional flag:", flagKey, response.data.value);
|
|
3327
|
+
if (response.status === 404 || !response.ok) return { value: false, flagKey };
|
|
3050
3328
|
return { value: response.data.value, flagKey };
|
|
3051
3329
|
} catch {
|
|
3052
|
-
this.log("Failed to get conditional flag:", flagKey);
|
|
3053
3330
|
return { value: false, flagKey };
|
|
3054
3331
|
}
|
|
3055
3332
|
}
|
|
@@ -3057,34 +3334,13 @@ var _ApiClient = class _ApiClient {
|
|
|
3057
3334
|
const key = `${flagKey}:${distinctId}`;
|
|
3058
3335
|
const hit = this.cache.getVariant(key);
|
|
3059
3336
|
if (hit !== void 0) return hit;
|
|
3060
|
-
const persisted = await
|
|
3337
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
3061
3338
|
if (persisted !== null) {
|
|
3062
3339
|
this.cache.setVariant(key, persisted);
|
|
3063
3340
|
return persisted;
|
|
3064
3341
|
}
|
|
3065
3342
|
return null;
|
|
3066
3343
|
}
|
|
3067
|
-
async readFlagFromStorage(flagKey, distinctId) {
|
|
3068
|
-
try {
|
|
3069
|
-
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3070
|
-
if (!raw) return null;
|
|
3071
|
-
const parsed = JSON.parse(raw);
|
|
3072
|
-
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3073
|
-
const { variant, payload, cachedAt } = parsed;
|
|
3074
|
-
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
3075
|
-
return { variant, payload };
|
|
3076
|
-
} catch {
|
|
3077
|
-
return null;
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3081
|
-
try {
|
|
3082
|
-
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3083
|
-
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3084
|
-
} catch (err) {
|
|
3085
|
-
this.log("writeFlagToStorage failed", err);
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
3344
|
async postWithQueue(url, payload, label) {
|
|
3089
3345
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
3090
3346
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -3097,7 +3353,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3097
3353
|
this.log("Request failed:", label, error);
|
|
3098
3354
|
if (this.offlineQueueEnabled) {
|
|
3099
3355
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
3100
|
-
this.log("Queued after failure:", label);
|
|
3101
3356
|
return;
|
|
3102
3357
|
}
|
|
3103
3358
|
throw error;
|
|
@@ -3107,8 +3362,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3107
3362
|
if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
|
|
3108
3363
|
}
|
|
3109
3364
|
};
|
|
3110
|
-
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3111
|
-
var ApiClient = _ApiClient;
|
|
3112
3365
|
|
|
3113
3366
|
// src/domains/campaign/CampaignError.ts
|
|
3114
3367
|
var CampaignError = class extends PaywalloError {
|
|
@@ -3133,6 +3386,7 @@ var CampaignGateService = class {
|
|
|
3133
3386
|
constructor() {
|
|
3134
3387
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3135
3388
|
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
3389
|
+
this.activePreloadPromises = /* @__PURE__ */ new Map();
|
|
3136
3390
|
}
|
|
3137
3391
|
async getCampaign(apiClient, placement, context) {
|
|
3138
3392
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3172,12 +3426,23 @@ var CampaignGateService = class {
|
|
|
3172
3426
|
}
|
|
3173
3427
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
3174
3428
|
const isActive = await hasActive();
|
|
3175
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
3429
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3176
3430
|
if (isActive) return true;
|
|
3177
3431
|
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
|
|
3178
3432
|
return r.purchased || r.restored;
|
|
3179
3433
|
}
|
|
3180
3434
|
async preloadCampaign(apiClient, placement, context) {
|
|
3435
|
+
const existing = this.activePreloadPromises.get(placement);
|
|
3436
|
+
if (existing) return existing;
|
|
3437
|
+
const promise = this._doPreload(apiClient, placement, context);
|
|
3438
|
+
this.activePreloadPromises.set(placement, promise);
|
|
3439
|
+
try {
|
|
3440
|
+
return await promise;
|
|
3441
|
+
} finally {
|
|
3442
|
+
this.activePreloadPromises.delete(placement);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
async _doPreload(apiClient, placement, context) {
|
|
3181
3446
|
this.pendingPreloads.add(placement);
|
|
3182
3447
|
try {
|
|
3183
3448
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
@@ -3186,7 +3451,7 @@ var CampaignGateService = class {
|
|
|
3186
3451
|
}
|
|
3187
3452
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3188
3453
|
if (PaywalloClient.getConfig()?.debug) {
|
|
3189
|
-
console.
|
|
3454
|
+
console.log(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3190
3455
|
}
|
|
3191
3456
|
return { success: true };
|
|
3192
3457
|
} catch (error) {
|
|
@@ -3212,7 +3477,7 @@ var CampaignGateService = class {
|
|
|
3212
3477
|
var campaignGateService = new CampaignGateService();
|
|
3213
3478
|
|
|
3214
3479
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
3215
|
-
var
|
|
3480
|
+
var import_react_native12 = require("react-native");
|
|
3216
3481
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
3217
3482
|
var AdvertisingIdManager = class {
|
|
3218
3483
|
constructor() {
|
|
@@ -3223,7 +3488,7 @@ var AdvertisingIdManager = class {
|
|
|
3223
3488
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
3224
3489
|
try {
|
|
3225
3490
|
const tracking = await import("expo-tracking-transparency");
|
|
3226
|
-
if (
|
|
3491
|
+
if (import_react_native12.Platform.OS === "ios") {
|
|
3227
3492
|
if (!tracking.isAvailable()) {
|
|
3228
3493
|
const idfv2 = await this.collectIdfv();
|
|
3229
3494
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -3242,10 +3507,8 @@ var AdvertisingIdManager = class {
|
|
|
3242
3507
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
3243
3508
|
return this.cached;
|
|
3244
3509
|
}
|
|
3245
|
-
if (
|
|
3246
|
-
const
|
|
3247
|
-
const optedIn = androidStatus.granted;
|
|
3248
|
-
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
3510
|
+
if (import_react_native12.Platform.OS === "android") {
|
|
3511
|
+
const rawGaid = tracking.getAdvertisingId();
|
|
3249
3512
|
const gaid = this.sanitizeId(rawGaid);
|
|
3250
3513
|
this.cached = { idfv: null, idfa: null, gaid, attStatus: "unavailable" };
|
|
3251
3514
|
return this.cached;
|
|
@@ -3259,9 +3522,8 @@ var AdvertisingIdManager = class {
|
|
|
3259
3522
|
}
|
|
3260
3523
|
async collectIdfv() {
|
|
3261
3524
|
try {
|
|
3262
|
-
const
|
|
3263
|
-
|
|
3264
|
-
return this.sanitizeId(idfv);
|
|
3525
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
3526
|
+
return this.sanitizeId(info.idfv);
|
|
3265
3527
|
} catch {
|
|
3266
3528
|
return null;
|
|
3267
3529
|
}
|
|
@@ -3273,14 +3535,12 @@ var AdvertisingIdManager = class {
|
|
|
3273
3535
|
};
|
|
3274
3536
|
|
|
3275
3537
|
// src/domains/identity/InstallTracker.ts
|
|
3276
|
-
var
|
|
3277
|
-
var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
3278
|
-
var import_react_native_device_info2 = __toESM(require("react-native-device-info"));
|
|
3538
|
+
var import_react_native16 = require("react-native");
|
|
3279
3539
|
|
|
3280
3540
|
// src/domains/identity/MetaBridge.ts
|
|
3281
|
-
var
|
|
3541
|
+
var import_react_native13 = require("react-native");
|
|
3282
3542
|
var TIMEOUT_MS = 2e3;
|
|
3283
|
-
var NativeBridge =
|
|
3543
|
+
var NativeBridge = import_react_native13.NativeModules.PaywalloFBBridge ?? null;
|
|
3284
3544
|
var MetaBridge = class {
|
|
3285
3545
|
async getAnonymousID() {
|
|
3286
3546
|
if (!NativeBridge) return null;
|
|
@@ -3328,11 +3588,11 @@ var MetaBridge = class {
|
|
|
3328
3588
|
var metaBridge = new MetaBridge();
|
|
3329
3589
|
|
|
3330
3590
|
// src/domains/identity/InstallReferrerManager.ts
|
|
3331
|
-
var
|
|
3591
|
+
var import_react_native14 = require("react-native");
|
|
3332
3592
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
3333
3593
|
var InstallReferrerManager = class {
|
|
3334
3594
|
async getReferrer() {
|
|
3335
|
-
if (
|
|
3595
|
+
if (import_react_native14.Platform.OS !== "android") {
|
|
3336
3596
|
return this.emptyResult();
|
|
3337
3597
|
}
|
|
3338
3598
|
let timerId;
|
|
@@ -3346,8 +3606,7 @@ var InstallReferrerManager = class {
|
|
|
3346
3606
|
}
|
|
3347
3607
|
async fetchReferrer() {
|
|
3348
3608
|
try {
|
|
3349
|
-
const
|
|
3350
|
-
const referrer = await Application.getInstallReferrerAsync();
|
|
3609
|
+
const referrer = await nativeDeviceInfo.getInstallReferrer();
|
|
3351
3610
|
if (!referrer) return this.emptyResult();
|
|
3352
3611
|
const capped = referrer.slice(0, 2048);
|
|
3353
3612
|
const params = this.parseReferrer(capped);
|
|
@@ -3392,51 +3651,16 @@ var InstallReferrerManager = class {
|
|
|
3392
3651
|
var installReferrerManager = new InstallReferrerManager();
|
|
3393
3652
|
|
|
3394
3653
|
// src/domains/session/SessionManager.ts
|
|
3395
|
-
var
|
|
3654
|
+
var import_react_native15 = require("react-native");
|
|
3396
3655
|
init_uuid();
|
|
3397
3656
|
|
|
3398
3657
|
// src/utils/deviceInfo.ts
|
|
3399
|
-
var import_react_native12 = require("react-native");
|
|
3400
|
-
async function getAppVersion() {
|
|
3401
|
-
try {
|
|
3402
|
-
const app = await import("expo-application");
|
|
3403
|
-
if (app.nativeApplicationVersion) {
|
|
3404
|
-
return app.nativeApplicationVersion;
|
|
3405
|
-
}
|
|
3406
|
-
} catch {
|
|
3407
|
-
}
|
|
3408
|
-
try {
|
|
3409
|
-
const DeviceInfo2 = (await import("react-native-device-info")).default;
|
|
3410
|
-
const v = DeviceInfo2.getVersion();
|
|
3411
|
-
if (v) return v;
|
|
3412
|
-
} catch {
|
|
3413
|
-
}
|
|
3414
|
-
return "unknown";
|
|
3415
|
-
}
|
|
3416
|
-
async function getDeviceModel() {
|
|
3417
|
-
try {
|
|
3418
|
-
const DeviceInfo2 = (await import("react-native-device-info")).default;
|
|
3419
|
-
const model = DeviceInfo2.getModel();
|
|
3420
|
-
if (model) return model;
|
|
3421
|
-
} catch {
|
|
3422
|
-
}
|
|
3423
|
-
return "unknown";
|
|
3424
|
-
}
|
|
3425
|
-
function getOsVersion() {
|
|
3426
|
-
const v = import_react_native12.Platform.Version;
|
|
3427
|
-
if (typeof v === "number") return String(v);
|
|
3428
|
-
if (typeof v === "string" && v.length > 0) return v;
|
|
3429
|
-
return "unknown";
|
|
3430
|
-
}
|
|
3431
3658
|
async function collectDeviceInfo() {
|
|
3432
|
-
const
|
|
3433
|
-
getAppVersion(),
|
|
3434
|
-
getDeviceModel()
|
|
3435
|
-
]);
|
|
3659
|
+
const result = await nativeDeviceInfo.getDeviceInfo();
|
|
3436
3660
|
return {
|
|
3437
|
-
appVersion,
|
|
3438
|
-
deviceModel,
|
|
3439
|
-
osVersion:
|
|
3661
|
+
appVersion: result.appVersion,
|
|
3662
|
+
deviceModel: result.model,
|
|
3663
|
+
osVersion: result.systemVersion
|
|
3440
3664
|
};
|
|
3441
3665
|
}
|
|
3442
3666
|
|
|
@@ -3627,7 +3851,7 @@ var SessionManager = class {
|
|
|
3627
3851
|
if (this.appStateSubscription) {
|
|
3628
3852
|
this.appStateSubscription.remove();
|
|
3629
3853
|
}
|
|
3630
|
-
this.appStateSubscription =
|
|
3854
|
+
this.appStateSubscription = import_react_native15.AppState.addEventListener("change", this.handleAppStateChange);
|
|
3631
3855
|
}
|
|
3632
3856
|
async handleForeground() {
|
|
3633
3857
|
if (this.backgroundTimestamp) {
|
|
@@ -3700,16 +3924,26 @@ var sessionManager = new SessionManager();
|
|
|
3700
3924
|
// src/domains/identity/InstallTracker.ts
|
|
3701
3925
|
var InstallTracker = class {
|
|
3702
3926
|
constructor(debug = false, advertisingIdManager) {
|
|
3927
|
+
this._trackingInProgress = false;
|
|
3703
3928
|
this.debug = debug;
|
|
3704
3929
|
this.advertisingIdManager = advertisingIdManager ?? new AdvertisingIdManager();
|
|
3705
3930
|
}
|
|
3706
3931
|
async trackIfNeeded(apiClient, requestATT = false) {
|
|
3932
|
+
if (this._trackingInProgress) return;
|
|
3933
|
+
this._trackingInProgress = true;
|
|
3934
|
+
try {
|
|
3935
|
+
await this._doTrack(apiClient, requestATT);
|
|
3936
|
+
} finally {
|
|
3937
|
+
this._trackingInProgress = false;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
async _doTrack(apiClient, requestATT) {
|
|
3707
3941
|
const storage = new SecureStorage(this.debug);
|
|
3708
3942
|
const alreadyTracked = await storage.get("@panel:install_tracked");
|
|
3709
3943
|
if (alreadyTracked) return;
|
|
3710
3944
|
let fallbackTracked = null;
|
|
3711
3945
|
try {
|
|
3712
|
-
fallbackTracked = await
|
|
3946
|
+
fallbackTracked = await nativeStorage.get("@paywallo:install_tracked");
|
|
3713
3947
|
} catch {
|
|
3714
3948
|
}
|
|
3715
3949
|
if (fallbackTracked) return;
|
|
@@ -3717,47 +3951,47 @@ var InstallTracker = class {
|
|
|
3717
3951
|
const sessionId = sessionManager.getSessionId();
|
|
3718
3952
|
const installedAt = Date.now();
|
|
3719
3953
|
if (!distinctId) return;
|
|
3720
|
-
const deviceInfo = import_react_native_device_info2.default;
|
|
3721
3954
|
let deviceData = {};
|
|
3722
3955
|
try {
|
|
3723
|
-
const screen =
|
|
3956
|
+
const screen = import_react_native16.Dimensions.get("screen");
|
|
3724
3957
|
const screenWidth = Math.round(screen.width ?? 0);
|
|
3725
3958
|
const screenHeight = Math.round(screen.height ?? 0);
|
|
3726
3959
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
3727
3960
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
|
|
3728
|
-
const
|
|
3729
|
-
const
|
|
3730
|
-
const
|
|
3731
|
-
const
|
|
3961
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
3962
|
+
const deviceModel = info.modelId || info.model || "unknown";
|
|
3963
|
+
const osVersion = info.systemVersion || "unknown";
|
|
3964
|
+
const appVersion = info.appVersion || "unknown";
|
|
3965
|
+
const buildNumber = info.buildNumber || "0";
|
|
3732
3966
|
let carrier = "unknown";
|
|
3733
3967
|
try {
|
|
3734
|
-
carrier =
|
|
3968
|
+
carrier = info.carrier ?? "unknown";
|
|
3735
3969
|
} catch {
|
|
3736
3970
|
}
|
|
3737
3971
|
let totalDisk = 0;
|
|
3738
3972
|
try {
|
|
3739
|
-
totalDisk = Number(
|
|
3973
|
+
totalDisk = Number(info.totalDisk) || 0;
|
|
3740
3974
|
} catch {
|
|
3741
3975
|
}
|
|
3742
3976
|
let freeDisk = 0;
|
|
3743
3977
|
try {
|
|
3744
|
-
freeDisk = Number(
|
|
3978
|
+
freeDisk = Number(info.freeDisk) || 0;
|
|
3745
3979
|
} catch {
|
|
3746
3980
|
}
|
|
3747
3981
|
let totalRam = 0;
|
|
3748
3982
|
try {
|
|
3749
|
-
totalRam = Number(
|
|
3983
|
+
totalRam = Number(info.totalRam) || 0;
|
|
3750
3984
|
} catch {
|
|
3751
3985
|
}
|
|
3752
3986
|
let brand = "unknown";
|
|
3753
3987
|
try {
|
|
3754
|
-
brand =
|
|
3988
|
+
brand = info.brand ?? "unknown";
|
|
3755
3989
|
} catch {
|
|
3756
3990
|
}
|
|
3757
3991
|
let installerPackage = null;
|
|
3758
3992
|
try {
|
|
3759
|
-
if (
|
|
3760
|
-
installerPackage =
|
|
3993
|
+
if (import_react_native16.Platform.OS === "android") {
|
|
3994
|
+
installerPackage = info.installerPackage ?? null;
|
|
3761
3995
|
}
|
|
3762
3996
|
} catch {
|
|
3763
3997
|
}
|
|
@@ -3796,37 +4030,34 @@ var InstallTracker = class {
|
|
|
3796
4030
|
});
|
|
3797
4031
|
}
|
|
3798
4032
|
}
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
4033
|
+
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
4034
|
+
installedAt,
|
|
4035
|
+
platform: import_react_native16.Platform.OS,
|
|
4036
|
+
...sessionId && { sessionId },
|
|
4037
|
+
...deviceData,
|
|
4038
|
+
...effectiveAnonId && { fbAnonId: effectiveAnonId },
|
|
4039
|
+
...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
|
|
4040
|
+
...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
|
|
4041
|
+
...referrer.fbclid && { referrerFbclid: referrer.fbclid },
|
|
4042
|
+
...referrer.utmSource && { referrerUtmSource: referrer.utmSource },
|
|
4043
|
+
...referrer.utmMedium && { referrerUtmMedium: referrer.utmMedium },
|
|
4044
|
+
...referrer.utmCampaign && { referrerUtmCampaign: referrer.utmCampaign },
|
|
4045
|
+
...adIds.idfa && { idfa: adIds.idfa },
|
|
4046
|
+
...adIds.idfv && { idfv: adIds.idfv },
|
|
4047
|
+
...adIds.gaid && { gaid: adIds.gaid },
|
|
4048
|
+
attStatus: adIds.attStatus
|
|
4049
|
+
});
|
|
4050
|
+
if (import_react_native16.Platform.OS === "ios") {
|
|
4051
|
+
void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId, installedAt).catch(() => {
|
|
3816
4052
|
});
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
await storage.set("@panel:install_tracked", String(installedAt));
|
|
3822
|
-
try {
|
|
3823
|
-
await import_async_storage3.default.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3824
|
-
} catch {
|
|
3825
|
-
}
|
|
4053
|
+
}
|
|
4054
|
+
await storage.set("@panel:install_tracked", String(installedAt));
|
|
4055
|
+
try {
|
|
4056
|
+
await nativeStorage.set("@paywallo:install_tracked", String(installedAt));
|
|
3826
4057
|
} catch {
|
|
3827
4058
|
}
|
|
3828
4059
|
}
|
|
3829
|
-
async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
|
|
4060
|
+
async callDeferredMatch(apiClient, deviceData, adIds, anonId, installedAt) {
|
|
3830
4061
|
const storage = new SecureStorage(this.debug);
|
|
3831
4062
|
const alreadyMatched = await storage.get("@panel:deferred_match_done");
|
|
3832
4063
|
if (alreadyMatched) return;
|
|
@@ -3834,7 +4065,7 @@ var InstallTracker = class {
|
|
|
3834
4065
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
3835
4066
|
try {
|
|
3836
4067
|
const baseUrl = apiClient.getBaseUrl();
|
|
3837
|
-
const appKey = apiClient.
|
|
4068
|
+
const appKey = apiClient.getAppKey();
|
|
3838
4069
|
await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
|
|
3839
4070
|
method: "POST",
|
|
3840
4071
|
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
@@ -3846,7 +4077,8 @@ var InstallTracker = class {
|
|
|
3846
4077
|
screenHeight: deviceData.screenHeight,
|
|
3847
4078
|
timezone: deviceData.timezone,
|
|
3848
4079
|
locale: deviceData.locale,
|
|
3849
|
-
fbAnonId: anonId
|
|
4080
|
+
fbAnonId: anonId,
|
|
4081
|
+
installTimestamp: new Date(installedAt).toISOString()
|
|
3850
4082
|
}),
|
|
3851
4083
|
signal: controller.signal
|
|
3852
4084
|
});
|
|
@@ -3971,7 +4203,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3971
4203
|
error: error instanceof Error ? error.message : String(error)
|
|
3972
4204
|
});
|
|
3973
4205
|
} catch (err) {
|
|
3974
|
-
if (config.debug) console.
|
|
4206
|
+
if (config.debug) console.error("[Paywallo] reportInitFailure error", err);
|
|
3975
4207
|
}
|
|
3976
4208
|
}
|
|
3977
4209
|
reportInitSuccess(config, attempts) {
|
|
@@ -3980,7 +4212,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3980
4212
|
void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
|
|
3981
4213
|
}
|
|
3982
4214
|
} catch (err) {
|
|
3983
|
-
if (config.debug) console.
|
|
4215
|
+
if (config.debug) console.error("[Paywallo] reportInitSuccess error", err);
|
|
3984
4216
|
}
|
|
3985
4217
|
}
|
|
3986
4218
|
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
@@ -4027,24 +4259,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4027
4259
|
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
4028
4260
|
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
4029
4261
|
}
|
|
4262
|
+
setNativeStorageDebug(config.debug ?? false);
|
|
4263
|
+
setNativeDeviceInfoDebug(config.debug ?? false);
|
|
4030
4264
|
this.config = config;
|
|
4031
4265
|
if (this.resolveReady) {
|
|
4032
4266
|
this.resolveReady();
|
|
4033
4267
|
this.resolveReady = null;
|
|
4034
4268
|
}
|
|
4035
|
-
if (config.debug) console.
|
|
4269
|
+
if (config.debug) console.log("[Paywallo INIT] complete");
|
|
4036
4270
|
if (distinctId) {
|
|
4037
4271
|
this.apiClient.getSubscriptionStatus(distinctId).then(() => {
|
|
4038
|
-
if (config.debug) console.
|
|
4272
|
+
if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
|
|
4039
4273
|
}).catch(() => {
|
|
4040
4274
|
});
|
|
4041
4275
|
}
|
|
4042
|
-
|
|
4276
|
+
{
|
|
4043
4277
|
const apiClientRef = this.apiClient;
|
|
4044
4278
|
if (config.autoPreloadCampaign) {
|
|
4045
4279
|
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
4046
4280
|
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
4047
|
-
if (config.debug) console.
|
|
4281
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
4048
4282
|
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
4049
4283
|
});
|
|
4050
4284
|
} else {
|
|
@@ -4052,7 +4286,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4052
4286
|
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
4053
4287
|
if (placement) {
|
|
4054
4288
|
this.autoPreloadedPlacement = placement;
|
|
4055
|
-
if (config.debug) console.
|
|
4289
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
4056
4290
|
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
4057
4291
|
});
|
|
4058
4292
|
return placement;
|
|
@@ -4064,21 +4298,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4064
4298
|
}
|
|
4065
4299
|
async identify(options) {
|
|
4066
4300
|
this.ensureInitialized();
|
|
4067
|
-
if (this.config?.debug) console.
|
|
4301
|
+
if (this.config?.debug) console.log("[Paywallo IDENTIFY]", options ? { hasEmail: "email" in options && !!options.email, hasProperties: "properties" in options } : void 0);
|
|
4068
4302
|
await identityManager.identify(options);
|
|
4069
4303
|
}
|
|
4070
4304
|
async track(eventName, options) {
|
|
4071
4305
|
const apiClient = this.getApiClientOrThrow();
|
|
4072
4306
|
const distinctId = identityManager.getDistinctId();
|
|
4073
4307
|
if (!distinctId) {
|
|
4074
|
-
if (this.config?.debug) console.
|
|
4308
|
+
if (this.config?.debug) console.log(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
|
|
4075
4309
|
return;
|
|
4076
4310
|
}
|
|
4077
4311
|
if (!isValidEventName(eventName)) {
|
|
4078
4312
|
const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
|
|
4079
4313
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
4080
4314
|
}
|
|
4081
|
-
if (this.config?.debug && eventName.startsWith("$purchase")) console.
|
|
4315
|
+
if (this.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
|
|
4082
4316
|
const sessionId = sessionManager.getSessionId();
|
|
4083
4317
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
4084
4318
|
...identityManager.getProperties(),
|
|
@@ -4137,14 +4371,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4137
4371
|
try {
|
|
4138
4372
|
const distinctId = identityManager.getDistinctId();
|
|
4139
4373
|
if (!distinctId) {
|
|
4140
|
-
if (this.config?.debug) console.
|
|
4374
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
|
|
4141
4375
|
return { variant: null };
|
|
4142
4376
|
}
|
|
4143
4377
|
const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
4144
|
-
if (this.config?.debug) console.
|
|
4378
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
|
|
4145
4379
|
return result;
|
|
4146
4380
|
} catch (error) {
|
|
4147
|
-
if (this.config?.debug) console.
|
|
4381
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
|
|
4148
4382
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
4149
4383
|
return { variant: null };
|
|
4150
4384
|
}
|
|
@@ -4177,11 +4411,11 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4177
4411
|
}
|
|
4178
4412
|
async presentCampaign(placement, context) {
|
|
4179
4413
|
this.ensureInitialized();
|
|
4180
|
-
if (this.config?.debug) console.
|
|
4414
|
+
if (this.config?.debug) console.log(`[Paywallo CAMPAIGN] presenting ${placement}`);
|
|
4181
4415
|
const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
4182
4416
|
if (this.config?.debug) {
|
|
4183
|
-
if (result.error) console.
|
|
4184
|
-
else console.
|
|
4417
|
+
if (result.error) console.log(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
|
|
4418
|
+
else console.log(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
|
|
4185
4419
|
}
|
|
4186
4420
|
return result;
|
|
4187
4421
|
}
|
|
@@ -4191,20 +4425,20 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4191
4425
|
if (this.activeChecker) {
|
|
4192
4426
|
const result = await this.activeChecker();
|
|
4193
4427
|
const isActive2 = result === true;
|
|
4194
|
-
if (this.config?.debug) console.
|
|
4428
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
|
|
4195
4429
|
return isActive2;
|
|
4196
4430
|
}
|
|
4197
4431
|
const distinctId = identityManager.getDistinctId();
|
|
4198
4432
|
if (!distinctId || !this.apiClient) {
|
|
4199
|
-
if (this.config?.debug) console.
|
|
4433
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
|
|
4200
4434
|
return false;
|
|
4201
4435
|
}
|
|
4202
4436
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
4203
4437
|
const isActive = status.hasActiveSubscription === true;
|
|
4204
|
-
if (this.config?.debug) console.
|
|
4438
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (server):", isActive);
|
|
4205
4439
|
return isActive;
|
|
4206
4440
|
} catch (error) {
|
|
4207
|
-
if (this.config?.debug) console.
|
|
4441
|
+
if (this.config?.debug) console.error("[Paywallo SUB] hasActiveSubscription error", error);
|
|
4208
4442
|
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4209
4443
|
return false;
|
|
4210
4444
|
}
|
|
@@ -4225,9 +4459,9 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4225
4459
|
async restorePurchases() {
|
|
4226
4460
|
this.ensureInitialized();
|
|
4227
4461
|
if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
4228
|
-
if (this.config?.debug) console.
|
|
4462
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] starting");
|
|
4229
4463
|
const result = await this.restoreHandler();
|
|
4230
|
-
if (this.config?.debug) console.
|
|
4464
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
|
|
4231
4465
|
return result;
|
|
4232
4466
|
}
|
|
4233
4467
|
async reset() {
|
|
@@ -4237,6 +4471,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4237
4471
|
async fullReset() {
|
|
4238
4472
|
const wasDebug = this.config?.debug ?? false;
|
|
4239
4473
|
await sessionManager.endSession();
|
|
4474
|
+
sessionManager.destroy();
|
|
4240
4475
|
await identityManager.reset();
|
|
4241
4476
|
await subscriptionCache.invalidateAll();
|
|
4242
4477
|
queueProcessor.dispose();
|
|
@@ -4245,6 +4480,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4245
4480
|
campaignGateService.clearPreloaded();
|
|
4246
4481
|
this.cleanupNetworkRecovery();
|
|
4247
4482
|
this.sessionFlagsMap.clear();
|
|
4483
|
+
this.apiClient?.dispose();
|
|
4248
4484
|
this.apiClient = null;
|
|
4249
4485
|
this.config = null;
|
|
4250
4486
|
this.initPromise = null;
|
|
@@ -4581,6 +4817,7 @@ function usePurchase() {
|
|
|
4581
4817
|
|
|
4582
4818
|
// src/hooks/useSubscription.ts
|
|
4583
4819
|
var import_react13 = require("react");
|
|
4820
|
+
var EMPTY_ENTITLEMENTS = [];
|
|
4584
4821
|
function useSubscription() {
|
|
4585
4822
|
const [status, setStatus] = (0, import_react13.useState)(null);
|
|
4586
4823
|
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
@@ -4616,7 +4853,8 @@ function useSubscription() {
|
|
|
4616
4853
|
setStatus(s);
|
|
4617
4854
|
setIsLoading(false);
|
|
4618
4855
|
}).catch((err) => {
|
|
4619
|
-
|
|
4856
|
+
const e = err instanceof PurchaseError ? err : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, String(err));
|
|
4857
|
+
setError(e);
|
|
4620
4858
|
setIsLoading(false);
|
|
4621
4859
|
});
|
|
4622
4860
|
const removeListener = subscriptionManager.addListener((newStatus) => {
|
|
@@ -4631,7 +4869,7 @@ function useSubscription() {
|
|
|
4631
4869
|
isActive: status?.hasActiveSubscription ?? false,
|
|
4632
4870
|
isLoading,
|
|
4633
4871
|
error,
|
|
4634
|
-
entitlements: status?.entitlements ??
|
|
4872
|
+
entitlements: status?.entitlements ?? EMPTY_ENTITLEMENTS,
|
|
4635
4873
|
refresh,
|
|
4636
4874
|
restore
|
|
4637
4875
|
};
|
|
@@ -4639,7 +4877,7 @@ function useSubscription() {
|
|
|
4639
4877
|
|
|
4640
4878
|
// src/PaywalloProvider.tsx
|
|
4641
4879
|
var import_react18 = require("react");
|
|
4642
|
-
var
|
|
4880
|
+
var import_react_native17 = require("react-native");
|
|
4643
4881
|
|
|
4644
4882
|
// src/hooks/useCampaignPreload.ts
|
|
4645
4883
|
var import_react14 = require("react");
|
|
@@ -4763,7 +5001,7 @@ function useCampaignPreload() {
|
|
|
4763
5001
|
campaignId: campaign.campaignId,
|
|
4764
5002
|
variantKey: campaign.variantKey
|
|
4765
5003
|
});
|
|
4766
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5004
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4767
5005
|
return { success: true };
|
|
4768
5006
|
} catch (error) {
|
|
4769
5007
|
return {
|
|
@@ -4798,7 +5036,7 @@ function useProductLoader() {
|
|
|
4798
5036
|
for (const p of loaded) map.set(p.productId, p);
|
|
4799
5037
|
setProducts(map);
|
|
4800
5038
|
} catch (err) {
|
|
4801
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5039
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] refreshProducts failed", err);
|
|
4802
5040
|
} finally {
|
|
4803
5041
|
setIsLoadingProducts(false);
|
|
4804
5042
|
}
|
|
@@ -4838,7 +5076,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4838
5076
|
clearPreloadedWebView();
|
|
4839
5077
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4840
5078
|
} catch (error) {
|
|
4841
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5079
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded purchase failed", error);
|
|
4842
5080
|
setShowingPreloadedWebView(false);
|
|
4843
5081
|
clearPreloadedWebView();
|
|
4844
5082
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4859,7 +5097,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4859
5097
|
clearPreloadedWebView();
|
|
4860
5098
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4861
5099
|
} catch (error) {
|
|
4862
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5100
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded restore failed", error);
|
|
4863
5101
|
setShowingPreloadedWebView(false);
|
|
4864
5102
|
clearPreloadedWebView();
|
|
4865
5103
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4872,11 +5110,28 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4872
5110
|
var import_react17 = require("react");
|
|
4873
5111
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4874
5112
|
var ACTIVE_STATUSES = ["active", "in_grace_period"];
|
|
5113
|
+
var PUBLIC_TO_DOMAIN_STATUS = {
|
|
5114
|
+
active: "active",
|
|
5115
|
+
expired: "expired",
|
|
5116
|
+
in_billing_retry: "billing_retry",
|
|
5117
|
+
in_grace_period: "grace_period",
|
|
5118
|
+
revoked: "unknown",
|
|
5119
|
+
cancelled: "canceled"
|
|
5120
|
+
};
|
|
5121
|
+
var DOMAIN_TO_PUBLIC_STATUS = {
|
|
5122
|
+
active: "active",
|
|
5123
|
+
expired: "expired",
|
|
5124
|
+
billing_retry: "in_billing_retry",
|
|
5125
|
+
grace_period: "in_grace_period",
|
|
5126
|
+
canceled: "cancelled",
|
|
5127
|
+
paused: "expired",
|
|
5128
|
+
unknown: "expired"
|
|
5129
|
+
};
|
|
4875
5130
|
function toDomainSubscriptionInfo(sub) {
|
|
4876
5131
|
return {
|
|
4877
5132
|
id: "",
|
|
4878
5133
|
productId: sub.productId,
|
|
4879
|
-
status: sub.status,
|
|
5134
|
+
status: PUBLIC_TO_DOMAIN_STATUS[sub.status] ?? "unknown",
|
|
4880
5135
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4881
5136
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4882
5137
|
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4899,6 +5154,10 @@ function isSubscriptionStillActive(sub) {
|
|
|
4899
5154
|
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4900
5155
|
return true;
|
|
4901
5156
|
}
|
|
5157
|
+
function subscriptionInfoToSub(s) {
|
|
5158
|
+
const status = DOMAIN_TO_PUBLIC_STATUS[s.status] ?? "expired";
|
|
5159
|
+
return { productId: s.productId, status, expiresAt: s.expiresAt ?? null, platform: s.platform, autoRenewEnabled: s.willRenew, inGracePeriod: status === "in_grace_period" };
|
|
5160
|
+
}
|
|
4902
5161
|
async function resolveOfflineFromCache(distinctId) {
|
|
4903
5162
|
try {
|
|
4904
5163
|
const cached = await subscriptionCache.get(distinctId);
|
|
@@ -4939,24 +5198,12 @@ function useSubscriptionSync() {
|
|
|
4939
5198
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4940
5199
|
if (!apiClient || !distinctId) return false;
|
|
4941
5200
|
const cached = cacheRef.current;
|
|
4942
|
-
if (cached && cached.expiresAt > Date.now())
|
|
4943
|
-
return isActiveCacheEntry(cached);
|
|
4944
|
-
}
|
|
5201
|
+
if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
|
|
4945
5202
|
try {
|
|
4946
5203
|
const persisted = await subscriptionCache.get(distinctId);
|
|
4947
5204
|
if (persisted && !persisted.isStale) {
|
|
4948
|
-
const
|
|
4949
|
-
|
|
4950
|
-
const sub = persistedSub ? {
|
|
4951
|
-
productId: persistedSub.productId,
|
|
4952
|
-
status: persistedStatus,
|
|
4953
|
-
expiresAt: persistedSub.expiresAt ?? null,
|
|
4954
|
-
platform: persistedSub.platform,
|
|
4955
|
-
autoRenewEnabled: persistedSub.willRenew,
|
|
4956
|
-
inGracePeriod: persistedStatus === "in_grace_period"
|
|
4957
|
-
} : null;
|
|
4958
|
-
const stillActive = isSubscriptionStillActive(sub);
|
|
4959
|
-
if (stillActive) {
|
|
5205
|
+
const sub = persisted.data.subscription ? subscriptionInfoToSub(persisted.data.subscription) : null;
|
|
5206
|
+
if (isSubscriptionStillActive(sub)) {
|
|
4960
5207
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4961
5208
|
setSubscription(sub);
|
|
4962
5209
|
return true;
|
|
@@ -4965,17 +5212,14 @@ function useSubscriptionSync() {
|
|
|
4965
5212
|
} catch {
|
|
4966
5213
|
}
|
|
4967
5214
|
try {
|
|
4968
|
-
const
|
|
4969
|
-
const sub =
|
|
4970
|
-
...status.subscription,
|
|
4971
|
-
expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null
|
|
4972
|
-
} : null;
|
|
5215
|
+
const apiStatus = await apiClient.getSubscriptionStatus(distinctId);
|
|
5216
|
+
const sub = apiStatus.subscription ? { ...apiStatus.subscription, expiresAt: apiStatus.subscription.expiresAt ? new Date(apiStatus.subscription.expiresAt) : null } : null;
|
|
4973
5217
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4974
5218
|
setSubscription(sub);
|
|
4975
|
-
void subscriptionCache.set(distinctId, toDomainResponse(
|
|
4976
|
-
return
|
|
5219
|
+
void subscriptionCache.set(distinctId, toDomainResponse(apiStatus));
|
|
5220
|
+
return apiStatus.hasActiveSubscription === true;
|
|
4977
5221
|
} catch {
|
|
4978
|
-
return
|
|
5222
|
+
return resolveOfflineFromCache(distinctId);
|
|
4979
5223
|
}
|
|
4980
5224
|
}, []);
|
|
4981
5225
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -4990,16 +5234,7 @@ function useSubscriptionSync() {
|
|
|
4990
5234
|
try {
|
|
4991
5235
|
const persisted = await subscriptionCache.get(distinctId);
|
|
4992
5236
|
if (!persisted?.data.subscription) return null;
|
|
4993
|
-
|
|
4994
|
-
const status = s.status;
|
|
4995
|
-
return {
|
|
4996
|
-
productId: s.productId,
|
|
4997
|
-
status,
|
|
4998
|
-
expiresAt: s.expiresAt ?? null,
|
|
4999
|
-
platform: s.platform,
|
|
5000
|
-
autoRenewEnabled: s.willRenew,
|
|
5001
|
-
inGracePeriod: status === "in_grace_period"
|
|
5002
|
-
};
|
|
5237
|
+
return subscriptionInfoToSub(persisted.data.subscription);
|
|
5003
5238
|
} catch {
|
|
5004
5239
|
return null;
|
|
5005
5240
|
}
|
|
@@ -5021,6 +5256,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
5021
5256
|
const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
5022
5257
|
const autoPreloadTriggeredRef = (0, import_react18.useRef)(false);
|
|
5023
5258
|
const preloadedWebViewRef = (0, import_react18.useRef)(preloadedWebView);
|
|
5259
|
+
const pollTimerRef = (0, import_react18.useRef)(null);
|
|
5260
|
+
const pollCancelledRef = (0, import_react18.useRef)(false);
|
|
5024
5261
|
(0, import_react18.useEffect)(() => {
|
|
5025
5262
|
preloadedWebViewRef.current = preloadedWebView;
|
|
5026
5263
|
}, [preloadedWebView]);
|
|
@@ -5029,21 +5266,21 @@ function PaywalloProvider({ children, config }) {
|
|
|
5029
5266
|
});
|
|
5030
5267
|
}, []);
|
|
5031
5268
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
5032
|
-
const
|
|
5033
|
-
const slideAnim = (0, import_react18.useRef)(new
|
|
5269
|
+
const SCREEN_HEIGHT = (0, import_react18.useMemo)(() => import_react_native17.Dimensions.get("window").height, []);
|
|
5270
|
+
const slideAnim = (0, import_react18.useRef)(new import_react_native17.Animated.Value(SCREEN_HEIGHT)).current;
|
|
5034
5271
|
const handlePreloadedWebViewReady = (0, import_react18.useCallback)(() => {
|
|
5035
5272
|
if (PaywalloClient.getConfig()?.debug) {
|
|
5036
|
-
console.
|
|
5273
|
+
console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
5037
5274
|
}
|
|
5038
5275
|
baseHandlePreloadedWebViewReady();
|
|
5039
|
-
}, [
|
|
5276
|
+
}, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
5040
5277
|
const handlePreloadedClose = (0, import_react18.useCallback)(() => {
|
|
5041
5278
|
const placement = preloadedWebView?.placement;
|
|
5042
|
-
|
|
5279
|
+
import_react_native17.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native17.Easing.in(import_react_native17.Easing.cubic), useNativeDriver: true }).start(() => {
|
|
5043
5280
|
baseHandlePreloadedClose();
|
|
5044
5281
|
if (placement) triggerRepreload(placement);
|
|
5045
5282
|
});
|
|
5046
|
-
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim,
|
|
5283
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT]);
|
|
5047
5284
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
5048
5285
|
preloadedWebView,
|
|
5049
5286
|
resolvePreloadedPaywall,
|
|
@@ -5061,21 +5298,28 @@ function PaywalloProvider({ children, config }) {
|
|
|
5061
5298
|
setIsPreloadPurchasing(false);
|
|
5062
5299
|
}
|
|
5063
5300
|
}, [rawPreloadedPurchase]);
|
|
5301
|
+
const configRef = (0, import_react18.useRef)(config);
|
|
5064
5302
|
(0, import_react18.useEffect)(() => {
|
|
5303
|
+
let mounted = true;
|
|
5065
5304
|
initLocalization({ detectDevice: true });
|
|
5066
|
-
PaywalloClient.init(
|
|
5305
|
+
PaywalloClient.init(configRef.current).then(async () => {
|
|
5306
|
+
if (!mounted) return;
|
|
5067
5307
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
5068
5308
|
setIsInitialized(true);
|
|
5069
5309
|
if (!autoPreloadTriggeredRef.current) {
|
|
5070
5310
|
autoPreloadTriggeredRef.current = true;
|
|
5071
5311
|
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
5072
|
-
if (placement) void preloadCampaign(placement).catch(() => {
|
|
5312
|
+
if (mounted && placement) void preloadCampaign(placement).catch(() => {
|
|
5073
5313
|
});
|
|
5074
5314
|
}
|
|
5075
5315
|
}).catch((err) => {
|
|
5316
|
+
if (!mounted) return;
|
|
5076
5317
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
5077
5318
|
});
|
|
5078
|
-
|
|
5319
|
+
return () => {
|
|
5320
|
+
mounted = false;
|
|
5321
|
+
};
|
|
5322
|
+
}, [preloadCampaign]);
|
|
5079
5323
|
const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
|
|
5080
5324
|
const api = PaywalloClient.getApiClient();
|
|
5081
5325
|
return api ? api.getPaywall(p) : null;
|
|
@@ -5176,15 +5420,17 @@ function PaywalloProvider({ children, config }) {
|
|
|
5176
5420
|
}, [activePaywall]);
|
|
5177
5421
|
const bridgePaywallPresenter = (0, import_react18.useCallback)((placement) => {
|
|
5178
5422
|
if (PaywalloClient.getConfig()?.debug) {
|
|
5179
|
-
console.
|
|
5423
|
+
console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
5180
5424
|
}
|
|
5181
5425
|
if (preloadedWebViewRef.current?.placement === placement) {
|
|
5182
5426
|
if (preloadedWebViewRef.current.loaded) {
|
|
5183
5427
|
return presentPreloadedWebView();
|
|
5184
5428
|
}
|
|
5429
|
+
pollCancelledRef.current = false;
|
|
5185
5430
|
return new Promise((resolve) => {
|
|
5186
5431
|
let elapsed = 0;
|
|
5187
5432
|
const poll = () => {
|
|
5433
|
+
if (pollCancelledRef.current) return;
|
|
5188
5434
|
if (preloadedWebViewRef.current?.loaded) {
|
|
5189
5435
|
resolve(presentPreloadedWebView());
|
|
5190
5436
|
return;
|
|
@@ -5194,9 +5440,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
5194
5440
|
resolve(presentCampaign(placement));
|
|
5195
5441
|
return;
|
|
5196
5442
|
}
|
|
5197
|
-
setTimeout(poll, 100);
|
|
5443
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5198
5444
|
};
|
|
5199
|
-
setTimeout(poll, 100);
|
|
5445
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5200
5446
|
});
|
|
5201
5447
|
}
|
|
5202
5448
|
return presentCampaign(placement);
|
|
@@ -5214,6 +5460,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
5214
5460
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
5215
5461
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
5216
5462
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
5463
|
+
return () => {
|
|
5464
|
+
pollCancelledRef.current = true;
|
|
5465
|
+
if (pollTimerRef.current !== null) {
|
|
5466
|
+
clearTimeout(pollTimerRef.current);
|
|
5467
|
+
pollTimerRef.current = null;
|
|
5468
|
+
}
|
|
5469
|
+
PaywalloClient.registerPaywallPresenter(null);
|
|
5470
|
+
PaywalloClient.registerSubscriptionGetter(null);
|
|
5471
|
+
PaywalloClient.registerActiveChecker(null);
|
|
5472
|
+
PaywalloClient.registerRestoreHandler(null);
|
|
5473
|
+
PaywalloClient.registerEmergencyPaywallHandler(null);
|
|
5474
|
+
};
|
|
5217
5475
|
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5218
5476
|
const isReady = isInitialized && !isLoadingProducts;
|
|
5219
5477
|
const noOp = (0, import_react18.useCallback)(() => {
|
|
@@ -5254,10 +5512,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
5254
5512
|
]);
|
|
5255
5513
|
(0, import_react18.useLayoutEffect)(() => {
|
|
5256
5514
|
if (showingPreloadedWebView) {
|
|
5257
|
-
slideAnim.setValue(
|
|
5258
|
-
|
|
5515
|
+
slideAnim.setValue(SCREEN_HEIGHT);
|
|
5516
|
+
import_react_native17.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native17.Easing.out(import_react_native17.Easing.cubic), useNativeDriver: true }).start();
|
|
5259
5517
|
}
|
|
5260
|
-
}, [showingPreloadedWebView, slideAnim,
|
|
5518
|
+
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
|
|
5261
5519
|
const preloadStyle = [
|
|
5262
5520
|
styles3.preloadOverlay,
|
|
5263
5521
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
@@ -5265,7 +5523,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
5265
5523
|
];
|
|
5266
5524
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
5267
5525
|
children,
|
|
5268
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5526
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native17.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
5269
5527
|
PaywallWebView,
|
|
5270
5528
|
{
|
|
5271
5529
|
paywallId: preloadedWebView.paywallId,
|
|
@@ -5295,7 +5553,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
5295
5553
|
) })
|
|
5296
5554
|
] });
|
|
5297
5555
|
}
|
|
5298
|
-
var styles3 =
|
|
5556
|
+
var styles3 = import_react_native17.StyleSheet.create({
|
|
5299
5557
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
5300
5558
|
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5301
5559
|
visible: { zIndex: 9999, opacity: 1 }
|