@virex-tech/paywallo-sdk 1.4.1 → 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 +30 -9
- 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 +26 -117
- package/dist/index.d.ts +26 -117
- package/dist/index.js +859 -627
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +816 -579
- 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
|
@@ -52,10 +52,7 @@ var init_uuid = __esm({
|
|
|
52
52
|
// src/index.ts
|
|
53
53
|
var index_exports = {};
|
|
54
54
|
__export(index_exports, {
|
|
55
|
-
AFFILIATE_ERROR_CODES: () => AFFILIATE_ERROR_CODES,
|
|
56
55
|
ANALYTICS_ERROR_CODES: () => ANALYTICS_ERROR_CODES,
|
|
57
|
-
AffiliateError: () => AffiliateError,
|
|
58
|
-
AffiliateManager: () => AffiliateManager,
|
|
59
56
|
AnalyticsError: () => AnalyticsError,
|
|
60
57
|
ApiClient: () => ApiClient,
|
|
61
58
|
CAMPAIGN_ERROR_CODES: () => CAMPAIGN_ERROR_CODES,
|
|
@@ -73,7 +70,6 @@ __export(index_exports, {
|
|
|
73
70
|
PaywallError: () => PaywallError,
|
|
74
71
|
PaywallModal: () => PaywallModal,
|
|
75
72
|
Paywallo: () => Paywallo,
|
|
76
|
-
PaywalloAffiliate: () => PaywalloAffiliate,
|
|
77
73
|
PaywalloClient: () => PaywalloClient,
|
|
78
74
|
PaywalloContext: () => PaywalloContext,
|
|
79
75
|
PaywalloError: () => PaywalloError,
|
|
@@ -83,7 +79,6 @@ __export(index_exports, {
|
|
|
83
79
|
SessionError: () => SessionError,
|
|
84
80
|
SessionManager: () => SessionManager,
|
|
85
81
|
SubscriptionCache: () => SubscriptionCache,
|
|
86
|
-
affiliateManager: () => affiliateManager,
|
|
87
82
|
createPurchaseError: () => createPurchaseError,
|
|
88
83
|
default: () => index_default,
|
|
89
84
|
detectDeviceLanguage: () => detectDeviceLanguage,
|
|
@@ -149,19 +144,20 @@ var CLIENT_ERROR_CODES = {
|
|
|
149
144
|
MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
|
|
150
145
|
INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
|
|
151
146
|
PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
|
|
147
|
+
INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
|
|
152
148
|
UNKNOWN: "CLIENT_UNKNOWN"
|
|
153
149
|
};
|
|
154
150
|
|
|
155
151
|
// src/domains/paywall/PaywallModal.tsx
|
|
156
152
|
var import_react5 = require("react");
|
|
157
|
-
var
|
|
153
|
+
var import_react_native8 = require("react-native");
|
|
158
154
|
|
|
159
155
|
// src/domains/paywall/usePaywallActions.ts
|
|
160
156
|
var import_react2 = require("react");
|
|
161
|
-
var
|
|
157
|
+
var import_react_native5 = require("react-native");
|
|
162
158
|
|
|
163
159
|
// src/domains/iap/IAPService.ts
|
|
164
|
-
var
|
|
160
|
+
var import_react_native4 = require("react-native");
|
|
165
161
|
|
|
166
162
|
// src/domains/iap/PurchaseError.ts
|
|
167
163
|
var PurchaseError = class extends PaywalloError {
|
|
@@ -206,10 +202,7 @@ function createPurchaseError(code, message) {
|
|
|
206
202
|
}
|
|
207
203
|
|
|
208
204
|
// src/domains/iap/NativeStoreKit.ts
|
|
209
|
-
var
|
|
210
|
-
|
|
211
|
-
// src/domains/identity/IdentityManager.ts
|
|
212
|
-
var import_react_native_device_info = __toESM(require("react-native-device-info"));
|
|
205
|
+
var import_react_native3 = require("react-native");
|
|
213
206
|
|
|
214
207
|
// src/domains/identity/IdentityError.ts
|
|
215
208
|
var IdentityError = class extends PaywalloError {
|
|
@@ -229,13 +222,141 @@ var IDENTITY_ERROR_CODES = {
|
|
|
229
222
|
// src/domains/identity/IdentityManager.ts
|
|
230
223
|
init_uuid();
|
|
231
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
|
+
|
|
232
354
|
// src/core/storage/SecureStorage.ts
|
|
233
|
-
var
|
|
234
|
-
var
|
|
235
|
-
var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
|
|
355
|
+
var KEYCHAIN_KEY_PREFIX = "com.paywallo.sdk.";
|
|
356
|
+
var REGULAR_KEY_PREFIX = "@paywallo:";
|
|
236
357
|
var SecureStorage = class {
|
|
237
358
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
238
|
-
constructor(
|
|
359
|
+
constructor(_debug3 = false) {
|
|
239
360
|
}
|
|
240
361
|
async get(key) {
|
|
241
362
|
const keychainValue = await this.getFromKeychain(key);
|
|
@@ -243,9 +364,10 @@ var SecureStorage = class {
|
|
|
243
364
|
return keychainValue;
|
|
244
365
|
}
|
|
245
366
|
try {
|
|
246
|
-
const value = await
|
|
367
|
+
const value = await nativeStorage.get(`${REGULAR_KEY_PREFIX}${key}`);
|
|
247
368
|
if (value) {
|
|
248
369
|
await this.setToKeychain(key, value);
|
|
370
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0);
|
|
249
371
|
}
|
|
250
372
|
return value;
|
|
251
373
|
} catch {
|
|
@@ -253,20 +375,13 @@ var SecureStorage = class {
|
|
|
253
375
|
}
|
|
254
376
|
}
|
|
255
377
|
async set(key, value) {
|
|
256
|
-
|
|
257
|
-
await Promise.all([
|
|
258
|
-
import_async_storage.default.setItem(`@paywallo:${key}`, value),
|
|
259
|
-
this.setToKeychain(key, value)
|
|
260
|
-
]);
|
|
261
|
-
return true;
|
|
262
|
-
} catch {
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
378
|
+
return this.setToKeychain(key, value);
|
|
265
379
|
}
|
|
266
380
|
async remove(key) {
|
|
267
381
|
try {
|
|
268
382
|
await Promise.all([
|
|
269
|
-
|
|
383
|
+
// Also remove the legacy plaintext copy if it exists (migration cleanup).
|
|
384
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0),
|
|
270
385
|
this.removeFromKeychain(key)
|
|
271
386
|
]);
|
|
272
387
|
return true;
|
|
@@ -275,31 +390,25 @@ var SecureStorage = class {
|
|
|
275
390
|
}
|
|
276
391
|
}
|
|
277
392
|
hasKeychainSupport() {
|
|
278
|
-
return
|
|
393
|
+
return nativeStorage.isAvailable();
|
|
279
394
|
}
|
|
280
395
|
async getFromKeychain(key) {
|
|
281
396
|
try {
|
|
282
|
-
|
|
283
|
-
if (result && result.password) {
|
|
284
|
-
return result.password;
|
|
285
|
-
}
|
|
286
|
-
return null;
|
|
397
|
+
return await nativeStorage.secureGet(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
287
398
|
} catch {
|
|
288
399
|
return null;
|
|
289
400
|
}
|
|
290
401
|
}
|
|
291
402
|
async setToKeychain(key, value) {
|
|
292
403
|
try {
|
|
293
|
-
await
|
|
294
|
-
return true;
|
|
404
|
+
return await nativeStorage.secureSet(`${KEYCHAIN_KEY_PREFIX}${key}`, value);
|
|
295
405
|
} catch {
|
|
296
406
|
return false;
|
|
297
407
|
}
|
|
298
408
|
}
|
|
299
409
|
async removeFromKeychain(key) {
|
|
300
410
|
try {
|
|
301
|
-
await
|
|
302
|
-
return true;
|
|
411
|
+
return await nativeStorage.secureRemove(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
303
412
|
} catch {
|
|
304
413
|
return false;
|
|
305
414
|
}
|
|
@@ -307,7 +416,59 @@ var SecureStorage = class {
|
|
|
307
416
|
};
|
|
308
417
|
var secureStorage = new SecureStorage();
|
|
309
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
|
+
|
|
310
470
|
// src/domains/identity/IdentityManager.ts
|
|
471
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
311
472
|
var DEVICE_ID_KEY = "device_id";
|
|
312
473
|
var ANON_ID_KEY = "anon_id";
|
|
313
474
|
var USER_EMAIL_KEY = "user_email";
|
|
@@ -322,18 +483,31 @@ var IdentityManager = class {
|
|
|
322
483
|
this.secureStorage = null;
|
|
323
484
|
this.debug = false;
|
|
324
485
|
this.initialized = false;
|
|
486
|
+
this.initPromise = null;
|
|
325
487
|
}
|
|
326
488
|
async initialize(apiClient, debug = false) {
|
|
327
489
|
if (this.initialized) {
|
|
328
490
|
return this.deviceId ?? "";
|
|
329
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) {
|
|
330
501
|
this.apiClient = apiClient;
|
|
331
502
|
this.debug = debug;
|
|
332
503
|
this.secureStorage = new SecureStorage(debug);
|
|
504
|
+
await runStorageMigrationIfNeeded();
|
|
333
505
|
await this.loadPersistedState();
|
|
334
506
|
if (!this.deviceId) {
|
|
335
507
|
try {
|
|
336
|
-
|
|
508
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
509
|
+
const deviceId = info.deviceId;
|
|
510
|
+
this.deviceId = !deviceId || deviceId === "unknown" ? generateUUID() : deviceId;
|
|
337
511
|
} catch {
|
|
338
512
|
this.deviceId = generateUUID();
|
|
339
513
|
}
|
|
@@ -364,8 +538,13 @@ var IdentityManager = class {
|
|
|
364
538
|
}
|
|
365
539
|
}
|
|
366
540
|
if (email) {
|
|
367
|
-
|
|
368
|
-
|
|
541
|
+
if (!EMAIL_REGEX.test(email)) {
|
|
542
|
+
if (this.debug) console.warn("[Paywallo] Invalid email format, skipping email field");
|
|
543
|
+
email = void 0;
|
|
544
|
+
} else {
|
|
545
|
+
this.email = email;
|
|
546
|
+
await this.secureStorage.set(USER_EMAIL_KEY, email);
|
|
547
|
+
}
|
|
369
548
|
}
|
|
370
549
|
if (properties) {
|
|
371
550
|
this.properties = { ...this.properties, ...properties };
|
|
@@ -469,8 +648,8 @@ var IdentityManager = class {
|
|
|
469
648
|
var identityManager = new IdentityManager();
|
|
470
649
|
|
|
471
650
|
// src/domains/iap/NativeStoreKit.ts
|
|
472
|
-
var PaywalloStoreKitNative =
|
|
473
|
-
function
|
|
651
|
+
var PaywalloStoreKitNative = import_react_native3.NativeModules.PaywalloStoreKit ?? null;
|
|
652
|
+
function isAvailable3() {
|
|
474
653
|
return PaywalloStoreKitNative != null;
|
|
475
654
|
}
|
|
476
655
|
function mapNativeProduct(native) {
|
|
@@ -542,11 +721,11 @@ function mapNativePurchase(native) {
|
|
|
542
721
|
transactionId: native.transactionId,
|
|
543
722
|
transactionDate: native.transactionDate,
|
|
544
723
|
receipt: native.receipt,
|
|
545
|
-
platform:
|
|
724
|
+
platform: import_react_native3.Platform.OS
|
|
546
725
|
};
|
|
547
726
|
}
|
|
548
727
|
var nativeStoreKit = {
|
|
549
|
-
isAvailable,
|
|
728
|
+
isAvailable: isAvailable3,
|
|
550
729
|
async getProducts(productIds) {
|
|
551
730
|
if (!PaywalloStoreKitNative) {
|
|
552
731
|
return [];
|
|
@@ -563,8 +742,8 @@ var nativeStoreKit = {
|
|
|
563
742
|
}
|
|
564
743
|
const distinctId = identityManager.getDistinctId();
|
|
565
744
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
566
|
-
if (distinctId && !uuidRegex.test(distinctId)
|
|
567
|
-
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);
|
|
568
747
|
}
|
|
569
748
|
try {
|
|
570
749
|
const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
|
|
@@ -585,7 +764,7 @@ var nativeStoreKit = {
|
|
|
585
764
|
try {
|
|
586
765
|
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
587
766
|
} catch (err) {
|
|
588
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
767
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction native error", err);
|
|
589
768
|
}
|
|
590
769
|
},
|
|
591
770
|
async getActiveTransactions() {
|
|
@@ -594,7 +773,7 @@ var nativeStoreKit = {
|
|
|
594
773
|
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
595
774
|
return results.map(mapNativePurchase);
|
|
596
775
|
} catch (err) {
|
|
597
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
776
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] getActiveTransactions failed", err);
|
|
598
777
|
return [];
|
|
599
778
|
}
|
|
600
779
|
}
|
|
@@ -605,6 +784,7 @@ var IAPService = class {
|
|
|
605
784
|
constructor(apiClient) {
|
|
606
785
|
this.productsCache = /* @__PURE__ */ new Map();
|
|
607
786
|
this.apiClient = null;
|
|
787
|
+
this.purchaseInFlight = null;
|
|
608
788
|
this.apiClient = apiClient ?? null;
|
|
609
789
|
}
|
|
610
790
|
get debug() {
|
|
@@ -640,7 +820,7 @@ var IAPService = class {
|
|
|
640
820
|
}
|
|
641
821
|
return products;
|
|
642
822
|
} catch (err) {
|
|
643
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
823
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts failed", err);
|
|
644
824
|
return [];
|
|
645
825
|
}
|
|
646
826
|
}
|
|
@@ -665,7 +845,7 @@ var IAPService = class {
|
|
|
665
845
|
async validateWithServer(purchase, productId, options) {
|
|
666
846
|
const apiClient = this.getApiClient();
|
|
667
847
|
const product = this.productsCache.get(productId);
|
|
668
|
-
const platform =
|
|
848
|
+
const platform = import_react_native4.Platform.OS;
|
|
669
849
|
const distinctId = PaywalloClient.getDistinctId();
|
|
670
850
|
return this.validateWithRetry(
|
|
671
851
|
() => apiClient.validatePurchase(
|
|
@@ -686,7 +866,7 @@ var IAPService = class {
|
|
|
686
866
|
try {
|
|
687
867
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
688
868
|
} catch (err) {
|
|
689
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
869
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
690
870
|
}
|
|
691
871
|
}
|
|
692
872
|
async purchase(productId, options) {
|
|
@@ -696,27 +876,36 @@ var IAPService = class {
|
|
|
696
876
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
697
877
|
};
|
|
698
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;
|
|
699
886
|
const debug = PaywalloClient.getConfig()?.debug;
|
|
700
|
-
if (debug) console.
|
|
887
|
+
if (debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
701
888
|
try {
|
|
702
889
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
703
890
|
if (!purchase) {
|
|
704
|
-
if (debug) console.
|
|
891
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
|
|
705
892
|
return { success: false };
|
|
706
893
|
}
|
|
707
|
-
if (debug) console.
|
|
894
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
|
|
708
895
|
await this.finishTransaction(purchase.transactionId);
|
|
709
|
-
if (debug) console.
|
|
896
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
710
897
|
this.validateWithServer(purchase, productId, options).then(() => {
|
|
711
|
-
if (debug) console.
|
|
898
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
712
899
|
}).catch((err) => {
|
|
713
|
-
if (debug) console.
|
|
900
|
+
if (debug) console.error("[Paywallo PURCHASE] background validation failed", err);
|
|
714
901
|
});
|
|
715
902
|
return { success: true, purchase };
|
|
716
903
|
} catch (error) {
|
|
717
|
-
if (debug) console.
|
|
904
|
+
if (debug) console.error(`[Paywallo PURCHASE] ${productId} failed`, error);
|
|
718
905
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
719
906
|
return { success: false, error: e };
|
|
907
|
+
} finally {
|
|
908
|
+
this.purchaseInFlight = null;
|
|
720
909
|
}
|
|
721
910
|
}
|
|
722
911
|
async restore() {
|
|
@@ -727,7 +916,7 @@ var IAPService = class {
|
|
|
727
916
|
const transactions = await nativeStoreKit.getActiveTransactions();
|
|
728
917
|
const apiClient = this.getApiClient();
|
|
729
918
|
const distinctId = PaywalloClient.getDistinctId();
|
|
730
|
-
const platform =
|
|
919
|
+
const platform = import_react_native4.Platform.OS;
|
|
731
920
|
const results = await Promise.allSettled(
|
|
732
921
|
transactions.map((tx) => {
|
|
733
922
|
const product = this.productsCache.get(tx.productId);
|
|
@@ -751,14 +940,14 @@ var IAPService = class {
|
|
|
751
940
|
try {
|
|
752
941
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
753
942
|
} catch (err) {
|
|
754
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
943
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
755
944
|
}
|
|
756
945
|
validated.push(tx);
|
|
757
946
|
}
|
|
758
947
|
}
|
|
759
948
|
return validated;
|
|
760
949
|
} catch (err) {
|
|
761
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
950
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] restore failed", err);
|
|
762
951
|
return [];
|
|
763
952
|
}
|
|
764
953
|
}
|
|
@@ -789,7 +978,7 @@ function usePaywallTracking() {
|
|
|
789
978
|
...sessionId && { sessionId }
|
|
790
979
|
});
|
|
791
980
|
} catch (err) {
|
|
792
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
981
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackDismissed failed", err);
|
|
793
982
|
}
|
|
794
983
|
}, []);
|
|
795
984
|
const trackProductSelected = (0, import_react.useCallback)((opts) => {
|
|
@@ -804,7 +993,7 @@ function usePaywallTracking() {
|
|
|
804
993
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
805
994
|
...sessionId && { sessionId }
|
|
806
995
|
}).catch((err) => {
|
|
807
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
996
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackProductSelected failed", err);
|
|
808
997
|
});
|
|
809
998
|
}, []);
|
|
810
999
|
const trackPurchased = (0, import_react.useCallback)(async (opts) => {
|
|
@@ -821,25 +1010,24 @@ function usePaywallTracking() {
|
|
|
821
1010
|
...sessionId && { sessionId }
|
|
822
1011
|
});
|
|
823
1012
|
} catch (err) {
|
|
824
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
1013
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackPurchased failed", err);
|
|
825
1014
|
}
|
|
826
1015
|
}, []);
|
|
827
1016
|
return { trackDismissed, trackProductSelected, trackPurchased };
|
|
828
1017
|
}
|
|
829
1018
|
|
|
830
1019
|
// src/domains/paywall/usePaywallActions.ts
|
|
831
|
-
var SCREEN_HEIGHT = import_react_native3.Dimensions.get("window").height;
|
|
832
1020
|
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
833
1021
|
const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
|
|
834
1022
|
const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
|
|
835
|
-
const slideAnim = (0, import_react2.useRef)(new
|
|
1023
|
+
const slideAnim = (0, import_react2.useRef)(new import_react_native5.Animated.Value(0)).current;
|
|
836
1024
|
const presentedAtRef = (0, import_react2.useRef)(Date.now());
|
|
837
1025
|
const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
|
|
838
1026
|
const handleClose = (0, import_react2.useCallback)(() => {
|
|
839
1027
|
if (isClosing) return;
|
|
840
1028
|
setIsClosing(true);
|
|
841
1029
|
const animTarget = openAnim ?? slideAnim;
|
|
842
|
-
|
|
1030
|
+
import_react_native5.Animated.timing(animTarget, { toValue: import_react_native5.Dimensions.get("window").height, duration: 350, useNativeDriver: true }).start(() => {
|
|
843
1031
|
if (paywallConfig) {
|
|
844
1032
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
845
1033
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -928,6 +1116,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
928
1116
|
trackingDoneRef.current = false;
|
|
929
1117
|
return;
|
|
930
1118
|
}
|
|
1119
|
+
let cancelled = false;
|
|
931
1120
|
const load = async () => {
|
|
932
1121
|
setError(null);
|
|
933
1122
|
try {
|
|
@@ -948,27 +1137,30 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
948
1137
|
if (!fetched) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_FOUND, `Paywall not found for placement: ${placement}`);
|
|
949
1138
|
config = fetched;
|
|
950
1139
|
}
|
|
1140
|
+
if (cancelled) return;
|
|
951
1141
|
setPaywallConfig(config);
|
|
952
1142
|
const productIds = [];
|
|
953
1143
|
if (config.primaryProductId) productIds.push(config.primaryProductId);
|
|
954
1144
|
if (config.secondaryProductId) productIds.push(config.secondaryProductId);
|
|
955
1145
|
const snapProducts = preloadedProductsRef.current;
|
|
956
1146
|
if (snapProducts && snapProducts.size > 0) {
|
|
957
|
-
setProducts(snapProducts);
|
|
1147
|
+
if (!cancelled) setProducts(snapProducts);
|
|
958
1148
|
} else if (productIds.length > 0) {
|
|
959
1149
|
try {
|
|
960
1150
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1151
|
+
if (!cancelled) {
|
|
1152
|
+
const map = /* @__PURE__ */ new Map();
|
|
1153
|
+
for (const p of storeProducts) map.set(p.productId, p);
|
|
1154
|
+
setProducts(map);
|
|
1155
|
+
}
|
|
964
1156
|
} catch (err) {
|
|
965
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
966
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1157
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts in paywall failed", err);
|
|
1158
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
967
1159
|
}
|
|
968
1160
|
} else {
|
|
969
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1161
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
970
1162
|
}
|
|
971
|
-
if (!trackingDoneRef.current) {
|
|
1163
|
+
if (!trackingDoneRef.current && !cancelled) {
|
|
972
1164
|
trackingDoneRef.current = true;
|
|
973
1165
|
const sessionId = PaywalloClient.getSessionId();
|
|
974
1166
|
await apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
|
|
@@ -980,17 +1172,21 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
980
1172
|
});
|
|
981
1173
|
}
|
|
982
1174
|
} catch (err) {
|
|
1175
|
+
if (cancelled) return;
|
|
983
1176
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
984
1177
|
setError(e);
|
|
985
1178
|
}
|
|
986
1179
|
};
|
|
987
1180
|
void load();
|
|
1181
|
+
return () => {
|
|
1182
|
+
cancelled = true;
|
|
1183
|
+
};
|
|
988
1184
|
}, [placement, visible, variantKey, campaignId]);
|
|
989
1185
|
return { paywallConfig, products, error };
|
|
990
1186
|
}
|
|
991
1187
|
|
|
992
1188
|
// src/utils/localization.ts
|
|
993
|
-
var
|
|
1189
|
+
var import_react_native6 = require("react-native");
|
|
994
1190
|
var currentLanguage = "pt-BR";
|
|
995
1191
|
var defaultLanguage = "pt-BR";
|
|
996
1192
|
function setCurrentLanguage(language) {
|
|
@@ -1012,11 +1208,11 @@ function getLocalizedString(text) {
|
|
|
1012
1208
|
}
|
|
1013
1209
|
function detectDeviceLanguage() {
|
|
1014
1210
|
try {
|
|
1015
|
-
if (
|
|
1016
|
-
const settings =
|
|
1211
|
+
if (import_react_native6.Platform.OS === "ios") {
|
|
1212
|
+
const settings = import_react_native6.NativeModules.SettingsManager?.settings;
|
|
1017
1213
|
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
1018
1214
|
}
|
|
1019
|
-
return
|
|
1215
|
+
return import_react_native6.NativeModules.I18nManager?.localeIdentifier ?? "en-US";
|
|
1020
1216
|
} catch {
|
|
1021
1217
|
return "en-US";
|
|
1022
1218
|
}
|
|
@@ -1052,9 +1248,8 @@ var SDK_STRINGS = {
|
|
|
1052
1248
|
};
|
|
1053
1249
|
function getSdkString(key) {
|
|
1054
1250
|
const map = SDK_STRINGS[key];
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const base = lang.split("-")[0];
|
|
1251
|
+
if (currentLanguage in map) return map[currentLanguage];
|
|
1252
|
+
const base = currentLanguage.split("-")[0];
|
|
1058
1253
|
if (base && base in map) return map[base];
|
|
1059
1254
|
if (defaultLanguage in map) return map[defaultLanguage];
|
|
1060
1255
|
return Object.values(map)[0];
|
|
@@ -1071,9 +1266,17 @@ function initLocalization(options) {
|
|
|
1071
1266
|
|
|
1072
1267
|
// src/domains/paywall/PaywallWebView.tsx
|
|
1073
1268
|
var import_react4 = require("react");
|
|
1074
|
-
var
|
|
1269
|
+
var import_react_native7 = require("react-native");
|
|
1075
1270
|
|
|
1076
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
|
+
]);
|
|
1077
1280
|
function deriveMessageId(message) {
|
|
1078
1281
|
if (message.id) return message.id;
|
|
1079
1282
|
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
@@ -1082,10 +1285,20 @@ function deriveMessageId(message) {
|
|
|
1082
1285
|
function parseWebViewMessage(raw) {
|
|
1083
1286
|
try {
|
|
1084
1287
|
const parsed = JSON.parse(raw);
|
|
1085
|
-
if (typeof parsed?.type !== "string") {
|
|
1288
|
+
if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
|
|
1086
1289
|
return null;
|
|
1087
1290
|
}
|
|
1088
|
-
|
|
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
|
+
};
|
|
1089
1302
|
} catch {
|
|
1090
1303
|
return null;
|
|
1091
1304
|
}
|
|
@@ -1113,14 +1326,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
1113
1326
|
var NativeWebViewComponent = null;
|
|
1114
1327
|
function getNativeWebView() {
|
|
1115
1328
|
if (!NativeWebViewComponent) {
|
|
1116
|
-
NativeWebViewComponent = (0,
|
|
1329
|
+
NativeWebViewComponent = (0, import_react_native7.requireNativeComponent)("PaywalloWebView");
|
|
1117
1330
|
}
|
|
1118
1331
|
return NativeWebViewComponent;
|
|
1119
1332
|
}
|
|
1120
1333
|
var webViewEmitter = null;
|
|
1121
1334
|
function getWebViewEmitter() {
|
|
1122
1335
|
if (!webViewEmitter) {
|
|
1123
|
-
webViewEmitter = new
|
|
1336
|
+
webViewEmitter = new import_react_native7.NativeEventEmitter(import_react_native7.NativeModules.PaywalloWebViewModule);
|
|
1124
1337
|
}
|
|
1125
1338
|
return webViewEmitter;
|
|
1126
1339
|
}
|
|
@@ -1195,12 +1408,24 @@ function PaywallWebView({
|
|
|
1195
1408
|
setScriptToInject(script);
|
|
1196
1409
|
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1197
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
|
+
}, []);
|
|
1198
1419
|
const handleParsedMessage = (0, import_react4.useCallback)(
|
|
1199
1420
|
(message) => {
|
|
1200
1421
|
const msgId = deriveMessageId(message);
|
|
1201
1422
|
if (processedIdsRef.current.has(msgId)) return;
|
|
1202
1423
|
processedIdsRef.current.add(msgId);
|
|
1203
|
-
setTimeout(() =>
|
|
1424
|
+
const timerId = setTimeout(() => {
|
|
1425
|
+
processedIdsRef.current.delete(msgId);
|
|
1426
|
+
dedupeTimersRef.current.delete(msgId);
|
|
1427
|
+
}, 5e3);
|
|
1428
|
+
dedupeTimersRef.current.set(msgId, timerId);
|
|
1204
1429
|
switch (message.type) {
|
|
1205
1430
|
case "ready":
|
|
1206
1431
|
if (!paywallDataSent.current) {
|
|
@@ -1221,8 +1446,8 @@ function PaywallWebView({
|
|
|
1221
1446
|
case "open-url":
|
|
1222
1447
|
if (message.payload?.url) {
|
|
1223
1448
|
const url = message.payload.url;
|
|
1224
|
-
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
|
|
1225
|
-
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);
|
|
1226
1451
|
}
|
|
1227
1452
|
}
|
|
1228
1453
|
break;
|
|
@@ -1300,7 +1525,7 @@ function PaywallWebView({
|
|
|
1300
1525
|
}, [isPurchasing]);
|
|
1301
1526
|
if (!resolvedWebUrl) return null;
|
|
1302
1527
|
const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
|
|
1303
|
-
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)(
|
|
1304
1529
|
NativeWebView,
|
|
1305
1530
|
{
|
|
1306
1531
|
sourceUrl: paywallUrl,
|
|
@@ -1312,24 +1537,23 @@ function PaywallWebView({
|
|
|
1312
1537
|
}
|
|
1313
1538
|
) });
|
|
1314
1539
|
}
|
|
1315
|
-
var styles =
|
|
1540
|
+
var styles = import_react_native7.StyleSheet.create({
|
|
1316
1541
|
container: { flex: 1 },
|
|
1317
1542
|
webview: { flex: 1, backgroundColor: "transparent" }
|
|
1318
1543
|
});
|
|
1319
1544
|
|
|
1320
1545
|
// src/domains/paywall/PaywallModal.tsx
|
|
1321
1546
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1322
|
-
var SCREEN_HEIGHT2 = import_react_native6.Dimensions.get("window").height;
|
|
1323
1547
|
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1324
1548
|
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1325
1549
|
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
1326
1550
|
const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
|
|
1327
1551
|
const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
|
|
1328
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1329
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1330
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1331
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1332
|
-
/* @__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 }) })
|
|
1333
1557
|
] }) });
|
|
1334
1558
|
}
|
|
1335
1559
|
function PaywallModal({
|
|
@@ -1349,7 +1573,8 @@ function PaywallModal({
|
|
|
1349
1573
|
variantKey,
|
|
1350
1574
|
campaignId
|
|
1351
1575
|
);
|
|
1352
|
-
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;
|
|
1353
1578
|
const [modalVisible, setModalVisible] = (0, import_react5.useState)(false);
|
|
1354
1579
|
const handleResult = (0, import_react5.useCallback)((result) => {
|
|
1355
1580
|
setModalVisible(false);
|
|
@@ -1378,18 +1603,18 @@ function PaywallModal({
|
|
|
1378
1603
|
(0, import_react5.useEffect)(() => {
|
|
1379
1604
|
if (visible) {
|
|
1380
1605
|
setModalVisible(true);
|
|
1381
|
-
openAnim.setValue(
|
|
1382
|
-
|
|
1606
|
+
openAnim.setValue(import_react_native8.Dimensions.get("window").height);
|
|
1607
|
+
import_react_native8.Animated.timing(openAnim, {
|
|
1383
1608
|
toValue: 0,
|
|
1384
1609
|
duration: 550,
|
|
1385
|
-
easing:
|
|
1610
|
+
easing: import_react_native8.Easing.out(import_react_native8.Easing.cubic),
|
|
1386
1611
|
useNativeDriver: true
|
|
1387
1612
|
}).start();
|
|
1388
1613
|
}
|
|
1389
1614
|
}, [visible, openAnim]);
|
|
1390
1615
|
if (!modalVisible && !visible) return null;
|
|
1391
1616
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1392
|
-
|
|
1617
|
+
import_react_native8.Modal,
|
|
1393
1618
|
{
|
|
1394
1619
|
visible: modalVisible,
|
|
1395
1620
|
animationType: "none",
|
|
@@ -1397,8 +1622,8 @@ function PaywallModal({
|
|
|
1397
1622
|
onRequestClose: handleClose,
|
|
1398
1623
|
transparent: true,
|
|
1399
1624
|
statusBarTranslucent: true,
|
|
1400
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1401
|
-
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 }) }),
|
|
1402
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)(
|
|
1403
1628
|
ErrorFallback,
|
|
1404
1629
|
{
|
|
@@ -1426,7 +1651,7 @@ function PaywallModal({
|
|
|
1426
1651
|
}
|
|
1427
1652
|
);
|
|
1428
1653
|
}
|
|
1429
|
-
var styles2 =
|
|
1654
|
+
var styles2 = import_react_native8.StyleSheet.create({
|
|
1430
1655
|
overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
|
|
1431
1656
|
animatedContainer: { flex: 1, backgroundColor: "#fff" },
|
|
1432
1657
|
container: { flex: 1, backgroundColor: "#fff" },
|
|
@@ -1528,6 +1753,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1528
1753
|
resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
|
|
1529
1754
|
resolverRef.current = null;
|
|
1530
1755
|
}
|
|
1756
|
+
setShowingPreloadedWebView(false);
|
|
1531
1757
|
};
|
|
1532
1758
|
}, []);
|
|
1533
1759
|
const presentPreloadedWebView = (0, import_react8.useCallback)(() => {
|
|
@@ -1572,230 +1798,10 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1572
1798
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1573
1799
|
}
|
|
1574
1800
|
|
|
1575
|
-
// src/domains/affiliate/AffiliateError.ts
|
|
1576
|
-
var AffiliateError = class extends PaywalloError {
|
|
1577
|
-
constructor(code, message) {
|
|
1578
|
-
super("affiliate", code, message);
|
|
1579
|
-
this.name = "AffiliateError";
|
|
1580
|
-
}
|
|
1581
|
-
};
|
|
1582
|
-
var AFFILIATE_ERROR_CODES = {
|
|
1583
|
-
NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
|
|
1584
|
-
COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
|
|
1585
|
-
COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
|
|
1586
|
-
BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
|
|
1587
|
-
WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
|
|
1588
|
-
NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
|
|
1589
|
-
};
|
|
1590
|
-
|
|
1591
|
-
// src/domains/affiliate/affiliateHttp.ts
|
|
1592
|
-
function buildQueryString(params) {
|
|
1593
|
-
if (!params) return "";
|
|
1594
|
-
const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
|
|
1595
|
-
if (entries.length === 0) return "";
|
|
1596
|
-
return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
|
|
1597
|
-
}
|
|
1598
|
-
async function affiliateGet(apiClient, path, params) {
|
|
1599
|
-
const response = await apiClient.get(`${path}${buildQueryString(params)}`);
|
|
1600
|
-
return response.data;
|
|
1601
|
-
}
|
|
1602
|
-
async function affiliatePost(apiClient, path, body) {
|
|
1603
|
-
const response = await apiClient.post(path, body);
|
|
1604
|
-
return response.data;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
// src/domains/affiliate/affiliateDateMappers.ts
|
|
1608
|
-
function mapRawCoupon(raw) {
|
|
1609
|
-
return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
|
|
1610
|
-
}
|
|
1611
|
-
function mapRawCommission(raw) {
|
|
1612
|
-
return { ...raw, createdAt: new Date(raw.createdAt) };
|
|
1613
|
-
}
|
|
1614
|
-
function mapRawWithdrawal(raw) {
|
|
1615
|
-
return { ...raw, createdAt: new Date(raw.createdAt) };
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// src/domains/affiliate/AffiliateManager.ts
|
|
1619
|
-
var AffiliateManager = class {
|
|
1620
|
-
constructor() {
|
|
1621
|
-
this.apiClient = null;
|
|
1622
|
-
this.debug = false;
|
|
1623
|
-
}
|
|
1624
|
-
initialize(apiClient, debug = false) {
|
|
1625
|
-
this.apiClient = apiClient;
|
|
1626
|
-
this.debug = debug;
|
|
1627
|
-
this.log("AffiliateManager initialized");
|
|
1628
|
-
}
|
|
1629
|
-
async registerCoupon(code) {
|
|
1630
|
-
this.ensureInitialized();
|
|
1631
|
-
const distinctId = identityManager.getDistinctId();
|
|
1632
|
-
try {
|
|
1633
|
-
const response = await this.post("/api/v1/affiliate/register-coupon", {
|
|
1634
|
-
code,
|
|
1635
|
-
distinctId
|
|
1636
|
-
});
|
|
1637
|
-
if (response.success && response.coupon) {
|
|
1638
|
-
this.log("Coupon registered:", code);
|
|
1639
|
-
return { success: true, coupon: mapRawCoupon(response.coupon) };
|
|
1640
|
-
}
|
|
1641
|
-
return {
|
|
1642
|
-
success: false,
|
|
1643
|
-
error: response.error
|
|
1644
|
-
};
|
|
1645
|
-
} catch (error) {
|
|
1646
|
-
this.log("Failed to register coupon:", error);
|
|
1647
|
-
return {
|
|
1648
|
-
success: false,
|
|
1649
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
async applyCoupon(code) {
|
|
1654
|
-
this.ensureInitialized();
|
|
1655
|
-
const distinctId = identityManager.getDistinctId();
|
|
1656
|
-
try {
|
|
1657
|
-
const response = await this.post("/api/v1/affiliate/apply-coupon", {
|
|
1658
|
-
code,
|
|
1659
|
-
distinctId
|
|
1660
|
-
});
|
|
1661
|
-
if (response.success) {
|
|
1662
|
-
this.log("Coupon applied:", code);
|
|
1663
|
-
}
|
|
1664
|
-
return response;
|
|
1665
|
-
} catch (error) {
|
|
1666
|
-
this.log("Failed to apply coupon:", error);
|
|
1667
|
-
return {
|
|
1668
|
-
success: false,
|
|
1669
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1670
|
-
};
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
async getMyCoupon() {
|
|
1674
|
-
this.ensureInitialized();
|
|
1675
|
-
const distinctId = identityManager.getDistinctId();
|
|
1676
|
-
try {
|
|
1677
|
-
const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
|
|
1678
|
-
if (response.coupon) {
|
|
1679
|
-
return { coupon: mapRawCoupon(response.coupon) };
|
|
1680
|
-
}
|
|
1681
|
-
return { coupon: null };
|
|
1682
|
-
} catch (error) {
|
|
1683
|
-
this.log("Failed to get my coupon:", error);
|
|
1684
|
-
return { coupon: null };
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
async getAppliedCoupon() {
|
|
1688
|
-
this.ensureInitialized();
|
|
1689
|
-
const distinctId = identityManager.getDistinctId();
|
|
1690
|
-
try {
|
|
1691
|
-
const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
|
|
1692
|
-
return response;
|
|
1693
|
-
} catch (error) {
|
|
1694
|
-
this.log("Failed to get applied coupon:", error);
|
|
1695
|
-
return { coupon: null };
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
async getBalance() {
|
|
1699
|
-
this.ensureInitialized();
|
|
1700
|
-
const distinctId = identityManager.getDistinctId();
|
|
1701
|
-
try {
|
|
1702
|
-
const response = await this.get("/api/v1/affiliate/balance", {
|
|
1703
|
-
distinctId
|
|
1704
|
-
});
|
|
1705
|
-
return response;
|
|
1706
|
-
} catch (error) {
|
|
1707
|
-
this.log("Failed to get balance:", error);
|
|
1708
|
-
return {
|
|
1709
|
-
total: 0,
|
|
1710
|
-
pending: 0,
|
|
1711
|
-
available: 0,
|
|
1712
|
-
withdrawn: 0
|
|
1713
|
-
};
|
|
1714
|
-
}
|
|
1715
|
-
}
|
|
1716
|
-
async getCommissions() {
|
|
1717
|
-
this.ensureInitialized();
|
|
1718
|
-
const distinctId = identityManager.getDistinctId();
|
|
1719
|
-
try {
|
|
1720
|
-
const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
|
|
1721
|
-
return response.commissions.map(mapRawCommission);
|
|
1722
|
-
} catch (error) {
|
|
1723
|
-
this.log("Failed to get commissions:", error);
|
|
1724
|
-
return [];
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
async requestWithdrawal(amount) {
|
|
1728
|
-
this.ensureInitialized();
|
|
1729
|
-
const distinctId = identityManager.getDistinctId();
|
|
1730
|
-
try {
|
|
1731
|
-
const response = await this.post("/api/v1/affiliate/withdraw", {
|
|
1732
|
-
amount,
|
|
1733
|
-
distinctId
|
|
1734
|
-
});
|
|
1735
|
-
if (response.success && response.withdrawal) {
|
|
1736
|
-
this.log("Withdrawal requested:", amount);
|
|
1737
|
-
return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
|
|
1738
|
-
}
|
|
1739
|
-
return {
|
|
1740
|
-
success: false,
|
|
1741
|
-
error: response.error
|
|
1742
|
-
};
|
|
1743
|
-
} catch (error) {
|
|
1744
|
-
this.log("Failed to request withdrawal:", error);
|
|
1745
|
-
return {
|
|
1746
|
-
success: false,
|
|
1747
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
}
|
|
1751
|
-
async getWithdrawals() {
|
|
1752
|
-
this.ensureInitialized();
|
|
1753
|
-
const distinctId = identityManager.getDistinctId();
|
|
1754
|
-
try {
|
|
1755
|
-
const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
|
|
1756
|
-
return response.withdrawals.map(mapRawWithdrawal);
|
|
1757
|
-
} catch (error) {
|
|
1758
|
-
this.log("Failed to get withdrawals:", error);
|
|
1759
|
-
return [];
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
async get(path, params) {
|
|
1763
|
-
this.ensureInitialized();
|
|
1764
|
-
return affiliateGet(this.apiClient, path, params);
|
|
1765
|
-
}
|
|
1766
|
-
async post(path, body) {
|
|
1767
|
-
this.ensureInitialized();
|
|
1768
|
-
return affiliatePost(this.apiClient, path, body);
|
|
1769
|
-
}
|
|
1770
|
-
ensureInitialized() {
|
|
1771
|
-
if (!this.apiClient) {
|
|
1772
|
-
throw new AffiliateError(
|
|
1773
|
-
AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
|
|
1774
|
-
"AffiliateManager not initialized. Call initialize() first"
|
|
1775
|
-
);
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
log(...args) {
|
|
1779
|
-
if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
|
|
1780
|
-
}
|
|
1781
|
-
};
|
|
1782
|
-
var affiliateManager = new AffiliateManager();
|
|
1783
|
-
|
|
1784
|
-
// src/domains/affiliate/PaywalloAffiliate.ts
|
|
1785
|
-
var PaywalloAffiliate = {
|
|
1786
|
-
registerCoupon: (code) => affiliateManager.registerCoupon(code),
|
|
1787
|
-
applyCoupon: (code) => affiliateManager.applyCoupon(code),
|
|
1788
|
-
getMyCoupon: () => affiliateManager.getMyCoupon(),
|
|
1789
|
-
getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
|
|
1790
|
-
getBalance: () => affiliateManager.getBalance(),
|
|
1791
|
-
getCommissions: () => affiliateManager.getCommissions(),
|
|
1792
|
-
requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
|
|
1793
|
-
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1794
|
-
};
|
|
1795
|
-
|
|
1796
1801
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1797
|
-
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1802
|
+
var LEGACY_CACHE_KEY = "@panel:subscription_cache";
|
|
1798
1803
|
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1804
|
+
var CACHE_INDEX_KEY = "subscription_cache:__index__";
|
|
1799
1805
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1800
1806
|
function keyFor(distinctId) {
|
|
1801
1807
|
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
@@ -1875,7 +1881,28 @@ var SubscriptionCache = class {
|
|
|
1875
1881
|
this.memoryCache.set(distinctId, cached);
|
|
1876
1882
|
try {
|
|
1877
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
|
+
}
|
|
1896
|
+
} catch {
|
|
1897
|
+
}
|
|
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);
|
|
1878
1904
|
} catch {
|
|
1905
|
+
return [];
|
|
1879
1906
|
}
|
|
1880
1907
|
}
|
|
1881
1908
|
async invalidate(distinctId) {
|
|
@@ -1887,11 +1914,15 @@ var SubscriptionCache = class {
|
|
|
1887
1914
|
}
|
|
1888
1915
|
}
|
|
1889
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]));
|
|
1890
1920
|
this.memoryCache.clear();
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
+
]);
|
|
1895
1926
|
}
|
|
1896
1927
|
isExpired(cached) {
|
|
1897
1928
|
return Date.now() - cached.cachedAt > this.ttl;
|
|
@@ -1902,9 +1933,6 @@ var SubscriptionCache = class {
|
|
|
1902
1933
|
};
|
|
1903
1934
|
var subscriptionCache = new SubscriptionCache();
|
|
1904
1935
|
|
|
1905
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
1906
|
-
var import_react_native7 = require("react-native");
|
|
1907
|
-
|
|
1908
1936
|
// src/domains/session/SessionError.ts
|
|
1909
1937
|
var SessionError = class extends PaywalloError {
|
|
1910
1938
|
constructor(code, message) {
|
|
@@ -1992,11 +2020,10 @@ var SubscriptionManagerClass = class {
|
|
|
1992
2020
|
if (!this.config) {
|
|
1993
2021
|
return this.getEmptyStatus();
|
|
1994
2022
|
}
|
|
1995
|
-
const url = new URL(`${this.config.serverUrl}/
|
|
2023
|
+
const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
|
|
1996
2024
|
if (this.userId) {
|
|
1997
|
-
url.searchParams.set("
|
|
2025
|
+
url.searchParams.set("distinctId", this.userId);
|
|
1998
2026
|
}
|
|
1999
|
-
url.searchParams.set("platform", import_react_native7.Platform.OS);
|
|
2000
2027
|
const response = await fetch(url.toString(), {
|
|
2001
2028
|
method: "GET",
|
|
2002
2029
|
headers: {
|
|
@@ -2051,7 +2078,7 @@ var SubscriptionManagerClass = class {
|
|
|
2051
2078
|
var subscriptionManager = new SubscriptionManagerClass();
|
|
2052
2079
|
|
|
2053
2080
|
// src/core/ApiClient.ts
|
|
2054
|
-
var
|
|
2081
|
+
var import_react_native11 = require("react-native");
|
|
2055
2082
|
|
|
2056
2083
|
// src/core/apiUrlUtils.ts
|
|
2057
2084
|
var DEFAULT_WEB_URL = "https://paywallo.com.br";
|
|
@@ -2099,7 +2126,7 @@ async function validatePurchase(client, appKey, platform, receipt, productId, tr
|
|
|
2099
2126
|
false
|
|
2100
2127
|
);
|
|
2101
2128
|
if (!response.ok) {
|
|
2102
|
-
throw new
|
|
2129
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, `Server validation failed: HTTP ${response.status}`);
|
|
2103
2130
|
}
|
|
2104
2131
|
return response.data;
|
|
2105
2132
|
}
|
|
@@ -2108,7 +2135,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
|
2108
2135
|
url.searchParams.set("distinctId", distinctId);
|
|
2109
2136
|
const response = await client.get(url.toString());
|
|
2110
2137
|
if (!response.ok) {
|
|
2111
|
-
throw new
|
|
2138
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.NETWORK_ERROR, `Subscription status check failed: HTTP ${response.status}`);
|
|
2112
2139
|
}
|
|
2113
2140
|
return response.data;
|
|
2114
2141
|
}
|
|
@@ -2150,7 +2177,7 @@ var HttpClient = class {
|
|
|
2150
2177
|
constructor(baseUrl, config) {
|
|
2151
2178
|
this.config = {
|
|
2152
2179
|
baseUrl,
|
|
2153
|
-
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
|
|
2180
|
+
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout ?? 1e4,
|
|
2154
2181
|
retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
|
|
2155
2182
|
debug: config?.debug ?? false
|
|
2156
2183
|
};
|
|
@@ -2180,7 +2207,7 @@ var HttpClient = class {
|
|
|
2180
2207
|
if (this.shouldRetry(response.status, retryConfig)) {
|
|
2181
2208
|
if (state.attempt < retryConfig.maxRetries) {
|
|
2182
2209
|
state.attempt++;
|
|
2183
|
-
const delay = this.
|
|
2210
|
+
const delay = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
|
|
2184
2211
|
await this.sleep(delay);
|
|
2185
2212
|
continue;
|
|
2186
2213
|
}
|
|
@@ -2234,7 +2261,7 @@ var HttpClient = class {
|
|
|
2234
2261
|
};
|
|
2235
2262
|
} catch (error) {
|
|
2236
2263
|
this.log("Request failed", {
|
|
2237
|
-
url,
|
|
2264
|
+
url: this.redactUrl(url),
|
|
2238
2265
|
error: error instanceof Error ? error.message : String(error)
|
|
2239
2266
|
});
|
|
2240
2267
|
throw error;
|
|
@@ -2247,6 +2274,14 @@ var HttpClient = class {
|
|
|
2247
2274
|
}
|
|
2248
2275
|
buildUrl(path) {
|
|
2249
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
|
+
}
|
|
2250
2285
|
return path;
|
|
2251
2286
|
}
|
|
2252
2287
|
return `${this.config.baseUrl}${path}`;
|
|
@@ -2286,9 +2321,28 @@ var HttpClient = class {
|
|
|
2286
2321
|
const jitter = Math.random() * 0.3 * exponentialDelay;
|
|
2287
2322
|
return Math.min(exponentialDelay + jitter, config.maxDelay);
|
|
2288
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
|
+
}
|
|
2289
2339
|
sleep(ms) {
|
|
2290
2340
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2291
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
|
+
}
|
|
2292
2346
|
log(...args) {
|
|
2293
2347
|
if (this.config.debug) console.log("[Paywallo:Http]", ...args);
|
|
2294
2348
|
}
|
|
@@ -2301,7 +2355,7 @@ var HttpClient = class {
|
|
|
2301
2355
|
};
|
|
2302
2356
|
|
|
2303
2357
|
// src/core/network/NetworkMonitor.ts
|
|
2304
|
-
var
|
|
2358
|
+
var import_react_native9 = require("react-native");
|
|
2305
2359
|
|
|
2306
2360
|
// src/core/network/types.ts
|
|
2307
2361
|
var DEFAULT_NETWORK_CONFIG = {
|
|
@@ -2338,9 +2392,19 @@ var NetworkMonitorClass = class {
|
|
|
2338
2392
|
}, this.config.checkInterval);
|
|
2339
2393
|
}
|
|
2340
2394
|
setupAppStateListener() {
|
|
2341
|
-
this.appStateSubscription =
|
|
2395
|
+
this.appStateSubscription = import_react_native9.AppState.addEventListener("change", (state) => {
|
|
2342
2396
|
if (state === "active") {
|
|
2397
|
+
if (!this.checkIntervalId) {
|
|
2398
|
+
this.checkIntervalId = setInterval(() => {
|
|
2399
|
+
void this.checkConnectivity();
|
|
2400
|
+
}, this.config.checkInterval);
|
|
2401
|
+
}
|
|
2343
2402
|
void this.checkConnectivity();
|
|
2403
|
+
} else if (state === "background" || state === "inactive") {
|
|
2404
|
+
if (this.checkIntervalId) {
|
|
2405
|
+
clearInterval(this.checkIntervalId);
|
|
2406
|
+
this.checkIntervalId = null;
|
|
2407
|
+
}
|
|
2344
2408
|
}
|
|
2345
2409
|
});
|
|
2346
2410
|
}
|
|
@@ -2422,7 +2486,6 @@ var NetworkMonitorClass = class {
|
|
|
2422
2486
|
var networkMonitor = new NetworkMonitorClass();
|
|
2423
2487
|
|
|
2424
2488
|
// src/core/queue/OfflineQueue.ts
|
|
2425
|
-
var import_async_storage2 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
2426
2489
|
init_uuid();
|
|
2427
2490
|
|
|
2428
2491
|
// src/core/queue/deduplication.ts
|
|
@@ -2484,7 +2547,7 @@ var OfflineQueueClass = class {
|
|
|
2484
2547
|
}
|
|
2485
2548
|
async loadFromStorage() {
|
|
2486
2549
|
try {
|
|
2487
|
-
const stored = await
|
|
2550
|
+
const stored = await nativeStorage.get(this.config.storageKey);
|
|
2488
2551
|
if (stored) {
|
|
2489
2552
|
this.queue = JSON.parse(stored);
|
|
2490
2553
|
}
|
|
@@ -2494,7 +2557,7 @@ var OfflineQueueClass = class {
|
|
|
2494
2557
|
}
|
|
2495
2558
|
async saveToStorage() {
|
|
2496
2559
|
try {
|
|
2497
|
-
await
|
|
2560
|
+
await nativeStorage.set(this.config.storageKey, JSON.stringify(this.queue));
|
|
2498
2561
|
return true;
|
|
2499
2562
|
} catch (error) {
|
|
2500
2563
|
this.log("Failed to save queue to storage", error);
|
|
@@ -2509,7 +2572,7 @@ var OfflineQueueClass = class {
|
|
|
2509
2572
|
cleanupExpired() {
|
|
2510
2573
|
const now = Date.now();
|
|
2511
2574
|
const before = this.queue.length;
|
|
2512
|
-
this.queue = this.queue.filter((item) =>
|
|
2575
|
+
this.queue = this.queue.filter((item) => now - item.timestamp < this.config.maxAge);
|
|
2513
2576
|
if (before !== this.queue.length) {
|
|
2514
2577
|
void this.saveToStorage();
|
|
2515
2578
|
}
|
|
@@ -2532,10 +2595,16 @@ var OfflineQueueClass = class {
|
|
|
2532
2595
|
return this.queue[existingIndex];
|
|
2533
2596
|
}
|
|
2534
2597
|
if (this.queue.length >= this.config.maxItems) {
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
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
|
+
}
|
|
2538
2605
|
}
|
|
2606
|
+
const [removed] = this.queue.splice(evictIndex, 1);
|
|
2607
|
+
this.emit({ type: "item:evicted", item: removed, queueSize: this.queue.length });
|
|
2539
2608
|
}
|
|
2540
2609
|
this.queue.push(item);
|
|
2541
2610
|
await this.saveToStorage();
|
|
@@ -2734,9 +2803,78 @@ var QueueProcessorClass = class {
|
|
|
2734
2803
|
this.log("Offline, skipping queue processing");
|
|
2735
2804
|
return { processed: 0, failed: 0 };
|
|
2736
2805
|
}
|
|
2737
|
-
|
|
2806
|
+
const allItems = offlineQueue.getQueue();
|
|
2807
|
+
const eventItems = allItems.filter(
|
|
2808
|
+
(i) => i.url.includes("/api/v1/events") && !i.url.includes("/batch")
|
|
2809
|
+
);
|
|
2810
|
+
const eventItemIds = new Set(eventItems.map((i) => i.id));
|
|
2811
|
+
const { processed: batchProcessed, failed: batchFailed, retryIds } = await this.processEventBatches(eventItems);
|
|
2812
|
+
const batchResult = { processed: batchProcessed, failed: batchFailed };
|
|
2813
|
+
const individualResult = await offlineQueue.processQueue(async (item) => {
|
|
2814
|
+
if (eventItemIds.has(item.id)) {
|
|
2815
|
+
if (retryIds.has(item.id)) {
|
|
2816
|
+
return { success: false, permanent: false };
|
|
2817
|
+
}
|
|
2818
|
+
return { success: true, permanent: false };
|
|
2819
|
+
}
|
|
2738
2820
|
return this.processItem(item);
|
|
2739
2821
|
});
|
|
2822
|
+
this.log("Queue processing complete", {
|
|
2823
|
+
batchProcessed: batchResult.processed,
|
|
2824
|
+
batchFailed: batchResult.failed,
|
|
2825
|
+
individualProcessed: individualResult.processed,
|
|
2826
|
+
individualFailed: individualResult.failed
|
|
2827
|
+
});
|
|
2828
|
+
return {
|
|
2829
|
+
processed: batchResult.processed + individualResult.processed,
|
|
2830
|
+
failed: batchResult.failed + individualResult.failed
|
|
2831
|
+
};
|
|
2832
|
+
}
|
|
2833
|
+
async processEventBatches(eventItems) {
|
|
2834
|
+
if (eventItems.length === 0 || !this.httpClient) {
|
|
2835
|
+
return { processed: 0, failed: 0, retryIds: /* @__PURE__ */ new Set() };
|
|
2836
|
+
}
|
|
2837
|
+
const BATCH_SIZE = 50;
|
|
2838
|
+
let processed = 0;
|
|
2839
|
+
let failed = 0;
|
|
2840
|
+
const retryIds = /* @__PURE__ */ new Set();
|
|
2841
|
+
for (let i = 0; i < eventItems.length; i += BATCH_SIZE) {
|
|
2842
|
+
const batch = eventItems.slice(i, i + BATCH_SIZE);
|
|
2843
|
+
const bodies = batch.map((item) => item.body);
|
|
2844
|
+
const headers = batch[0].headers;
|
|
2845
|
+
this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
|
|
2846
|
+
const response = await this.httpClient.request("/api/v1/events/batch", {
|
|
2847
|
+
method: "POST",
|
|
2848
|
+
body: { events: bodies },
|
|
2849
|
+
headers,
|
|
2850
|
+
skipRetry: false
|
|
2851
|
+
});
|
|
2852
|
+
if (response.ok) {
|
|
2853
|
+
for (const item of batch) {
|
|
2854
|
+
await offlineQueue.removeItem(item.id);
|
|
2855
|
+
processed++;
|
|
2856
|
+
}
|
|
2857
|
+
this.log("Event batch processed successfully", { size: batch.length });
|
|
2858
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
2859
|
+
for (const item of batch) {
|
|
2860
|
+
await offlineQueue.removeItem(item.id);
|
|
2861
|
+
failed++;
|
|
2862
|
+
}
|
|
2863
|
+
this.log("Event batch failed permanently (4xx) - removing from queue", {
|
|
2864
|
+
status: response.status,
|
|
2865
|
+
size: batch.length
|
|
2866
|
+
});
|
|
2867
|
+
} else {
|
|
2868
|
+
for (const item of batch) {
|
|
2869
|
+
retryIds.add(item.id);
|
|
2870
|
+
}
|
|
2871
|
+
this.log("Event batch failed with server error - will retry", {
|
|
2872
|
+
status: response.status,
|
|
2873
|
+
size: batch.length
|
|
2874
|
+
});
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
return { processed, failed, retryIds };
|
|
2740
2878
|
}
|
|
2741
2879
|
async processItem(item) {
|
|
2742
2880
|
if (!this.httpClient) {
|
|
@@ -2746,7 +2884,6 @@ var QueueProcessorClass = class {
|
|
|
2746
2884
|
id: item.id,
|
|
2747
2885
|
url: item.url,
|
|
2748
2886
|
method: item.method,
|
|
2749
|
-
headers: item.headers,
|
|
2750
2887
|
attempts: item.attempts
|
|
2751
2888
|
});
|
|
2752
2889
|
const response = await this.httpClient.request(item.url, {
|
|
@@ -2764,7 +2901,7 @@ var QueueProcessorClass = class {
|
|
|
2764
2901
|
id: item.id,
|
|
2765
2902
|
url: item.url,
|
|
2766
2903
|
status: response.status,
|
|
2767
|
-
|
|
2904
|
+
hasAppKey: item.headers?.["X-App-Key"] != null
|
|
2768
2905
|
});
|
|
2769
2906
|
return { success: false, permanent: true, status: response.status };
|
|
2770
2907
|
}
|
|
@@ -2886,9 +3023,94 @@ var ApiCache = class {
|
|
|
2886
3023
|
}
|
|
2887
3024
|
};
|
|
2888
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
|
+
|
|
2889
3111
|
// src/core/ApiClient.ts
|
|
2890
|
-
var SDK_VERSION = "1.
|
|
2891
|
-
var
|
|
3112
|
+
var SDK_VERSION = "1.5.2";
|
|
3113
|
+
var ApiClient = class {
|
|
2892
3114
|
constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2893
3115
|
this.cache = new ApiCache();
|
|
2894
3116
|
this.appKey = appKey;
|
|
@@ -2898,6 +3120,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2898
3120
|
this.environment = environment;
|
|
2899
3121
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2900
3122
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
3123
|
+
this.batcher = new EventBatcher((url, payload, label) => this.postWithQueue(url, payload, label), debug);
|
|
2901
3124
|
}
|
|
2902
3125
|
getHttpClient() {
|
|
2903
3126
|
return this.httpClient;
|
|
@@ -2908,6 +3131,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2908
3131
|
getWebUrl() {
|
|
2909
3132
|
return this.webUrl;
|
|
2910
3133
|
}
|
|
3134
|
+
getAppKey() {
|
|
3135
|
+
return this.appKey;
|
|
3136
|
+
}
|
|
2911
3137
|
getEnvironment() {
|
|
2912
3138
|
return this.environment;
|
|
2913
3139
|
}
|
|
@@ -2943,30 +3169,33 @@ var _ApiClient = class _ApiClient {
|
|
|
2943
3169
|
message,
|
|
2944
3170
|
context,
|
|
2945
3171
|
sdkVersion: SDK_VERSION,
|
|
2946
|
-
platform:
|
|
3172
|
+
platform: import_react_native11.Platform.OS
|
|
2947
3173
|
}, true);
|
|
2948
3174
|
} catch (err) {
|
|
2949
|
-
if (this.debug) console.
|
|
3175
|
+
if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
|
|
2950
3176
|
}
|
|
2951
3177
|
}
|
|
2952
3178
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2953
|
-
await this.
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
3179
|
+
await this.batcher.track(eventName, distinctId, this.environment, properties, timestamp);
|
|
3180
|
+
}
|
|
3181
|
+
async flushEventBatch() {
|
|
3182
|
+
await this.batcher.flush();
|
|
3183
|
+
}
|
|
3184
|
+
dispose() {
|
|
3185
|
+
this.batcher.dispose();
|
|
2960
3186
|
}
|
|
2961
3187
|
async identify(distinctId, properties, email, deviceId) {
|
|
2962
|
-
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");
|
|
2963
3189
|
}
|
|
2964
|
-
async startSession(distinctId, sessionId, platform) {
|
|
3190
|
+
async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
|
|
2965
3191
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2966
3192
|
distinctId,
|
|
2967
3193
|
sessionId,
|
|
2968
|
-
platform: platform ??
|
|
2969
|
-
environment: this.environment.toLowerCase()
|
|
3194
|
+
platform: platform ?? import_react_native11.Platform.OS,
|
|
3195
|
+
environment: this.environment.toLowerCase(),
|
|
3196
|
+
appVersion: appVersion ?? "unknown",
|
|
3197
|
+
deviceModel: deviceModel ?? "unknown",
|
|
3198
|
+
osVersion: osVersion ?? String(import_react_native11.Platform.Version)
|
|
2970
3199
|
}, `session.start:${sessionId}`);
|
|
2971
3200
|
return { success: true };
|
|
2972
3201
|
}
|
|
@@ -2978,7 +3207,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2978
3207
|
const key = `${flagKey}:${distinctId}`;
|
|
2979
3208
|
const hit = this.cache.getVariant(key);
|
|
2980
3209
|
if (hit !== void 0) return hit;
|
|
2981
|
-
const persisted = await
|
|
3210
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
2982
3211
|
if (persisted !== null) {
|
|
2983
3212
|
this.cache.setVariant(key, persisted);
|
|
2984
3213
|
return persisted;
|
|
@@ -2986,25 +3215,20 @@ var _ApiClient = class _ApiClient {
|
|
|
2986
3215
|
try {
|
|
2987
3216
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2988
3217
|
if (res.status === 404) {
|
|
2989
|
-
this.log("Flag not found (404):", flagKey);
|
|
2990
3218
|
const v = { variant: null };
|
|
2991
3219
|
this.cache.setVariant(key, v, this.cache.nullTTL);
|
|
2992
3220
|
return v;
|
|
2993
3221
|
}
|
|
2994
3222
|
if (!res.ok) {
|
|
2995
|
-
this.log("Non-OK response for variant:", flagKey, res.status);
|
|
2996
3223
|
const stale = this.cache.getStaleVariant(key);
|
|
2997
|
-
|
|
2998
|
-
return { variant: null };
|
|
3224
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
2999
3225
|
}
|
|
3000
3226
|
this.cache.setVariant(key, res.data);
|
|
3001
|
-
await
|
|
3227
|
+
await writeFlagToStorage(flagKey, distinctId, res.data);
|
|
3002
3228
|
return res.data;
|
|
3003
|
-
} catch
|
|
3004
|
-
this.log("Failed to get variant:", flagKey, error);
|
|
3229
|
+
} catch {
|
|
3005
3230
|
const stale = this.cache.getStaleVariant(key);
|
|
3006
|
-
|
|
3007
|
-
return { variant: null };
|
|
3231
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
3008
3232
|
}
|
|
3009
3233
|
}
|
|
3010
3234
|
async getPaywall(placement) {
|
|
@@ -3013,29 +3237,22 @@ var _ApiClient = class _ApiClient {
|
|
|
3013
3237
|
try {
|
|
3014
3238
|
const res = await this.get(`/api/v1/paywalls/${placement}`);
|
|
3015
3239
|
if (res.status === 404) {
|
|
3016
|
-
this.log("Paywall not found (404):", placement);
|
|
3017
3240
|
this.cache.setPaywall(placement, null, this.cache.nullTTL);
|
|
3018
3241
|
return null;
|
|
3019
3242
|
}
|
|
3020
3243
|
if (!res.ok) {
|
|
3021
|
-
this.log("Non-OK response for paywall:", placement, res.status);
|
|
3022
3244
|
const stale = this.cache.getStalePaywall(placement);
|
|
3023
|
-
|
|
3024
|
-
return null;
|
|
3245
|
+
return stale !== void 0 ? stale : null;
|
|
3025
3246
|
}
|
|
3026
3247
|
this.cache.setPaywall(placement, res.data);
|
|
3027
3248
|
return res.data;
|
|
3028
|
-
} catch
|
|
3029
|
-
this.log("Failed to get paywall:", placement, error);
|
|
3249
|
+
} catch {
|
|
3030
3250
|
const stale = this.cache.getStalePaywall(placement);
|
|
3031
|
-
|
|
3032
|
-
return null;
|
|
3251
|
+
return stale !== void 0 ? stale : null;
|
|
3033
3252
|
}
|
|
3034
3253
|
}
|
|
3035
3254
|
async getEmergencyPaywall() {
|
|
3036
|
-
|
|
3037
|
-
this.log("Got emergency paywall:", result.enabled);
|
|
3038
|
-
return result;
|
|
3255
|
+
return getEmergencyPaywall(this);
|
|
3039
3256
|
}
|
|
3040
3257
|
async getCampaign(placement, distinctId, context) {
|
|
3041
3258
|
const key = `${placement}:${distinctId}`;
|
|
@@ -3044,18 +3261,14 @@ var _ApiClient = class _ApiClient {
|
|
|
3044
3261
|
try {
|
|
3045
3262
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
3046
3263
|
if (!result) {
|
|
3047
|
-
this.log("No campaign found for placement:", placement);
|
|
3048
3264
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
3049
3265
|
return null;
|
|
3050
3266
|
}
|
|
3051
|
-
this.log("Got campaign:", placement, result.variantKey);
|
|
3052
3267
|
this.cache.setCampaign(key, result);
|
|
3053
3268
|
return result;
|
|
3054
|
-
} catch
|
|
3055
|
-
this.log("Failed to get campaign:", placement, error);
|
|
3269
|
+
} catch {
|
|
3056
3270
|
const stale = this.cache.getStaleCampaign(key);
|
|
3057
|
-
|
|
3058
|
-
return null;
|
|
3271
|
+
return stale !== void 0 ? stale : null;
|
|
3059
3272
|
}
|
|
3060
3273
|
}
|
|
3061
3274
|
async getPrimaryCampaign(distinctId) {
|
|
@@ -3065,63 +3278,34 @@ var _ApiClient = class _ApiClient {
|
|
|
3065
3278
|
try {
|
|
3066
3279
|
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
3067
3280
|
if (res.status === 404) {
|
|
3068
|
-
this.log("Primary campaign not found (404)");
|
|
3069
3281
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
3070
3282
|
return null;
|
|
3071
3283
|
}
|
|
3072
3284
|
if (!res.ok) {
|
|
3073
|
-
this.log("Non-OK response for primary campaign:", res.status);
|
|
3074
3285
|
const stale = this.cache.getStaleCampaign(key);
|
|
3075
|
-
|
|
3076
|
-
return null;
|
|
3286
|
+
return stale !== void 0 ? stale : null;
|
|
3077
3287
|
}
|
|
3078
|
-
this.log("Got primary campaign:", res.data?.campaignId);
|
|
3079
3288
|
this.cache.setCampaign(key, res.data);
|
|
3080
3289
|
return res.data;
|
|
3081
|
-
} catch
|
|
3082
|
-
this.log("Failed to get primary campaign:", error);
|
|
3290
|
+
} catch {
|
|
3083
3291
|
const stale = this.cache.getStaleCampaign(key);
|
|
3084
|
-
|
|
3085
|
-
return null;
|
|
3292
|
+
return stale !== void 0 ? stale : null;
|
|
3086
3293
|
}
|
|
3087
3294
|
}
|
|
3088
3295
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
3089
|
-
|
|
3090
|
-
this,
|
|
3091
|
-
this.appKey,
|
|
3092
|
-
platform,
|
|
3093
|
-
receipt,
|
|
3094
|
-
productId,
|
|
3095
|
-
transactionId,
|
|
3096
|
-
priceLocal,
|
|
3097
|
-
currency,
|
|
3098
|
-
country,
|
|
3099
|
-
distinctId,
|
|
3100
|
-
paywallPlacement,
|
|
3101
|
-
variantKey
|
|
3102
|
-
);
|
|
3103
|
-
this.log("Purchase validated:", result.success);
|
|
3104
|
-
return result;
|
|
3296
|
+
return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
|
|
3105
3297
|
}
|
|
3106
3298
|
async getSubscriptionStatus(distinctId) {
|
|
3107
|
-
|
|
3108
|
-
this.log("Subscription status:", result.hasActiveSubscription);
|
|
3109
|
-
return result;
|
|
3299
|
+
return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
|
|
3110
3300
|
}
|
|
3111
3301
|
async evaluateFlags(keys, distinctId) {
|
|
3112
3302
|
const query = keys.map(encodeURIComponent).join(",");
|
|
3113
|
-
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
3114
3303
|
try {
|
|
3115
|
-
const res = await this.httpClient.get(
|
|
3304
|
+
const res = await this.httpClient.get(`/api/v1/flags/evaluate?keys=${query}`, {
|
|
3116
3305
|
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
3117
3306
|
});
|
|
3118
|
-
if (!res.ok) {
|
|
3119
|
-
|
|
3120
|
-
return {};
|
|
3121
|
-
}
|
|
3122
|
-
const raw = res.data;
|
|
3123
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
3124
|
-
const record = raw;
|
|
3307
|
+
if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
|
|
3308
|
+
const record = res.data;
|
|
3125
3309
|
const result = {};
|
|
3126
3310
|
for (const key of Object.keys(record)) {
|
|
3127
3311
|
const value = record[key];
|
|
@@ -3129,11 +3313,10 @@ var _ApiClient = class _ApiClient {
|
|
|
3129
3313
|
result[key] = variant;
|
|
3130
3314
|
const flagVariant = { variant };
|
|
3131
3315
|
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
3132
|
-
await
|
|
3316
|
+
await writeFlagToStorage(key, distinctId, flagVariant);
|
|
3133
3317
|
}
|
|
3134
3318
|
return result;
|
|
3135
|
-
} catch
|
|
3136
|
-
this.log("Failed to evaluateFlags:", error);
|
|
3319
|
+
} catch {
|
|
3137
3320
|
return {};
|
|
3138
3321
|
}
|
|
3139
3322
|
}
|
|
@@ -3141,15 +3324,9 @@ var _ApiClient = class _ApiClient {
|
|
|
3141
3324
|
try {
|
|
3142
3325
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
3143
3326
|
const response = await this.get(url);
|
|
3144
|
-
if (response.status === 404) return { value: false, flagKey };
|
|
3145
|
-
if (!response.ok) {
|
|
3146
|
-
this.log("Non-OK response for conditional flag:", flagKey, response.status);
|
|
3147
|
-
return { value: false, flagKey };
|
|
3148
|
-
}
|
|
3149
|
-
this.log("Got conditional flag:", flagKey, response.data.value);
|
|
3327
|
+
if (response.status === 404 || !response.ok) return { value: false, flagKey };
|
|
3150
3328
|
return { value: response.data.value, flagKey };
|
|
3151
3329
|
} catch {
|
|
3152
|
-
this.log("Failed to get conditional flag:", flagKey);
|
|
3153
3330
|
return { value: false, flagKey };
|
|
3154
3331
|
}
|
|
3155
3332
|
}
|
|
@@ -3157,34 +3334,13 @@ var _ApiClient = class _ApiClient {
|
|
|
3157
3334
|
const key = `${flagKey}:${distinctId}`;
|
|
3158
3335
|
const hit = this.cache.getVariant(key);
|
|
3159
3336
|
if (hit !== void 0) return hit;
|
|
3160
|
-
const persisted = await
|
|
3337
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
3161
3338
|
if (persisted !== null) {
|
|
3162
3339
|
this.cache.setVariant(key, persisted);
|
|
3163
3340
|
return persisted;
|
|
3164
3341
|
}
|
|
3165
3342
|
return null;
|
|
3166
3343
|
}
|
|
3167
|
-
async readFlagFromStorage(flagKey, distinctId) {
|
|
3168
|
-
try {
|
|
3169
|
-
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3170
|
-
if (!raw) return null;
|
|
3171
|
-
const parsed = JSON.parse(raw);
|
|
3172
|
-
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3173
|
-
const { variant, payload, cachedAt } = parsed;
|
|
3174
|
-
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
3175
|
-
return { variant, payload };
|
|
3176
|
-
} catch {
|
|
3177
|
-
return null;
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3181
|
-
try {
|
|
3182
|
-
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3183
|
-
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3184
|
-
} catch (err) {
|
|
3185
|
-
this.log("writeFlagToStorage failed", err);
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
3344
|
async postWithQueue(url, payload, label) {
|
|
3189
3345
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
3190
3346
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -3197,7 +3353,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3197
3353
|
this.log("Request failed:", label, error);
|
|
3198
3354
|
if (this.offlineQueueEnabled) {
|
|
3199
3355
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
3200
|
-
this.log("Queued after failure:", label);
|
|
3201
3356
|
return;
|
|
3202
3357
|
}
|
|
3203
3358
|
throw error;
|
|
@@ -3207,8 +3362,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3207
3362
|
if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
|
|
3208
3363
|
}
|
|
3209
3364
|
};
|
|
3210
|
-
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3211
|
-
var ApiClient = _ApiClient;
|
|
3212
3365
|
|
|
3213
3366
|
// src/domains/campaign/CampaignError.ts
|
|
3214
3367
|
var CampaignError = class extends PaywalloError {
|
|
@@ -3233,6 +3386,7 @@ var CampaignGateService = class {
|
|
|
3233
3386
|
constructor() {
|
|
3234
3387
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3235
3388
|
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
3389
|
+
this.activePreloadPromises = /* @__PURE__ */ new Map();
|
|
3236
3390
|
}
|
|
3237
3391
|
async getCampaign(apiClient, placement, context) {
|
|
3238
3392
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3272,12 +3426,23 @@ var CampaignGateService = class {
|
|
|
3272
3426
|
}
|
|
3273
3427
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
3274
3428
|
const isActive = await hasActive();
|
|
3275
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
3429
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3276
3430
|
if (isActive) return true;
|
|
3277
|
-
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
3431
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
|
|
3278
3432
|
return r.purchased || r.restored;
|
|
3279
3433
|
}
|
|
3280
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) {
|
|
3281
3446
|
this.pendingPreloads.add(placement);
|
|
3282
3447
|
try {
|
|
3283
3448
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
@@ -3286,7 +3451,7 @@ var CampaignGateService = class {
|
|
|
3286
3451
|
}
|
|
3287
3452
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3288
3453
|
if (PaywalloClient.getConfig()?.debug) {
|
|
3289
|
-
console.
|
|
3454
|
+
console.log(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3290
3455
|
}
|
|
3291
3456
|
return { success: true };
|
|
3292
3457
|
} catch (error) {
|
|
@@ -3312,7 +3477,7 @@ var CampaignGateService = class {
|
|
|
3312
3477
|
var campaignGateService = new CampaignGateService();
|
|
3313
3478
|
|
|
3314
3479
|
// src/domains/identity/AdvertisingIdManager.ts
|
|
3315
|
-
var
|
|
3480
|
+
var import_react_native12 = require("react-native");
|
|
3316
3481
|
var ZERO_IDFA = "00000000-0000-0000-0000-000000000000";
|
|
3317
3482
|
var AdvertisingIdManager = class {
|
|
3318
3483
|
constructor() {
|
|
@@ -3323,7 +3488,7 @@ var AdvertisingIdManager = class {
|
|
|
3323
3488
|
const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
|
|
3324
3489
|
try {
|
|
3325
3490
|
const tracking = await import("expo-tracking-transparency");
|
|
3326
|
-
if (
|
|
3491
|
+
if (import_react_native12.Platform.OS === "ios") {
|
|
3327
3492
|
if (!tracking.isAvailable()) {
|
|
3328
3493
|
const idfv2 = await this.collectIdfv();
|
|
3329
3494
|
this.cached = { ...fallback, idfv: idfv2 };
|
|
@@ -3342,10 +3507,8 @@ var AdvertisingIdManager = class {
|
|
|
3342
3507
|
this.cached = { idfv, idfa, gaid: null, attStatus: status };
|
|
3343
3508
|
return this.cached;
|
|
3344
3509
|
}
|
|
3345
|
-
if (
|
|
3346
|
-
const
|
|
3347
|
-
const optedIn = androidStatus.granted;
|
|
3348
|
-
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
3510
|
+
if (import_react_native12.Platform.OS === "android") {
|
|
3511
|
+
const rawGaid = tracking.getAdvertisingId();
|
|
3349
3512
|
const gaid = this.sanitizeId(rawGaid);
|
|
3350
3513
|
this.cached = { idfv: null, idfa: null, gaid, attStatus: "unavailable" };
|
|
3351
3514
|
return this.cached;
|
|
@@ -3359,9 +3522,8 @@ var AdvertisingIdManager = class {
|
|
|
3359
3522
|
}
|
|
3360
3523
|
async collectIdfv() {
|
|
3361
3524
|
try {
|
|
3362
|
-
const
|
|
3363
|
-
|
|
3364
|
-
return this.sanitizeId(idfv);
|
|
3525
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
3526
|
+
return this.sanitizeId(info.idfv);
|
|
3365
3527
|
} catch {
|
|
3366
3528
|
return null;
|
|
3367
3529
|
}
|
|
@@ -3373,14 +3535,12 @@ var AdvertisingIdManager = class {
|
|
|
3373
3535
|
};
|
|
3374
3536
|
|
|
3375
3537
|
// src/domains/identity/InstallTracker.ts
|
|
3376
|
-
var
|
|
3377
|
-
var import_async_storage3 = __toESM(require("@react-native-async-storage/async-storage"));
|
|
3378
|
-
var import_react_native_device_info2 = __toESM(require("react-native-device-info"));
|
|
3538
|
+
var import_react_native16 = require("react-native");
|
|
3379
3539
|
|
|
3380
3540
|
// src/domains/identity/MetaBridge.ts
|
|
3381
|
-
var
|
|
3541
|
+
var import_react_native13 = require("react-native");
|
|
3382
3542
|
var TIMEOUT_MS = 2e3;
|
|
3383
|
-
var NativeBridge =
|
|
3543
|
+
var NativeBridge = import_react_native13.NativeModules.PaywalloFBBridge ?? null;
|
|
3384
3544
|
var MetaBridge = class {
|
|
3385
3545
|
async getAnonymousID() {
|
|
3386
3546
|
if (!NativeBridge) return null;
|
|
@@ -3428,11 +3588,11 @@ var MetaBridge = class {
|
|
|
3428
3588
|
var metaBridge = new MetaBridge();
|
|
3429
3589
|
|
|
3430
3590
|
// src/domains/identity/InstallReferrerManager.ts
|
|
3431
|
-
var
|
|
3591
|
+
var import_react_native14 = require("react-native");
|
|
3432
3592
|
var REFERRER_TIMEOUT_MS = 5e3;
|
|
3433
3593
|
var InstallReferrerManager = class {
|
|
3434
3594
|
async getReferrer() {
|
|
3435
|
-
if (
|
|
3595
|
+
if (import_react_native14.Platform.OS !== "android") {
|
|
3436
3596
|
return this.emptyResult();
|
|
3437
3597
|
}
|
|
3438
3598
|
let timerId;
|
|
@@ -3446,8 +3606,7 @@ var InstallReferrerManager = class {
|
|
|
3446
3606
|
}
|
|
3447
3607
|
async fetchReferrer() {
|
|
3448
3608
|
try {
|
|
3449
|
-
const
|
|
3450
|
-
const referrer = await Application.getInstallReferrerAsync();
|
|
3609
|
+
const referrer = await nativeDeviceInfo.getInstallReferrer();
|
|
3451
3610
|
if (!referrer) return this.emptyResult();
|
|
3452
3611
|
const capped = referrer.slice(0, 2048);
|
|
3453
3612
|
const params = this.parseReferrer(capped);
|
|
@@ -3492,17 +3651,45 @@ var InstallReferrerManager = class {
|
|
|
3492
3651
|
var installReferrerManager = new InstallReferrerManager();
|
|
3493
3652
|
|
|
3494
3653
|
// src/domains/session/SessionManager.ts
|
|
3495
|
-
var
|
|
3654
|
+
var import_react_native15 = require("react-native");
|
|
3496
3655
|
init_uuid();
|
|
3497
3656
|
|
|
3657
|
+
// src/utils/deviceInfo.ts
|
|
3658
|
+
async function collectDeviceInfo() {
|
|
3659
|
+
const result = await nativeDeviceInfo.getDeviceInfo();
|
|
3660
|
+
return {
|
|
3661
|
+
appVersion: result.appVersion,
|
|
3662
|
+
deviceModel: result.model,
|
|
3663
|
+
osVersion: result.systemVersion
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3498
3667
|
// src/domains/session/sessionTracking.ts
|
|
3499
3668
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
3500
3669
|
const distinctId = identityManager.getDistinctId();
|
|
3501
|
-
await
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
});
|
|
3670
|
+
const deviceInfo = await collectDeviceInfo().catch(() => ({
|
|
3671
|
+
appVersion: "unknown",
|
|
3672
|
+
deviceModel: "unknown",
|
|
3673
|
+
osVersion: "unknown"
|
|
3674
|
+
}));
|
|
3675
|
+
await Promise.all([
|
|
3676
|
+
apiClient.startSession(
|
|
3677
|
+
distinctId,
|
|
3678
|
+
sessionId,
|
|
3679
|
+
void 0,
|
|
3680
|
+
deviceInfo.appVersion,
|
|
3681
|
+
deviceInfo.deviceModel,
|
|
3682
|
+
deviceInfo.osVersion
|
|
3683
|
+
),
|
|
3684
|
+
apiClient.trackEvent("$session_start", distinctId, {
|
|
3685
|
+
sessionId,
|
|
3686
|
+
timestamp: sessionStartedAt?.toISOString() ?? null,
|
|
3687
|
+
appVersion: deviceInfo.appVersion,
|
|
3688
|
+
deviceModel: deviceInfo.deviceModel,
|
|
3689
|
+
osVersion: deviceInfo.osVersion
|
|
3690
|
+
}).catch(() => {
|
|
3691
|
+
})
|
|
3692
|
+
]);
|
|
3506
3693
|
}
|
|
3507
3694
|
async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
|
|
3508
3695
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3599,6 +3786,9 @@ var SessionManager = class {
|
|
|
3599
3786
|
if (!this.sessionId || !this.sessionStartedAt) {
|
|
3600
3787
|
return;
|
|
3601
3788
|
}
|
|
3789
|
+
if (this.apiClient) {
|
|
3790
|
+
await this.apiClient.flushEventBatch();
|
|
3791
|
+
}
|
|
3602
3792
|
await this.trackSessionEnd();
|
|
3603
3793
|
this.sessionId = null;
|
|
3604
3794
|
this.sessionStartedAt = null;
|
|
@@ -3661,7 +3851,7 @@ var SessionManager = class {
|
|
|
3661
3851
|
if (this.appStateSubscription) {
|
|
3662
3852
|
this.appStateSubscription.remove();
|
|
3663
3853
|
}
|
|
3664
|
-
this.appStateSubscription =
|
|
3854
|
+
this.appStateSubscription = import_react_native15.AppState.addEventListener("change", this.handleAppStateChange);
|
|
3665
3855
|
}
|
|
3666
3856
|
async handleForeground() {
|
|
3667
3857
|
if (this.backgroundTimestamp) {
|
|
@@ -3683,6 +3873,9 @@ var SessionManager = class {
|
|
|
3683
3873
|
async handleBackground() {
|
|
3684
3874
|
this.backgroundTimestamp = Date.now();
|
|
3685
3875
|
await this.trackAppBackground();
|
|
3876
|
+
if (this.apiClient) {
|
|
3877
|
+
await this.apiClient.flushEventBatch();
|
|
3878
|
+
}
|
|
3686
3879
|
}
|
|
3687
3880
|
async checkEmergencyPaywall() {
|
|
3688
3881
|
if (!this.apiClient || !this.emergencyPaywallHandler) {
|
|
@@ -3731,16 +3924,26 @@ var sessionManager = new SessionManager();
|
|
|
3731
3924
|
// src/domains/identity/InstallTracker.ts
|
|
3732
3925
|
var InstallTracker = class {
|
|
3733
3926
|
constructor(debug = false, advertisingIdManager) {
|
|
3927
|
+
this._trackingInProgress = false;
|
|
3734
3928
|
this.debug = debug;
|
|
3735
3929
|
this.advertisingIdManager = advertisingIdManager ?? new AdvertisingIdManager();
|
|
3736
3930
|
}
|
|
3737
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) {
|
|
3738
3941
|
const storage = new SecureStorage(this.debug);
|
|
3739
3942
|
const alreadyTracked = await storage.get("@panel:install_tracked");
|
|
3740
3943
|
if (alreadyTracked) return;
|
|
3741
3944
|
let fallbackTracked = null;
|
|
3742
3945
|
try {
|
|
3743
|
-
fallbackTracked = await
|
|
3946
|
+
fallbackTracked = await nativeStorage.get("@paywallo:install_tracked");
|
|
3744
3947
|
} catch {
|
|
3745
3948
|
}
|
|
3746
3949
|
if (fallbackTracked) return;
|
|
@@ -3748,47 +3951,47 @@ var InstallTracker = class {
|
|
|
3748
3951
|
const sessionId = sessionManager.getSessionId();
|
|
3749
3952
|
const installedAt = Date.now();
|
|
3750
3953
|
if (!distinctId) return;
|
|
3751
|
-
const deviceInfo = import_react_native_device_info2.default;
|
|
3752
3954
|
let deviceData = {};
|
|
3753
3955
|
try {
|
|
3754
|
-
const screen =
|
|
3956
|
+
const screen = import_react_native16.Dimensions.get("screen");
|
|
3755
3957
|
const screenWidth = Math.round(screen.width ?? 0);
|
|
3756
3958
|
const screenHeight = Math.round(screen.height ?? 0);
|
|
3757
3959
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
3758
3960
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
|
|
3759
|
-
const
|
|
3760
|
-
const
|
|
3761
|
-
const
|
|
3762
|
-
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";
|
|
3763
3966
|
let carrier = "unknown";
|
|
3764
3967
|
try {
|
|
3765
|
-
carrier =
|
|
3968
|
+
carrier = info.carrier ?? "unknown";
|
|
3766
3969
|
} catch {
|
|
3767
3970
|
}
|
|
3768
3971
|
let totalDisk = 0;
|
|
3769
3972
|
try {
|
|
3770
|
-
totalDisk = Number(
|
|
3973
|
+
totalDisk = Number(info.totalDisk) || 0;
|
|
3771
3974
|
} catch {
|
|
3772
3975
|
}
|
|
3773
3976
|
let freeDisk = 0;
|
|
3774
3977
|
try {
|
|
3775
|
-
freeDisk = Number(
|
|
3978
|
+
freeDisk = Number(info.freeDisk) || 0;
|
|
3776
3979
|
} catch {
|
|
3777
3980
|
}
|
|
3778
3981
|
let totalRam = 0;
|
|
3779
3982
|
try {
|
|
3780
|
-
totalRam = Number(
|
|
3983
|
+
totalRam = Number(info.totalRam) || 0;
|
|
3781
3984
|
} catch {
|
|
3782
3985
|
}
|
|
3783
3986
|
let brand = "unknown";
|
|
3784
3987
|
try {
|
|
3785
|
-
brand =
|
|
3988
|
+
brand = info.brand ?? "unknown";
|
|
3786
3989
|
} catch {
|
|
3787
3990
|
}
|
|
3788
3991
|
let installerPackage = null;
|
|
3789
3992
|
try {
|
|
3790
|
-
if (
|
|
3791
|
-
installerPackage =
|
|
3993
|
+
if (import_react_native16.Platform.OS === "android") {
|
|
3994
|
+
installerPackage = info.installerPackage ?? null;
|
|
3792
3995
|
}
|
|
3793
3996
|
} catch {
|
|
3794
3997
|
}
|
|
@@ -3827,37 +4030,34 @@ var InstallTracker = class {
|
|
|
3827
4030
|
});
|
|
3828
4031
|
}
|
|
3829
4032
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
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(() => {
|
|
3847
4052
|
});
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
await storage.set("@panel:install_tracked", String(installedAt));
|
|
3853
|
-
try {
|
|
3854
|
-
await import_async_storage3.default.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3855
|
-
} catch {
|
|
3856
|
-
}
|
|
4053
|
+
}
|
|
4054
|
+
await storage.set("@panel:install_tracked", String(installedAt));
|
|
4055
|
+
try {
|
|
4056
|
+
await nativeStorage.set("@paywallo:install_tracked", String(installedAt));
|
|
3857
4057
|
} catch {
|
|
3858
4058
|
}
|
|
3859
4059
|
}
|
|
3860
|
-
async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
|
|
4060
|
+
async callDeferredMatch(apiClient, deviceData, adIds, anonId, installedAt) {
|
|
3861
4061
|
const storage = new SecureStorage(this.debug);
|
|
3862
4062
|
const alreadyMatched = await storage.get("@panel:deferred_match_done");
|
|
3863
4063
|
if (alreadyMatched) return;
|
|
@@ -3865,7 +4065,7 @@ var InstallTracker = class {
|
|
|
3865
4065
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
3866
4066
|
try {
|
|
3867
4067
|
const baseUrl = apiClient.getBaseUrl();
|
|
3868
|
-
const appKey = apiClient.
|
|
4068
|
+
const appKey = apiClient.getAppKey();
|
|
3869
4069
|
await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
|
|
3870
4070
|
method: "POST",
|
|
3871
4071
|
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
@@ -3877,7 +4077,8 @@ var InstallTracker = class {
|
|
|
3877
4077
|
screenHeight: deviceData.screenHeight,
|
|
3878
4078
|
timezone: deviceData.timezone,
|
|
3879
4079
|
locale: deviceData.locale,
|
|
3880
|
-
fbAnonId: anonId
|
|
4080
|
+
fbAnonId: anonId,
|
|
4081
|
+
installTimestamp: new Date(installedAt).toISOString()
|
|
3881
4082
|
}),
|
|
3882
4083
|
signal: controller.signal
|
|
3883
4084
|
});
|
|
@@ -4002,7 +4203,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4002
4203
|
error: error instanceof Error ? error.message : String(error)
|
|
4003
4204
|
});
|
|
4004
4205
|
} catch (err) {
|
|
4005
|
-
if (config.debug) console.
|
|
4206
|
+
if (config.debug) console.error("[Paywallo] reportInitFailure error", err);
|
|
4006
4207
|
}
|
|
4007
4208
|
}
|
|
4008
4209
|
reportInitSuccess(config, attempts) {
|
|
@@ -4011,7 +4212,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4011
4212
|
void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
|
|
4012
4213
|
}
|
|
4013
4214
|
} catch (err) {
|
|
4014
|
-
if (config.debug) console.
|
|
4215
|
+
if (config.debug) console.error("[Paywallo] reportInitSuccess error", err);
|
|
4015
4216
|
}
|
|
4016
4217
|
}
|
|
4017
4218
|
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
@@ -4038,7 +4239,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4038
4239
|
]);
|
|
4039
4240
|
this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
4040
4241
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
4041
|
-
affiliateManager.initialize(this.apiClient, config.debug);
|
|
4042
4242
|
subscriptionManager.init({
|
|
4043
4243
|
serverUrl: this.apiClient.getBaseUrl(),
|
|
4044
4244
|
appKey: config.appKey,
|
|
@@ -4059,18 +4259,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4059
4259
|
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
4060
4260
|
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
4061
4261
|
}
|
|
4262
|
+
setNativeStorageDebug(config.debug ?? false);
|
|
4263
|
+
setNativeDeviceInfoDebug(config.debug ?? false);
|
|
4062
4264
|
this.config = config;
|
|
4063
4265
|
if (this.resolveReady) {
|
|
4064
4266
|
this.resolveReady();
|
|
4065
4267
|
this.resolveReady = null;
|
|
4066
4268
|
}
|
|
4067
|
-
if (config.debug) console.
|
|
4068
|
-
if (
|
|
4269
|
+
if (config.debug) console.log("[Paywallo INIT] complete");
|
|
4270
|
+
if (distinctId) {
|
|
4271
|
+
this.apiClient.getSubscriptionStatus(distinctId).then(() => {
|
|
4272
|
+
if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
|
|
4273
|
+
}).catch(() => {
|
|
4274
|
+
});
|
|
4275
|
+
}
|
|
4276
|
+
{
|
|
4069
4277
|
const apiClientRef = this.apiClient;
|
|
4070
4278
|
if (config.autoPreloadCampaign) {
|
|
4071
4279
|
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
4072
4280
|
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
4073
|
-
if (config.debug) console.
|
|
4281
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
4074
4282
|
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
4075
4283
|
});
|
|
4076
4284
|
} else {
|
|
@@ -4078,7 +4286,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4078
4286
|
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
4079
4287
|
if (placement) {
|
|
4080
4288
|
this.autoPreloadedPlacement = placement;
|
|
4081
|
-
if (config.debug) console.
|
|
4289
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
4082
4290
|
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
4083
4291
|
});
|
|
4084
4292
|
return placement;
|
|
@@ -4090,21 +4298,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4090
4298
|
}
|
|
4091
4299
|
async identify(options) {
|
|
4092
4300
|
this.ensureInitialized();
|
|
4093
|
-
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);
|
|
4094
4302
|
await identityManager.identify(options);
|
|
4095
4303
|
}
|
|
4096
4304
|
async track(eventName, options) {
|
|
4097
4305
|
const apiClient = this.getApiClientOrThrow();
|
|
4098
4306
|
const distinctId = identityManager.getDistinctId();
|
|
4099
4307
|
if (!distinctId) {
|
|
4100
|
-
if (this.config?.debug) console.
|
|
4308
|
+
if (this.config?.debug) console.log(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
|
|
4101
4309
|
return;
|
|
4102
4310
|
}
|
|
4103
4311
|
if (!isValidEventName(eventName)) {
|
|
4104
4312
|
const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
|
|
4105
4313
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
4106
4314
|
}
|
|
4107
|
-
if (this.config?.debug && eventName.startsWith("$purchase")) console.
|
|
4315
|
+
if (this.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
|
|
4108
4316
|
const sessionId = sessionManager.getSessionId();
|
|
4109
4317
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
4110
4318
|
...identityManager.getProperties(),
|
|
@@ -4163,14 +4371,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4163
4371
|
try {
|
|
4164
4372
|
const distinctId = identityManager.getDistinctId();
|
|
4165
4373
|
if (!distinctId) {
|
|
4166
|
-
if (this.config?.debug) console.
|
|
4374
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
|
|
4167
4375
|
return { variant: null };
|
|
4168
4376
|
}
|
|
4169
4377
|
const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
4170
|
-
if (this.config?.debug) console.
|
|
4378
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
|
|
4171
4379
|
return result;
|
|
4172
4380
|
} catch (error) {
|
|
4173
|
-
if (this.config?.debug) console.
|
|
4381
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
|
|
4174
4382
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
4175
4383
|
return { variant: null };
|
|
4176
4384
|
}
|
|
@@ -4203,11 +4411,11 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4203
4411
|
}
|
|
4204
4412
|
async presentCampaign(placement, context) {
|
|
4205
4413
|
this.ensureInitialized();
|
|
4206
|
-
if (this.config?.debug) console.
|
|
4414
|
+
if (this.config?.debug) console.log(`[Paywallo CAMPAIGN] presenting ${placement}`);
|
|
4207
4415
|
const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
4208
4416
|
if (this.config?.debug) {
|
|
4209
|
-
if (result.error) console.
|
|
4210
|
-
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 });
|
|
4211
4419
|
}
|
|
4212
4420
|
return result;
|
|
4213
4421
|
}
|
|
@@ -4217,20 +4425,20 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4217
4425
|
if (this.activeChecker) {
|
|
4218
4426
|
const result = await this.activeChecker();
|
|
4219
4427
|
const isActive2 = result === true;
|
|
4220
|
-
if (this.config?.debug) console.
|
|
4428
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
|
|
4221
4429
|
return isActive2;
|
|
4222
4430
|
}
|
|
4223
4431
|
const distinctId = identityManager.getDistinctId();
|
|
4224
4432
|
if (!distinctId || !this.apiClient) {
|
|
4225
|
-
if (this.config?.debug) console.
|
|
4433
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
|
|
4226
4434
|
return false;
|
|
4227
4435
|
}
|
|
4228
4436
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
4229
4437
|
const isActive = status.hasActiveSubscription === true;
|
|
4230
|
-
if (this.config?.debug) console.
|
|
4438
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (server):", isActive);
|
|
4231
4439
|
return isActive;
|
|
4232
4440
|
} catch (error) {
|
|
4233
|
-
if (this.config?.debug) console.
|
|
4441
|
+
if (this.config?.debug) console.error("[Paywallo SUB] hasActiveSubscription error", error);
|
|
4234
4442
|
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4235
4443
|
return false;
|
|
4236
4444
|
}
|
|
@@ -4251,9 +4459,9 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4251
4459
|
async restorePurchases() {
|
|
4252
4460
|
this.ensureInitialized();
|
|
4253
4461
|
if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
4254
|
-
if (this.config?.debug) console.
|
|
4462
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] starting");
|
|
4255
4463
|
const result = await this.restoreHandler();
|
|
4256
|
-
if (this.config?.debug) console.
|
|
4464
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
|
|
4257
4465
|
return result;
|
|
4258
4466
|
}
|
|
4259
4467
|
async reset() {
|
|
@@ -4263,6 +4471,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4263
4471
|
async fullReset() {
|
|
4264
4472
|
const wasDebug = this.config?.debug ?? false;
|
|
4265
4473
|
await sessionManager.endSession();
|
|
4474
|
+
sessionManager.destroy();
|
|
4266
4475
|
await identityManager.reset();
|
|
4267
4476
|
await subscriptionCache.invalidateAll();
|
|
4268
4477
|
queueProcessor.dispose();
|
|
@@ -4271,6 +4480,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4271
4480
|
campaignGateService.clearPreloaded();
|
|
4272
4481
|
this.cleanupNetworkRecovery();
|
|
4273
4482
|
this.sessionFlagsMap.clear();
|
|
4483
|
+
this.apiClient?.dispose();
|
|
4274
4484
|
this.apiClient = null;
|
|
4275
4485
|
this.config = null;
|
|
4276
4486
|
this.initPromise = null;
|
|
@@ -4349,6 +4559,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4349
4559
|
registerEmergencyPaywallHandler(h) {
|
|
4350
4560
|
sessionManager.registerEmergencyPaywallHandler(h);
|
|
4351
4561
|
}
|
|
4562
|
+
async getEmergencyPaywall() {
|
|
4563
|
+
if (!this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized");
|
|
4564
|
+
return this.apiClient.getEmergencyPaywall();
|
|
4565
|
+
}
|
|
4352
4566
|
async requireSubscription(paywallPlacement) {
|
|
4353
4567
|
this.ensureInitialized();
|
|
4354
4568
|
if (await this.hasActiveSubscription()) return true;
|
|
@@ -4603,6 +4817,7 @@ function usePurchase() {
|
|
|
4603
4817
|
|
|
4604
4818
|
// src/hooks/useSubscription.ts
|
|
4605
4819
|
var import_react13 = require("react");
|
|
4820
|
+
var EMPTY_ENTITLEMENTS = [];
|
|
4606
4821
|
function useSubscription() {
|
|
4607
4822
|
const [status, setStatus] = (0, import_react13.useState)(null);
|
|
4608
4823
|
const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
|
|
@@ -4638,7 +4853,8 @@ function useSubscription() {
|
|
|
4638
4853
|
setStatus(s);
|
|
4639
4854
|
setIsLoading(false);
|
|
4640
4855
|
}).catch((err) => {
|
|
4641
|
-
|
|
4856
|
+
const e = err instanceof PurchaseError ? err : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, String(err));
|
|
4857
|
+
setError(e);
|
|
4642
4858
|
setIsLoading(false);
|
|
4643
4859
|
});
|
|
4644
4860
|
const removeListener = subscriptionManager.addListener((newStatus) => {
|
|
@@ -4653,7 +4869,7 @@ function useSubscription() {
|
|
|
4653
4869
|
isActive: status?.hasActiveSubscription ?? false,
|
|
4654
4870
|
isLoading,
|
|
4655
4871
|
error,
|
|
4656
|
-
entitlements: status?.entitlements ??
|
|
4872
|
+
entitlements: status?.entitlements ?? EMPTY_ENTITLEMENTS,
|
|
4657
4873
|
refresh,
|
|
4658
4874
|
restore
|
|
4659
4875
|
};
|
|
@@ -4661,7 +4877,7 @@ function useSubscription() {
|
|
|
4661
4877
|
|
|
4662
4878
|
// src/PaywalloProvider.tsx
|
|
4663
4879
|
var import_react18 = require("react");
|
|
4664
|
-
var
|
|
4880
|
+
var import_react_native17 = require("react-native");
|
|
4665
4881
|
|
|
4666
4882
|
// src/hooks/useCampaignPreload.ts
|
|
4667
4883
|
var import_react14 = require("react");
|
|
@@ -4785,7 +5001,7 @@ function useCampaignPreload() {
|
|
|
4785
5001
|
campaignId: campaign.campaignId,
|
|
4786
5002
|
variantKey: campaign.variantKey
|
|
4787
5003
|
});
|
|
4788
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5004
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4789
5005
|
return { success: true };
|
|
4790
5006
|
} catch (error) {
|
|
4791
5007
|
return {
|
|
@@ -4820,7 +5036,7 @@ function useProductLoader() {
|
|
|
4820
5036
|
for (const p of loaded) map.set(p.productId, p);
|
|
4821
5037
|
setProducts(map);
|
|
4822
5038
|
} catch (err) {
|
|
4823
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5039
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] refreshProducts failed", err);
|
|
4824
5040
|
} finally {
|
|
4825
5041
|
setIsLoadingProducts(false);
|
|
4826
5042
|
}
|
|
@@ -4860,7 +5076,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4860
5076
|
clearPreloadedWebView();
|
|
4861
5077
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4862
5078
|
} catch (error) {
|
|
4863
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5079
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded purchase failed", error);
|
|
4864
5080
|
setShowingPreloadedWebView(false);
|
|
4865
5081
|
clearPreloadedWebView();
|
|
4866
5082
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4881,7 +5097,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4881
5097
|
clearPreloadedWebView();
|
|
4882
5098
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4883
5099
|
} catch (error) {
|
|
4884
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5100
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded restore failed", error);
|
|
4885
5101
|
setShowingPreloadedWebView(false);
|
|
4886
5102
|
clearPreloadedWebView();
|
|
4887
5103
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4893,12 +5109,29 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4893
5109
|
// src/hooks/useSubscriptionSync.ts
|
|
4894
5110
|
var import_react17 = require("react");
|
|
4895
5111
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4896
|
-
var ACTIVE_STATUSES = ["active", "
|
|
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
|
+
};
|
|
4897
5130
|
function toDomainSubscriptionInfo(sub) {
|
|
4898
5131
|
return {
|
|
4899
5132
|
id: "",
|
|
4900
5133
|
productId: sub.productId,
|
|
4901
|
-
status: sub.status,
|
|
5134
|
+
status: PUBLIC_TO_DOMAIN_STATUS[sub.status] ?? "unknown",
|
|
4902
5135
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4903
5136
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4904
5137
|
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4912,7 +5145,7 @@ function toDomainResponse(status) {
|
|
|
4912
5145
|
return {
|
|
4913
5146
|
hasActiveSubscription: status.hasActiveSubscription,
|
|
4914
5147
|
subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
|
|
4915
|
-
entitlements: []
|
|
5148
|
+
entitlements: status.entitlements ?? []
|
|
4916
5149
|
};
|
|
4917
5150
|
}
|
|
4918
5151
|
function isSubscriptionStillActive(sub) {
|
|
@@ -4921,6 +5154,10 @@ function isSubscriptionStillActive(sub) {
|
|
|
4921
5154
|
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4922
5155
|
return true;
|
|
4923
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
|
+
}
|
|
4924
5161
|
async function resolveOfflineFromCache(distinctId) {
|
|
4925
5162
|
try {
|
|
4926
5163
|
const cached = await subscriptionCache.get(distinctId);
|
|
@@ -4961,23 +5198,12 @@ function useSubscriptionSync() {
|
|
|
4961
5198
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4962
5199
|
if (!apiClient || !distinctId) return false;
|
|
4963
5200
|
const cached = cacheRef.current;
|
|
4964
|
-
if (cached && cached.expiresAt > Date.now())
|
|
4965
|
-
return isActiveCacheEntry(cached);
|
|
4966
|
-
}
|
|
5201
|
+
if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
|
|
4967
5202
|
try {
|
|
4968
5203
|
const persisted = await subscriptionCache.get(distinctId);
|
|
4969
5204
|
if (persisted && !persisted.isStale) {
|
|
4970
|
-
const
|
|
4971
|
-
|
|
4972
|
-
productId: persistedSub.productId,
|
|
4973
|
-
status: persistedSub.status,
|
|
4974
|
-
expiresAt: persistedSub.expiresAt ?? null,
|
|
4975
|
-
platform: persistedSub.platform,
|
|
4976
|
-
autoRenewEnabled: persistedSub.willRenew,
|
|
4977
|
-
inGracePeriod: persistedSub.status === "grace_period"
|
|
4978
|
-
} : null;
|
|
4979
|
-
const stillActive = isSubscriptionStillActive(sub);
|
|
4980
|
-
if (stillActive) {
|
|
5205
|
+
const sub = persisted.data.subscription ? subscriptionInfoToSub(persisted.data.subscription) : null;
|
|
5206
|
+
if (isSubscriptionStillActive(sub)) {
|
|
4981
5207
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4982
5208
|
setSubscription(sub);
|
|
4983
5209
|
return true;
|
|
@@ -4986,17 +5212,14 @@ function useSubscriptionSync() {
|
|
|
4986
5212
|
} catch {
|
|
4987
5213
|
}
|
|
4988
5214
|
try {
|
|
4989
|
-
const
|
|
4990
|
-
const sub =
|
|
4991
|
-
...status.subscription,
|
|
4992
|
-
expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null
|
|
4993
|
-
} : 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;
|
|
4994
5217
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4995
5218
|
setSubscription(sub);
|
|
4996
|
-
void subscriptionCache.set(distinctId, toDomainResponse(
|
|
4997
|
-
return
|
|
5219
|
+
void subscriptionCache.set(distinctId, toDomainResponse(apiStatus));
|
|
5220
|
+
return apiStatus.hasActiveSubscription === true;
|
|
4998
5221
|
} catch {
|
|
4999
|
-
return
|
|
5222
|
+
return resolveOfflineFromCache(distinctId);
|
|
5000
5223
|
}
|
|
5001
5224
|
}, []);
|
|
5002
5225
|
const getSubscription = (0, import_react17.useCallback)(async () => {
|
|
@@ -5011,16 +5234,7 @@ function useSubscriptionSync() {
|
|
|
5011
5234
|
try {
|
|
5012
5235
|
const persisted = await subscriptionCache.get(distinctId);
|
|
5013
5236
|
if (!persisted?.data.subscription) return null;
|
|
5014
|
-
|
|
5015
|
-
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
5016
|
-
return {
|
|
5017
|
-
productId: s.productId,
|
|
5018
|
-
status: normalisedStatus,
|
|
5019
|
-
expiresAt: s.expiresAt ?? null,
|
|
5020
|
-
platform: s.platform,
|
|
5021
|
-
autoRenewEnabled: s.willRenew,
|
|
5022
|
-
inGracePeriod: normalisedStatus === "in_grace_period"
|
|
5023
|
-
};
|
|
5237
|
+
return subscriptionInfoToSub(persisted.data.subscription);
|
|
5024
5238
|
} catch {
|
|
5025
5239
|
return null;
|
|
5026
5240
|
}
|
|
@@ -5042,6 +5256,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
5042
5256
|
const clearPreloadedWebView = (0, import_react18.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
5043
5257
|
const autoPreloadTriggeredRef = (0, import_react18.useRef)(false);
|
|
5044
5258
|
const preloadedWebViewRef = (0, import_react18.useRef)(preloadedWebView);
|
|
5259
|
+
const pollTimerRef = (0, import_react18.useRef)(null);
|
|
5260
|
+
const pollCancelledRef = (0, import_react18.useRef)(false);
|
|
5045
5261
|
(0, import_react18.useEffect)(() => {
|
|
5046
5262
|
preloadedWebViewRef.current = preloadedWebView;
|
|
5047
5263
|
}, [preloadedWebView]);
|
|
@@ -5050,21 +5266,21 @@ function PaywalloProvider({ children, config }) {
|
|
|
5050
5266
|
});
|
|
5051
5267
|
}, []);
|
|
5052
5268
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
5053
|
-
const
|
|
5054
|
-
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;
|
|
5055
5271
|
const handlePreloadedWebViewReady = (0, import_react18.useCallback)(() => {
|
|
5056
5272
|
if (PaywalloClient.getConfig()?.debug) {
|
|
5057
|
-
console.
|
|
5273
|
+
console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
5058
5274
|
}
|
|
5059
5275
|
baseHandlePreloadedWebViewReady();
|
|
5060
|
-
}, [
|
|
5276
|
+
}, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
5061
5277
|
const handlePreloadedClose = (0, import_react18.useCallback)(() => {
|
|
5062
5278
|
const placement = preloadedWebView?.placement;
|
|
5063
|
-
|
|
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(() => {
|
|
5064
5280
|
baseHandlePreloadedClose();
|
|
5065
5281
|
if (placement) triggerRepreload(placement);
|
|
5066
5282
|
});
|
|
5067
|
-
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim,
|
|
5283
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT]);
|
|
5068
5284
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
5069
5285
|
preloadedWebView,
|
|
5070
5286
|
resolvePreloadedPaywall,
|
|
@@ -5082,21 +5298,28 @@ function PaywalloProvider({ children, config }) {
|
|
|
5082
5298
|
setIsPreloadPurchasing(false);
|
|
5083
5299
|
}
|
|
5084
5300
|
}, [rawPreloadedPurchase]);
|
|
5301
|
+
const configRef = (0, import_react18.useRef)(config);
|
|
5085
5302
|
(0, import_react18.useEffect)(() => {
|
|
5303
|
+
let mounted = true;
|
|
5086
5304
|
initLocalization({ detectDevice: true });
|
|
5087
|
-
PaywalloClient.init(
|
|
5305
|
+
PaywalloClient.init(configRef.current).then(async () => {
|
|
5306
|
+
if (!mounted) return;
|
|
5088
5307
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
5089
5308
|
setIsInitialized(true);
|
|
5090
5309
|
if (!autoPreloadTriggeredRef.current) {
|
|
5091
5310
|
autoPreloadTriggeredRef.current = true;
|
|
5092
5311
|
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
5093
|
-
if (placement) void preloadCampaign(placement).catch(() => {
|
|
5312
|
+
if (mounted && placement) void preloadCampaign(placement).catch(() => {
|
|
5094
5313
|
});
|
|
5095
5314
|
}
|
|
5096
5315
|
}).catch((err) => {
|
|
5316
|
+
if (!mounted) return;
|
|
5097
5317
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
5098
5318
|
});
|
|
5099
|
-
|
|
5319
|
+
return () => {
|
|
5320
|
+
mounted = false;
|
|
5321
|
+
};
|
|
5322
|
+
}, [preloadCampaign]);
|
|
5100
5323
|
const getPaywallConfig = (0, import_react18.useCallback)(async (p) => {
|
|
5101
5324
|
const api = PaywalloClient.getApiClient();
|
|
5102
5325
|
return api ? api.getPaywall(p) : null;
|
|
@@ -5197,15 +5420,17 @@ function PaywalloProvider({ children, config }) {
|
|
|
5197
5420
|
}, [activePaywall]);
|
|
5198
5421
|
const bridgePaywallPresenter = (0, import_react18.useCallback)((placement) => {
|
|
5199
5422
|
if (PaywalloClient.getConfig()?.debug) {
|
|
5200
|
-
console.
|
|
5423
|
+
console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
5201
5424
|
}
|
|
5202
5425
|
if (preloadedWebViewRef.current?.placement === placement) {
|
|
5203
5426
|
if (preloadedWebViewRef.current.loaded) {
|
|
5204
5427
|
return presentPreloadedWebView();
|
|
5205
5428
|
}
|
|
5429
|
+
pollCancelledRef.current = false;
|
|
5206
5430
|
return new Promise((resolve) => {
|
|
5207
5431
|
let elapsed = 0;
|
|
5208
5432
|
const poll = () => {
|
|
5433
|
+
if (pollCancelledRef.current) return;
|
|
5209
5434
|
if (preloadedWebViewRef.current?.loaded) {
|
|
5210
5435
|
resolve(presentPreloadedWebView());
|
|
5211
5436
|
return;
|
|
@@ -5215,9 +5440,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
5215
5440
|
resolve(presentCampaign(placement));
|
|
5216
5441
|
return;
|
|
5217
5442
|
}
|
|
5218
|
-
setTimeout(poll, 100);
|
|
5443
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5219
5444
|
};
|
|
5220
|
-
setTimeout(poll, 100);
|
|
5445
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5221
5446
|
});
|
|
5222
5447
|
}
|
|
5223
5448
|
return presentCampaign(placement);
|
|
@@ -5235,6 +5460,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
5235
5460
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
5236
5461
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
5237
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
|
+
};
|
|
5238
5475
|
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5239
5476
|
const isReady = isInitialized && !isLoadingProducts;
|
|
5240
5477
|
const noOp = (0, import_react18.useCallback)(() => {
|
|
@@ -5275,10 +5512,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
5275
5512
|
]);
|
|
5276
5513
|
(0, import_react18.useLayoutEffect)(() => {
|
|
5277
5514
|
if (showingPreloadedWebView) {
|
|
5278
|
-
slideAnim.setValue(
|
|
5279
|
-
|
|
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();
|
|
5280
5517
|
}
|
|
5281
|
-
}, [showingPreloadedWebView, slideAnim,
|
|
5518
|
+
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
|
|
5282
5519
|
const preloadStyle = [
|
|
5283
5520
|
styles3.preloadOverlay,
|
|
5284
5521
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
@@ -5286,7 +5523,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
5286
5523
|
];
|
|
5287
5524
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
|
|
5288
5525
|
children,
|
|
5289
|
-
/* @__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)(
|
|
5290
5527
|
PaywallWebView,
|
|
5291
5528
|
{
|
|
5292
5529
|
paywallId: preloadedWebView.paywallId,
|
|
@@ -5316,7 +5553,7 @@ function PaywalloProvider({ children, config }) {
|
|
|
5316
5553
|
) })
|
|
5317
5554
|
] });
|
|
5318
5555
|
}
|
|
5319
|
-
var styles3 =
|
|
5556
|
+
var styles3 = import_react_native17.StyleSheet.create({
|
|
5320
5557
|
preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
|
|
5321
5558
|
hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
|
|
5322
5559
|
visible: { zIndex: 9999, opacity: 1 }
|
|
@@ -5445,10 +5682,7 @@ var Paywallo = PaywalloClient;
|
|
|
5445
5682
|
var index_default = Paywallo;
|
|
5446
5683
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5447
5684
|
0 && (module.exports = {
|
|
5448
|
-
AFFILIATE_ERROR_CODES,
|
|
5449
5685
|
ANALYTICS_ERROR_CODES,
|
|
5450
|
-
AffiliateError,
|
|
5451
|
-
AffiliateManager,
|
|
5452
5686
|
AnalyticsError,
|
|
5453
5687
|
ApiClient,
|
|
5454
5688
|
CAMPAIGN_ERROR_CODES,
|
|
@@ -5466,7 +5700,6 @@ var index_default = Paywallo;
|
|
|
5466
5700
|
PaywallError,
|
|
5467
5701
|
PaywallModal,
|
|
5468
5702
|
Paywallo,
|
|
5469
|
-
PaywalloAffiliate,
|
|
5470
5703
|
PaywalloClient,
|
|
5471
5704
|
PaywalloContext,
|
|
5472
5705
|
PaywalloError,
|
|
@@ -5476,7 +5709,6 @@ var index_default = Paywallo;
|
|
|
5476
5709
|
SessionError,
|
|
5477
5710
|
SessionManager,
|
|
5478
5711
|
SubscriptionCache,
|
|
5479
|
-
affiliateManager,
|
|
5480
5712
|
createPurchaseError,
|
|
5481
5713
|
detectDeviceLanguage,
|
|
5482
5714
|
getCurrentLanguage,
|