@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.mjs
CHANGED
|
@@ -63,6 +63,7 @@ var CLIENT_ERROR_CODES = {
|
|
|
63
63
|
MISSING_APP_KEY: "CLIENT_MISSING_APP_KEY",
|
|
64
64
|
INVALID_EVENT_NAME: "CLIENT_INVALID_EVENT_NAME",
|
|
65
65
|
PROVIDER_MISSING: "CLIENT_PROVIDER_MISSING",
|
|
66
|
+
INSECURE_REQUEST: "CLIENT_INSECURE_REQUEST",
|
|
66
67
|
UNKNOWN: "CLIENT_UNKNOWN"
|
|
67
68
|
};
|
|
68
69
|
|
|
@@ -129,10 +130,7 @@ function createPurchaseError(code, message) {
|
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
// src/domains/iap/NativeStoreKit.ts
|
|
132
|
-
import { NativeModules, Platform } from "react-native";
|
|
133
|
-
|
|
134
|
-
// src/domains/identity/IdentityManager.ts
|
|
135
|
-
import DeviceInfo from "react-native-device-info";
|
|
133
|
+
import { NativeModules as NativeModules3, Platform } from "react-native";
|
|
136
134
|
|
|
137
135
|
// src/domains/identity/IdentityError.ts
|
|
138
136
|
var IdentityError = class extends PaywalloError {
|
|
@@ -152,13 +150,141 @@ var IDENTITY_ERROR_CODES = {
|
|
|
152
150
|
// src/domains/identity/IdentityManager.ts
|
|
153
151
|
init_uuid();
|
|
154
152
|
|
|
153
|
+
// src/utils/NativeDeviceInfo.ts
|
|
154
|
+
import { NativeModules } from "react-native";
|
|
155
|
+
var FALLBACK = {
|
|
156
|
+
deviceId: "unknown",
|
|
157
|
+
model: "unknown",
|
|
158
|
+
modelId: "unknown",
|
|
159
|
+
systemVersion: "0.0.0",
|
|
160
|
+
appVersion: "0.0.0",
|
|
161
|
+
buildNumber: "0",
|
|
162
|
+
bundleId: "unknown",
|
|
163
|
+
brand: "unknown",
|
|
164
|
+
systemName: "unknown",
|
|
165
|
+
totalDisk: 0,
|
|
166
|
+
freeDisk: 0,
|
|
167
|
+
totalRam: 0,
|
|
168
|
+
carrier: "unknown",
|
|
169
|
+
installerPackage: null,
|
|
170
|
+
idfv: null
|
|
171
|
+
};
|
|
172
|
+
var _debug = false;
|
|
173
|
+
var warnedOnce = false;
|
|
174
|
+
var cachedDeviceInfo = null;
|
|
175
|
+
function setNativeDeviceInfoDebug(debug) {
|
|
176
|
+
_debug = debug;
|
|
177
|
+
}
|
|
178
|
+
function isDeviceInfoResult(value) {
|
|
179
|
+
if (typeof value !== "object" || value === null) return false;
|
|
180
|
+
const v = value;
|
|
181
|
+
return typeof v["deviceId"] === "string" && typeof v["model"] === "string" && typeof v["systemVersion"] === "string" && typeof v["appVersion"] === "string" && typeof v["bundleId"] === "string";
|
|
182
|
+
}
|
|
183
|
+
function getNativeModule() {
|
|
184
|
+
const mod = NativeModules.PaywalloDevice;
|
|
185
|
+
if (!mod) {
|
|
186
|
+
if (!warnedOnce) {
|
|
187
|
+
warnedOnce = true;
|
|
188
|
+
console.warn("[Paywallo] NativeModules.PaywalloDevice not found \u2014 falling back to defaults");
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
return mod;
|
|
193
|
+
}
|
|
194
|
+
async function getDeviceInfo() {
|
|
195
|
+
if (cachedDeviceInfo !== null) return cachedDeviceInfo;
|
|
196
|
+
const mod = getNativeModule();
|
|
197
|
+
if (!mod) return FALLBACK;
|
|
198
|
+
const raw = await mod.getDeviceInfo();
|
|
199
|
+
if (!isDeviceInfoResult(raw)) return FALLBACK;
|
|
200
|
+
cachedDeviceInfo = raw;
|
|
201
|
+
return raw;
|
|
202
|
+
}
|
|
203
|
+
async function getInstallReferrer() {
|
|
204
|
+
const mod = getNativeModule();
|
|
205
|
+
if (!mod) return null;
|
|
206
|
+
try {
|
|
207
|
+
return await mod.getInstallReferrer();
|
|
208
|
+
} catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function isAvailable() {
|
|
213
|
+
return NativeModules.PaywalloDevice != null;
|
|
214
|
+
}
|
|
215
|
+
function clearCache() {
|
|
216
|
+
cachedDeviceInfo = null;
|
|
217
|
+
}
|
|
218
|
+
var nativeDeviceInfo = {
|
|
219
|
+
getDeviceInfo,
|
|
220
|
+
getInstallReferrer,
|
|
221
|
+
isAvailable,
|
|
222
|
+
clearCache
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/core/storage/NativeStorage.ts
|
|
226
|
+
import { NativeModules as NativeModules2 } from "react-native";
|
|
227
|
+
var NativeModule = NativeModules2.PaywalloStorage;
|
|
228
|
+
var _debug2 = false;
|
|
229
|
+
var _warnedUnavailable = false;
|
|
230
|
+
function setNativeStorageDebug(debug) {
|
|
231
|
+
_debug2 = debug;
|
|
232
|
+
}
|
|
233
|
+
function isAvailable2() {
|
|
234
|
+
return NativeModule != null;
|
|
235
|
+
}
|
|
236
|
+
function warnIfUnavailable() {
|
|
237
|
+
if (NativeModule != null) return true;
|
|
238
|
+
if (!_warnedUnavailable) {
|
|
239
|
+
_warnedUnavailable = true;
|
|
240
|
+
if (_debug2) console.warn("[Paywallo] PaywalloStorage native module not available \u2014 storage will not persist");
|
|
241
|
+
}
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
function getModule() {
|
|
245
|
+
if (!warnIfUnavailable()) return null;
|
|
246
|
+
return NativeModule;
|
|
247
|
+
}
|
|
248
|
+
async function get(key) {
|
|
249
|
+
return getModule()?.get(key) ?? null;
|
|
250
|
+
}
|
|
251
|
+
async function set(key, value) {
|
|
252
|
+
return getModule()?.set(key, value) ?? false;
|
|
253
|
+
}
|
|
254
|
+
async function remove(key) {
|
|
255
|
+
return getModule()?.remove(key) ?? false;
|
|
256
|
+
}
|
|
257
|
+
async function secureGet(key) {
|
|
258
|
+
return getModule()?.secureGet(key) ?? null;
|
|
259
|
+
}
|
|
260
|
+
async function secureSet(key, value) {
|
|
261
|
+
return getModule()?.secureSet(key, value) ?? false;
|
|
262
|
+
}
|
|
263
|
+
async function secureRemove(key) {
|
|
264
|
+
return getModule()?.secureRemove(key) ?? false;
|
|
265
|
+
}
|
|
266
|
+
async function legacySecureGet(key) {
|
|
267
|
+
const mod = getModule();
|
|
268
|
+
if (!mod?.legacySecureGet) return null;
|
|
269
|
+
return mod.legacySecureGet(key);
|
|
270
|
+
}
|
|
271
|
+
var nativeStorage = {
|
|
272
|
+
isAvailable: isAvailable2,
|
|
273
|
+
get,
|
|
274
|
+
set,
|
|
275
|
+
remove,
|
|
276
|
+
secureGet,
|
|
277
|
+
secureSet,
|
|
278
|
+
secureRemove,
|
|
279
|
+
legacySecureGet
|
|
280
|
+
};
|
|
281
|
+
|
|
155
282
|
// src/core/storage/SecureStorage.ts
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
var KEYCHAIN_SERVICE_PREFIX = "com.paywallo.sdk.";
|
|
283
|
+
var KEYCHAIN_KEY_PREFIX = "com.paywallo.sdk.";
|
|
284
|
+
var REGULAR_KEY_PREFIX = "@paywallo:";
|
|
159
285
|
var SecureStorage = class {
|
|
160
286
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
161
|
-
constructor(
|
|
287
|
+
constructor(_debug3 = false) {
|
|
162
288
|
}
|
|
163
289
|
async get(key) {
|
|
164
290
|
const keychainValue = await this.getFromKeychain(key);
|
|
@@ -166,9 +292,10 @@ var SecureStorage = class {
|
|
|
166
292
|
return keychainValue;
|
|
167
293
|
}
|
|
168
294
|
try {
|
|
169
|
-
const value = await
|
|
295
|
+
const value = await nativeStorage.get(`${REGULAR_KEY_PREFIX}${key}`);
|
|
170
296
|
if (value) {
|
|
171
297
|
await this.setToKeychain(key, value);
|
|
298
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0);
|
|
172
299
|
}
|
|
173
300
|
return value;
|
|
174
301
|
} catch {
|
|
@@ -176,20 +303,13 @@ var SecureStorage = class {
|
|
|
176
303
|
}
|
|
177
304
|
}
|
|
178
305
|
async set(key, value) {
|
|
179
|
-
|
|
180
|
-
await Promise.all([
|
|
181
|
-
AsyncStorage.setItem(`@paywallo:${key}`, value),
|
|
182
|
-
this.setToKeychain(key, value)
|
|
183
|
-
]);
|
|
184
|
-
return true;
|
|
185
|
-
} catch {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
306
|
+
return this.setToKeychain(key, value);
|
|
188
307
|
}
|
|
189
308
|
async remove(key) {
|
|
190
309
|
try {
|
|
191
310
|
await Promise.all([
|
|
192
|
-
|
|
311
|
+
// Also remove the legacy plaintext copy if it exists (migration cleanup).
|
|
312
|
+
nativeStorage.remove(`${REGULAR_KEY_PREFIX}${key}`).catch(() => void 0),
|
|
193
313
|
this.removeFromKeychain(key)
|
|
194
314
|
]);
|
|
195
315
|
return true;
|
|
@@ -198,31 +318,25 @@ var SecureStorage = class {
|
|
|
198
318
|
}
|
|
199
319
|
}
|
|
200
320
|
hasKeychainSupport() {
|
|
201
|
-
return
|
|
321
|
+
return nativeStorage.isAvailable();
|
|
202
322
|
}
|
|
203
323
|
async getFromKeychain(key) {
|
|
204
324
|
try {
|
|
205
|
-
|
|
206
|
-
if (result && result.password) {
|
|
207
|
-
return result.password;
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
325
|
+
return await nativeStorage.secureGet(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
210
326
|
} catch {
|
|
211
327
|
return null;
|
|
212
328
|
}
|
|
213
329
|
}
|
|
214
330
|
async setToKeychain(key, value) {
|
|
215
331
|
try {
|
|
216
|
-
await
|
|
217
|
-
return true;
|
|
332
|
+
return await nativeStorage.secureSet(`${KEYCHAIN_KEY_PREFIX}${key}`, value);
|
|
218
333
|
} catch {
|
|
219
334
|
return false;
|
|
220
335
|
}
|
|
221
336
|
}
|
|
222
337
|
async removeFromKeychain(key) {
|
|
223
338
|
try {
|
|
224
|
-
await
|
|
225
|
-
return true;
|
|
339
|
+
return await nativeStorage.secureRemove(`${KEYCHAIN_KEY_PREFIX}${key}`);
|
|
226
340
|
} catch {
|
|
227
341
|
return false;
|
|
228
342
|
}
|
|
@@ -230,7 +344,59 @@ var SecureStorage = class {
|
|
|
230
344
|
};
|
|
231
345
|
var secureStorage = new SecureStorage();
|
|
232
346
|
|
|
347
|
+
// src/core/storage/StorageMigration.ts
|
|
348
|
+
var MIGRATION_FLAG_KEY = "@paywallo:_migration_v152_done";
|
|
349
|
+
var LEGACY_SECURE_KEYS = [
|
|
350
|
+
"device_id",
|
|
351
|
+
"device_id_fallback",
|
|
352
|
+
"anon_id",
|
|
353
|
+
"user_email",
|
|
354
|
+
"user_properties"
|
|
355
|
+
];
|
|
356
|
+
var KEYCHAIN_KEY_PREFIX2 = "com.paywallo.sdk.";
|
|
357
|
+
async function runStorageMigrationIfNeeded() {
|
|
358
|
+
if (!nativeStorage.isAvailable()) return;
|
|
359
|
+
const alreadyDone = await nativeStorage.get(MIGRATION_FLAG_KEY);
|
|
360
|
+
if (alreadyDone === "1") return;
|
|
361
|
+
await migrateAsyncStorage();
|
|
362
|
+
await migrateKeychainLegacy();
|
|
363
|
+
await nativeStorage.set(MIGRATION_FLAG_KEY, "1");
|
|
364
|
+
}
|
|
365
|
+
async function migrateAsyncStorage() {
|
|
366
|
+
try {
|
|
367
|
+
const mod = await import("@react-native-async-storage/async-storage");
|
|
368
|
+
const AsyncStorage = mod.default;
|
|
369
|
+
const allKeys = await AsyncStorage.getAllKeys();
|
|
370
|
+
const paywallo = allKeys.filter((k) => k.startsWith("@paywallo:"));
|
|
371
|
+
if (paywallo.length === 0) return;
|
|
372
|
+
const pairs = await AsyncStorage.multiGet(paywallo);
|
|
373
|
+
for (const [key, value] of pairs) {
|
|
374
|
+
if (!value) continue;
|
|
375
|
+
const existing = await nativeStorage.get(key);
|
|
376
|
+
if (!existing) {
|
|
377
|
+
await nativeStorage.set(key, value);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
} catch {
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async function migrateKeychainLegacy() {
|
|
384
|
+
for (const key of LEGACY_SECURE_KEYS) {
|
|
385
|
+
try {
|
|
386
|
+
const legacyValue = await nativeStorage.legacySecureGet(`${KEYCHAIN_KEY_PREFIX2}${key}`);
|
|
387
|
+
if (!legacyValue) continue;
|
|
388
|
+
const newSlotKey = `${KEYCHAIN_KEY_PREFIX2}${key}`;
|
|
389
|
+
const existing = await nativeStorage.secureGet(newSlotKey);
|
|
390
|
+
if (!existing) {
|
|
391
|
+
await nativeStorage.secureSet(newSlotKey, legacyValue);
|
|
392
|
+
}
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
233
398
|
// src/domains/identity/IdentityManager.ts
|
|
399
|
+
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
234
400
|
var DEVICE_ID_KEY = "device_id";
|
|
235
401
|
var ANON_ID_KEY = "anon_id";
|
|
236
402
|
var USER_EMAIL_KEY = "user_email";
|
|
@@ -245,18 +411,31 @@ var IdentityManager = class {
|
|
|
245
411
|
this.secureStorage = null;
|
|
246
412
|
this.debug = false;
|
|
247
413
|
this.initialized = false;
|
|
414
|
+
this.initPromise = null;
|
|
248
415
|
}
|
|
249
416
|
async initialize(apiClient, debug = false) {
|
|
250
417
|
if (this.initialized) {
|
|
251
418
|
return this.deviceId ?? "";
|
|
252
419
|
}
|
|
420
|
+
if (this.initPromise) {
|
|
421
|
+
return this.initPromise;
|
|
422
|
+
}
|
|
423
|
+
this.initPromise = this._doInitialize(apiClient, debug).finally(() => {
|
|
424
|
+
this.initPromise = null;
|
|
425
|
+
});
|
|
426
|
+
return this.initPromise;
|
|
427
|
+
}
|
|
428
|
+
async _doInitialize(apiClient, debug) {
|
|
253
429
|
this.apiClient = apiClient;
|
|
254
430
|
this.debug = debug;
|
|
255
431
|
this.secureStorage = new SecureStorage(debug);
|
|
432
|
+
await runStorageMigrationIfNeeded();
|
|
256
433
|
await this.loadPersistedState();
|
|
257
434
|
if (!this.deviceId) {
|
|
258
435
|
try {
|
|
259
|
-
|
|
436
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
437
|
+
const deviceId = info.deviceId;
|
|
438
|
+
this.deviceId = !deviceId || deviceId === "unknown" ? generateUUID() : deviceId;
|
|
260
439
|
} catch {
|
|
261
440
|
this.deviceId = generateUUID();
|
|
262
441
|
}
|
|
@@ -287,8 +466,13 @@ var IdentityManager = class {
|
|
|
287
466
|
}
|
|
288
467
|
}
|
|
289
468
|
if (email) {
|
|
290
|
-
|
|
291
|
-
|
|
469
|
+
if (!EMAIL_REGEX.test(email)) {
|
|
470
|
+
if (this.debug) console.warn("[Paywallo] Invalid email format, skipping email field");
|
|
471
|
+
email = void 0;
|
|
472
|
+
} else {
|
|
473
|
+
this.email = email;
|
|
474
|
+
await this.secureStorage.set(USER_EMAIL_KEY, email);
|
|
475
|
+
}
|
|
292
476
|
}
|
|
293
477
|
if (properties) {
|
|
294
478
|
this.properties = { ...this.properties, ...properties };
|
|
@@ -392,8 +576,8 @@ var IdentityManager = class {
|
|
|
392
576
|
var identityManager = new IdentityManager();
|
|
393
577
|
|
|
394
578
|
// src/domains/iap/NativeStoreKit.ts
|
|
395
|
-
var PaywalloStoreKitNative =
|
|
396
|
-
function
|
|
579
|
+
var PaywalloStoreKitNative = NativeModules3.PaywalloStoreKit ?? null;
|
|
580
|
+
function isAvailable3() {
|
|
397
581
|
return PaywalloStoreKitNative != null;
|
|
398
582
|
}
|
|
399
583
|
function mapNativeProduct(native) {
|
|
@@ -469,7 +653,7 @@ function mapNativePurchase(native) {
|
|
|
469
653
|
};
|
|
470
654
|
}
|
|
471
655
|
var nativeStoreKit = {
|
|
472
|
-
isAvailable,
|
|
656
|
+
isAvailable: isAvailable3,
|
|
473
657
|
async getProducts(productIds) {
|
|
474
658
|
if (!PaywalloStoreKitNative) {
|
|
475
659
|
return [];
|
|
@@ -486,8 +670,8 @@ var nativeStoreKit = {
|
|
|
486
670
|
}
|
|
487
671
|
const distinctId = identityManager.getDistinctId();
|
|
488
672
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
489
|
-
if (distinctId && !uuidRegex.test(distinctId)
|
|
490
|
-
console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
|
|
673
|
+
if (distinctId && !uuidRegex.test(distinctId)) {
|
|
674
|
+
if (PaywalloClient.getConfig()?.debug) console.warn("[Paywallo] distinctId is not a valid UUID for appAccountToken:", distinctId);
|
|
491
675
|
}
|
|
492
676
|
try {
|
|
493
677
|
const result = await PaywalloStoreKitNative.purchase(productId, distinctId ?? null);
|
|
@@ -508,7 +692,7 @@ var nativeStoreKit = {
|
|
|
508
692
|
try {
|
|
509
693
|
await PaywalloStoreKitNative.finishTransaction(transactionId);
|
|
510
694
|
} catch (err) {
|
|
511
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
695
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction native error", err);
|
|
512
696
|
}
|
|
513
697
|
},
|
|
514
698
|
async getActiveTransactions() {
|
|
@@ -517,7 +701,7 @@ var nativeStoreKit = {
|
|
|
517
701
|
const results = await PaywalloStoreKitNative.getActiveTransactions();
|
|
518
702
|
return results.map(mapNativePurchase);
|
|
519
703
|
} catch (err) {
|
|
520
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
704
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] getActiveTransactions failed", err);
|
|
521
705
|
return [];
|
|
522
706
|
}
|
|
523
707
|
}
|
|
@@ -528,6 +712,7 @@ var IAPService = class {
|
|
|
528
712
|
constructor(apiClient) {
|
|
529
713
|
this.productsCache = /* @__PURE__ */ new Map();
|
|
530
714
|
this.apiClient = null;
|
|
715
|
+
this.purchaseInFlight = null;
|
|
531
716
|
this.apiClient = apiClient ?? null;
|
|
532
717
|
}
|
|
533
718
|
get debug() {
|
|
@@ -563,7 +748,7 @@ var IAPService = class {
|
|
|
563
748
|
}
|
|
564
749
|
return products;
|
|
565
750
|
} catch (err) {
|
|
566
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
751
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts failed", err);
|
|
567
752
|
return [];
|
|
568
753
|
}
|
|
569
754
|
}
|
|
@@ -609,7 +794,7 @@ var IAPService = class {
|
|
|
609
794
|
try {
|
|
610
795
|
await nativeStoreKit.finishTransaction(transactionId);
|
|
611
796
|
} catch (err) {
|
|
612
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
797
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
613
798
|
}
|
|
614
799
|
}
|
|
615
800
|
async purchase(productId, options) {
|
|
@@ -619,27 +804,36 @@ var IAPService = class {
|
|
|
619
804
|
error: new PurchaseError(PURCHASE_ERROR_CODES.STORE_NOT_AVAILABLE, "In-app purchases are not available on this device")
|
|
620
805
|
};
|
|
621
806
|
}
|
|
807
|
+
if (this.purchaseInFlight) {
|
|
808
|
+
return {
|
|
809
|
+
success: false,
|
|
810
|
+
error: new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, "A purchase is already in progress")
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
this.purchaseInFlight = productId;
|
|
622
814
|
const debug = PaywalloClient.getConfig()?.debug;
|
|
623
|
-
if (debug) console.
|
|
815
|
+
if (debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
|
|
624
816
|
try {
|
|
625
817
|
const purchase = await nativeStoreKit.purchase(productId);
|
|
626
818
|
if (!purchase) {
|
|
627
|
-
if (debug) console.
|
|
819
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
|
|
628
820
|
return { success: false };
|
|
629
821
|
}
|
|
630
|
-
if (debug) console.
|
|
822
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} approved`, { transactionId: purchase.transactionId });
|
|
631
823
|
await this.finishTransaction(purchase.transactionId);
|
|
632
|
-
if (debug) console.
|
|
824
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} finished`);
|
|
633
825
|
this.validateWithServer(purchase, productId, options).then(() => {
|
|
634
|
-
if (debug) console.
|
|
826
|
+
if (debug) console.log(`[Paywallo PURCHASE] ${productId} server validated`);
|
|
635
827
|
}).catch((err) => {
|
|
636
|
-
if (debug) console.
|
|
828
|
+
if (debug) console.error("[Paywallo PURCHASE] background validation failed", err);
|
|
637
829
|
});
|
|
638
830
|
return { success: true, purchase };
|
|
639
831
|
} catch (error) {
|
|
640
|
-
if (debug) console.
|
|
832
|
+
if (debug) console.error(`[Paywallo PURCHASE] ${productId} failed`, error);
|
|
641
833
|
const e = error instanceof PurchaseError ? error : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, error instanceof Error ? error.message : String(error));
|
|
642
834
|
return { success: false, error: e };
|
|
835
|
+
} finally {
|
|
836
|
+
this.purchaseInFlight = null;
|
|
643
837
|
}
|
|
644
838
|
}
|
|
645
839
|
async restore() {
|
|
@@ -674,14 +868,14 @@ var IAPService = class {
|
|
|
674
868
|
try {
|
|
675
869
|
await nativeStoreKit.finishTransaction(tx.transactionId);
|
|
676
870
|
} catch (err) {
|
|
677
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
871
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] finishTransaction failed", err);
|
|
678
872
|
}
|
|
679
873
|
validated.push(tx);
|
|
680
874
|
}
|
|
681
875
|
}
|
|
682
876
|
return validated;
|
|
683
877
|
} catch (err) {
|
|
684
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
878
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] restore failed", err);
|
|
685
879
|
return [];
|
|
686
880
|
}
|
|
687
881
|
}
|
|
@@ -712,7 +906,7 @@ function usePaywallTracking() {
|
|
|
712
906
|
...sessionId && { sessionId }
|
|
713
907
|
});
|
|
714
908
|
} catch (err) {
|
|
715
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
909
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackDismissed failed", err);
|
|
716
910
|
}
|
|
717
911
|
}, []);
|
|
718
912
|
const trackProductSelected = useCallback((opts) => {
|
|
@@ -727,7 +921,7 @@ function usePaywallTracking() {
|
|
|
727
921
|
...opts.campaignId && { campaignId: opts.campaignId },
|
|
728
922
|
...sessionId && { sessionId }
|
|
729
923
|
}).catch((err) => {
|
|
730
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
924
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackProductSelected failed", err);
|
|
731
925
|
});
|
|
732
926
|
}, []);
|
|
733
927
|
const trackPurchased = useCallback(async (opts) => {
|
|
@@ -744,14 +938,13 @@ function usePaywallTracking() {
|
|
|
744
938
|
...sessionId && { sessionId }
|
|
745
939
|
});
|
|
746
940
|
} catch (err) {
|
|
747
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
941
|
+
if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] trackPurchased failed", err);
|
|
748
942
|
}
|
|
749
943
|
}, []);
|
|
750
944
|
return { trackDismissed, trackProductSelected, trackPurchased };
|
|
751
945
|
}
|
|
752
946
|
|
|
753
947
|
// src/domains/paywall/usePaywallActions.ts
|
|
754
|
-
var SCREEN_HEIGHT = Dimensions.get("window").height;
|
|
755
948
|
function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim) {
|
|
756
949
|
const [isPurchasing, setIsPurchasing] = useState(false);
|
|
757
950
|
const [isClosing, setIsClosing] = useState(false);
|
|
@@ -762,7 +955,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
|
|
|
762
955
|
if (isClosing) return;
|
|
763
956
|
setIsClosing(true);
|
|
764
957
|
const animTarget = openAnim ?? slideAnim;
|
|
765
|
-
Animated.timing(animTarget, { toValue:
|
|
958
|
+
Animated.timing(animTarget, { toValue: Dimensions.get("window").height, duration: 350, useNativeDriver: true }).start(() => {
|
|
766
959
|
if (paywallConfig) {
|
|
767
960
|
const durationSeconds = Math.round((Date.now() - presentedAtRef.current) / 1e3);
|
|
768
961
|
void trackDismissed({ placement, paywallId: paywallConfig.id, durationSeconds, action: "close", variantKey, campaignId });
|
|
@@ -851,6 +1044,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
851
1044
|
trackingDoneRef.current = false;
|
|
852
1045
|
return;
|
|
853
1046
|
}
|
|
1047
|
+
let cancelled = false;
|
|
854
1048
|
const load = async () => {
|
|
855
1049
|
setError(null);
|
|
856
1050
|
try {
|
|
@@ -871,27 +1065,30 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
871
1065
|
if (!fetched) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_FOUND, `Paywall not found for placement: ${placement}`);
|
|
872
1066
|
config = fetched;
|
|
873
1067
|
}
|
|
1068
|
+
if (cancelled) return;
|
|
874
1069
|
setPaywallConfig(config);
|
|
875
1070
|
const productIds = [];
|
|
876
1071
|
if (config.primaryProductId) productIds.push(config.primaryProductId);
|
|
877
1072
|
if (config.secondaryProductId) productIds.push(config.secondaryProductId);
|
|
878
1073
|
const snapProducts = preloadedProductsRef.current;
|
|
879
1074
|
if (snapProducts && snapProducts.size > 0) {
|
|
880
|
-
setProducts(snapProducts);
|
|
1075
|
+
if (!cancelled) setProducts(snapProducts);
|
|
881
1076
|
} else if (productIds.length > 0) {
|
|
882
1077
|
try {
|
|
883
1078
|
const storeProducts = await getIAPService().loadProducts(productIds);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1079
|
+
if (!cancelled) {
|
|
1080
|
+
const map = /* @__PURE__ */ new Map();
|
|
1081
|
+
for (const p of storeProducts) map.set(p.productId, p);
|
|
1082
|
+
setProducts(map);
|
|
1083
|
+
}
|
|
887
1084
|
} catch (err) {
|
|
888
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
889
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1085
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] loadProducts in paywall failed", err);
|
|
1086
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
890
1087
|
}
|
|
891
1088
|
} else {
|
|
892
|
-
setProducts(/* @__PURE__ */ new Map());
|
|
1089
|
+
if (!cancelled) setProducts(/* @__PURE__ */ new Map());
|
|
893
1090
|
}
|
|
894
|
-
if (!trackingDoneRef.current) {
|
|
1091
|
+
if (!trackingDoneRef.current && !cancelled) {
|
|
895
1092
|
trackingDoneRef.current = true;
|
|
896
1093
|
const sessionId = PaywalloClient.getSessionId();
|
|
897
1094
|
await apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
|
|
@@ -903,17 +1100,21 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
|
|
|
903
1100
|
});
|
|
904
1101
|
}
|
|
905
1102
|
} catch (err) {
|
|
1103
|
+
if (cancelled) return;
|
|
906
1104
|
const e = err instanceof Error ? err : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(err));
|
|
907
1105
|
setError(e);
|
|
908
1106
|
}
|
|
909
1107
|
};
|
|
910
1108
|
void load();
|
|
1109
|
+
return () => {
|
|
1110
|
+
cancelled = true;
|
|
1111
|
+
};
|
|
911
1112
|
}, [placement, visible, variantKey, campaignId]);
|
|
912
1113
|
return { paywallConfig, products, error };
|
|
913
1114
|
}
|
|
914
1115
|
|
|
915
1116
|
// src/utils/localization.ts
|
|
916
|
-
import { NativeModules as
|
|
1117
|
+
import { NativeModules as NativeModules4, Platform as Platform3 } from "react-native";
|
|
917
1118
|
var currentLanguage = "pt-BR";
|
|
918
1119
|
var defaultLanguage = "pt-BR";
|
|
919
1120
|
function setCurrentLanguage(language) {
|
|
@@ -936,10 +1137,10 @@ function getLocalizedString(text) {
|
|
|
936
1137
|
function detectDeviceLanguage() {
|
|
937
1138
|
try {
|
|
938
1139
|
if (Platform3.OS === "ios") {
|
|
939
|
-
const settings =
|
|
1140
|
+
const settings = NativeModules4.SettingsManager?.settings;
|
|
940
1141
|
return settings?.AppleLocale ?? settings?.AppleLanguages?.[0] ?? "en-US";
|
|
941
1142
|
}
|
|
942
|
-
return
|
|
1143
|
+
return NativeModules4.I18nManager?.localeIdentifier ?? "en-US";
|
|
943
1144
|
} catch {
|
|
944
1145
|
return "en-US";
|
|
945
1146
|
}
|
|
@@ -975,9 +1176,8 @@ var SDK_STRINGS = {
|
|
|
975
1176
|
};
|
|
976
1177
|
function getSdkString(key) {
|
|
977
1178
|
const map = SDK_STRINGS[key];
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const base = lang.split("-")[0];
|
|
1179
|
+
if (currentLanguage in map) return map[currentLanguage];
|
|
1180
|
+
const base = currentLanguage.split("-")[0];
|
|
981
1181
|
if (base && base in map) return map[base];
|
|
982
1182
|
if (defaultLanguage in map) return map[defaultLanguage];
|
|
983
1183
|
return Object.values(map)[0];
|
|
@@ -997,13 +1197,21 @@ import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef a
|
|
|
997
1197
|
import {
|
|
998
1198
|
Linking,
|
|
999
1199
|
NativeEventEmitter,
|
|
1000
|
-
NativeModules as
|
|
1200
|
+
NativeModules as NativeModules5,
|
|
1001
1201
|
requireNativeComponent,
|
|
1002
1202
|
StyleSheet,
|
|
1003
1203
|
View
|
|
1004
1204
|
} from "react-native";
|
|
1005
1205
|
|
|
1006
1206
|
// src/domains/paywall/parseWebViewMessage.ts
|
|
1207
|
+
var ALLOWED_MESSAGE_TYPES = /* @__PURE__ */ new Set([
|
|
1208
|
+
"purchase",
|
|
1209
|
+
"close",
|
|
1210
|
+
"restore",
|
|
1211
|
+
"select-product",
|
|
1212
|
+
"open-url",
|
|
1213
|
+
"ready"
|
|
1214
|
+
]);
|
|
1007
1215
|
function deriveMessageId(message) {
|
|
1008
1216
|
if (message.id) return message.id;
|
|
1009
1217
|
const payloadKey = message.payload?.productId ?? message.payload?.url ?? (message.payload?.timestamp != null ? String(message.payload.timestamp) : "");
|
|
@@ -1012,10 +1220,20 @@ function deriveMessageId(message) {
|
|
|
1012
1220
|
function parseWebViewMessage(raw) {
|
|
1013
1221
|
try {
|
|
1014
1222
|
const parsed = JSON.parse(raw);
|
|
1015
|
-
if (typeof parsed?.type !== "string") {
|
|
1223
|
+
if (typeof parsed?.type !== "string" || !ALLOWED_MESSAGE_TYPES.has(parsed.type)) {
|
|
1016
1224
|
return null;
|
|
1017
1225
|
}
|
|
1018
|
-
|
|
1226
|
+
const payload = parsed.payload;
|
|
1227
|
+
const safePayload = payload !== null && typeof payload === "object" ? {
|
|
1228
|
+
productId: typeof payload.productId === "string" ? String(payload.productId) : void 0,
|
|
1229
|
+
url: typeof payload.url === "string" ? String(payload.url) : void 0,
|
|
1230
|
+
timestamp: typeof payload.timestamp === "number" ? Number(payload.timestamp) : void 0
|
|
1231
|
+
} : void 0;
|
|
1232
|
+
return {
|
|
1233
|
+
type: parsed.type,
|
|
1234
|
+
id: typeof parsed.id === "string" ? parsed.id : void 0,
|
|
1235
|
+
payload: safePayload
|
|
1236
|
+
};
|
|
1019
1237
|
} catch {
|
|
1020
1238
|
return null;
|
|
1021
1239
|
}
|
|
@@ -1050,7 +1268,7 @@ function getNativeWebView() {
|
|
|
1050
1268
|
var webViewEmitter = null;
|
|
1051
1269
|
function getWebViewEmitter() {
|
|
1052
1270
|
if (!webViewEmitter) {
|
|
1053
|
-
webViewEmitter = new NativeEventEmitter(
|
|
1271
|
+
webViewEmitter = new NativeEventEmitter(NativeModules5.PaywalloWebViewModule);
|
|
1054
1272
|
}
|
|
1055
1273
|
return webViewEmitter;
|
|
1056
1274
|
}
|
|
@@ -1125,12 +1343,24 @@ function PaywallWebView({
|
|
|
1125
1343
|
setScriptToInject(script);
|
|
1126
1344
|
}, [craftData, products, primaryProductId, secondaryProductId]);
|
|
1127
1345
|
const processedIdsRef = useRef3(/* @__PURE__ */ new Set());
|
|
1346
|
+
const dedupeTimersRef = useRef3(/* @__PURE__ */ new Map());
|
|
1347
|
+
useEffect2(() => {
|
|
1348
|
+
return () => {
|
|
1349
|
+
for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
|
|
1350
|
+
dedupeTimersRef.current.clear();
|
|
1351
|
+
processedIdsRef.current.clear();
|
|
1352
|
+
};
|
|
1353
|
+
}, []);
|
|
1128
1354
|
const handleParsedMessage = useCallback3(
|
|
1129
1355
|
(message) => {
|
|
1130
1356
|
const msgId = deriveMessageId(message);
|
|
1131
1357
|
if (processedIdsRef.current.has(msgId)) return;
|
|
1132
1358
|
processedIdsRef.current.add(msgId);
|
|
1133
|
-
setTimeout(() =>
|
|
1359
|
+
const timerId = setTimeout(() => {
|
|
1360
|
+
processedIdsRef.current.delete(msgId);
|
|
1361
|
+
dedupeTimersRef.current.delete(msgId);
|
|
1362
|
+
}, 5e3);
|
|
1363
|
+
dedupeTimersRef.current.set(msgId, timerId);
|
|
1134
1364
|
switch (message.type) {
|
|
1135
1365
|
case "ready":
|
|
1136
1366
|
if (!paywallDataSent.current) {
|
|
@@ -1151,7 +1381,7 @@ function PaywallWebView({
|
|
|
1151
1381
|
case "open-url":
|
|
1152
1382
|
if (message.payload?.url) {
|
|
1153
1383
|
const url = message.payload.url;
|
|
1154
|
-
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url)) {
|
|
1384
|
+
if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url) || /^itms-apps:/i.test(url) || /^itms-services:/i.test(url) || /^market:/i.test(url)) {
|
|
1155
1385
|
void Linking.openURL(url);
|
|
1156
1386
|
}
|
|
1157
1387
|
}
|
|
@@ -1249,7 +1479,6 @@ var styles = StyleSheet.create({
|
|
|
1249
1479
|
|
|
1250
1480
|
// src/domains/paywall/PaywallModal.tsx
|
|
1251
1481
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1252
|
-
var SCREEN_HEIGHT2 = Dimensions2.get("window").height;
|
|
1253
1482
|
function ErrorFallback({ description, onRetry, onClose }) {
|
|
1254
1483
|
const overrides = PaywalloClient.getConfig()?.errorStrings;
|
|
1255
1484
|
const title = overrides?.title ?? getSdkString("paywallErrorTitle");
|
|
@@ -1279,7 +1508,8 @@ function PaywallModal({
|
|
|
1279
1508
|
variantKey,
|
|
1280
1509
|
campaignId
|
|
1281
1510
|
);
|
|
1282
|
-
const
|
|
1511
|
+
const screenHeight = Dimensions2.get("window").height;
|
|
1512
|
+
const openAnim = useRef4(new Animated2.Value(screenHeight)).current;
|
|
1283
1513
|
const [modalVisible, setModalVisible] = useState4(false);
|
|
1284
1514
|
const handleResult = useCallback4((result) => {
|
|
1285
1515
|
setModalVisible(false);
|
|
@@ -1308,7 +1538,7 @@ function PaywallModal({
|
|
|
1308
1538
|
useEffect3(() => {
|
|
1309
1539
|
if (visible) {
|
|
1310
1540
|
setModalVisible(true);
|
|
1311
|
-
openAnim.setValue(
|
|
1541
|
+
openAnim.setValue(Dimensions2.get("window").height);
|
|
1312
1542
|
Animated2.timing(openAnim, {
|
|
1313
1543
|
toValue: 0,
|
|
1314
1544
|
duration: 550,
|
|
@@ -1458,6 +1688,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1458
1688
|
resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
|
|
1459
1689
|
resolverRef.current = null;
|
|
1460
1690
|
}
|
|
1691
|
+
setShowingPreloadedWebView(false);
|
|
1461
1692
|
};
|
|
1462
1693
|
}, []);
|
|
1463
1694
|
const presentPreloadedWebView = useCallback5(() => {
|
|
@@ -1502,230 +1733,10 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
|
|
|
1502
1733
|
return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
|
|
1503
1734
|
}
|
|
1504
1735
|
|
|
1505
|
-
// src/domains/affiliate/AffiliateError.ts
|
|
1506
|
-
var AffiliateError = class extends PaywalloError {
|
|
1507
|
-
constructor(code, message) {
|
|
1508
|
-
super("affiliate", code, message);
|
|
1509
|
-
this.name = "AffiliateError";
|
|
1510
|
-
}
|
|
1511
|
-
};
|
|
1512
|
-
var AFFILIATE_ERROR_CODES = {
|
|
1513
|
-
NOT_INITIALIZED: "AFFILIATE_NOT_INITIALIZED",
|
|
1514
|
-
COUPON_REGISTER_FAILED: "AFFILIATE_COUPON_REGISTER_FAILED",
|
|
1515
|
-
COUPON_APPLY_FAILED: "AFFILIATE_COUPON_APPLY_FAILED",
|
|
1516
|
-
BALANCE_FETCH_FAILED: "AFFILIATE_BALANCE_FETCH_FAILED",
|
|
1517
|
-
WITHDRAWAL_FAILED: "AFFILIATE_WITHDRAWAL_FAILED",
|
|
1518
|
-
NETWORK_ERROR: "AFFILIATE_NETWORK_ERROR"
|
|
1519
|
-
};
|
|
1520
|
-
|
|
1521
|
-
// src/domains/affiliate/affiliateHttp.ts
|
|
1522
|
-
function buildQueryString(params) {
|
|
1523
|
-
if (!params) return "";
|
|
1524
|
-
const entries = Object.entries(params).filter(([, v]) => v !== void 0 && v !== null);
|
|
1525
|
-
if (entries.length === 0) return "";
|
|
1526
|
-
return `?${entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&")}`;
|
|
1527
|
-
}
|
|
1528
|
-
async function affiliateGet(apiClient, path, params) {
|
|
1529
|
-
const response = await apiClient.get(`${path}${buildQueryString(params)}`);
|
|
1530
|
-
return response.data;
|
|
1531
|
-
}
|
|
1532
|
-
async function affiliatePost(apiClient, path, body) {
|
|
1533
|
-
const response = await apiClient.post(path, body);
|
|
1534
|
-
return response.data;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
// src/domains/affiliate/affiliateDateMappers.ts
|
|
1538
|
-
function mapRawCoupon(raw) {
|
|
1539
|
-
return { code: raw.code, status: raw.status, createdAt: new Date(raw.createdAt) };
|
|
1540
|
-
}
|
|
1541
|
-
function mapRawCommission(raw) {
|
|
1542
|
-
return { ...raw, createdAt: new Date(raw.createdAt) };
|
|
1543
|
-
}
|
|
1544
|
-
function mapRawWithdrawal(raw) {
|
|
1545
|
-
return { ...raw, createdAt: new Date(raw.createdAt) };
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// src/domains/affiliate/AffiliateManager.ts
|
|
1549
|
-
var AffiliateManager = class {
|
|
1550
|
-
constructor() {
|
|
1551
|
-
this.apiClient = null;
|
|
1552
|
-
this.debug = false;
|
|
1553
|
-
}
|
|
1554
|
-
initialize(apiClient, debug = false) {
|
|
1555
|
-
this.apiClient = apiClient;
|
|
1556
|
-
this.debug = debug;
|
|
1557
|
-
this.log("AffiliateManager initialized");
|
|
1558
|
-
}
|
|
1559
|
-
async registerCoupon(code) {
|
|
1560
|
-
this.ensureInitialized();
|
|
1561
|
-
const distinctId = identityManager.getDistinctId();
|
|
1562
|
-
try {
|
|
1563
|
-
const response = await this.post("/api/v1/affiliate/register-coupon", {
|
|
1564
|
-
code,
|
|
1565
|
-
distinctId
|
|
1566
|
-
});
|
|
1567
|
-
if (response.success && response.coupon) {
|
|
1568
|
-
this.log("Coupon registered:", code);
|
|
1569
|
-
return { success: true, coupon: mapRawCoupon(response.coupon) };
|
|
1570
|
-
}
|
|
1571
|
-
return {
|
|
1572
|
-
success: false,
|
|
1573
|
-
error: response.error
|
|
1574
|
-
};
|
|
1575
|
-
} catch (error) {
|
|
1576
|
-
this.log("Failed to register coupon:", error);
|
|
1577
|
-
return {
|
|
1578
|
-
success: false,
|
|
1579
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1580
|
-
};
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
async applyCoupon(code) {
|
|
1584
|
-
this.ensureInitialized();
|
|
1585
|
-
const distinctId = identityManager.getDistinctId();
|
|
1586
|
-
try {
|
|
1587
|
-
const response = await this.post("/api/v1/affiliate/apply-coupon", {
|
|
1588
|
-
code,
|
|
1589
|
-
distinctId
|
|
1590
|
-
});
|
|
1591
|
-
if (response.success) {
|
|
1592
|
-
this.log("Coupon applied:", code);
|
|
1593
|
-
}
|
|
1594
|
-
return response;
|
|
1595
|
-
} catch (error) {
|
|
1596
|
-
this.log("Failed to apply coupon:", error);
|
|
1597
|
-
return {
|
|
1598
|
-
success: false,
|
|
1599
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1600
|
-
};
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
async getMyCoupon() {
|
|
1604
|
-
this.ensureInitialized();
|
|
1605
|
-
const distinctId = identityManager.getDistinctId();
|
|
1606
|
-
try {
|
|
1607
|
-
const response = await this.get("/api/v1/affiliate/my-coupon", { distinctId });
|
|
1608
|
-
if (response.coupon) {
|
|
1609
|
-
return { coupon: mapRawCoupon(response.coupon) };
|
|
1610
|
-
}
|
|
1611
|
-
return { coupon: null };
|
|
1612
|
-
} catch (error) {
|
|
1613
|
-
this.log("Failed to get my coupon:", error);
|
|
1614
|
-
return { coupon: null };
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
async getAppliedCoupon() {
|
|
1618
|
-
this.ensureInitialized();
|
|
1619
|
-
const distinctId = identityManager.getDistinctId();
|
|
1620
|
-
try {
|
|
1621
|
-
const response = await this.get("/api/v1/affiliate/applied-coupon", { distinctId });
|
|
1622
|
-
return response;
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
this.log("Failed to get applied coupon:", error);
|
|
1625
|
-
return { coupon: null };
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
async getBalance() {
|
|
1629
|
-
this.ensureInitialized();
|
|
1630
|
-
const distinctId = identityManager.getDistinctId();
|
|
1631
|
-
try {
|
|
1632
|
-
const response = await this.get("/api/v1/affiliate/balance", {
|
|
1633
|
-
distinctId
|
|
1634
|
-
});
|
|
1635
|
-
return response;
|
|
1636
|
-
} catch (error) {
|
|
1637
|
-
this.log("Failed to get balance:", error);
|
|
1638
|
-
return {
|
|
1639
|
-
total: 0,
|
|
1640
|
-
pending: 0,
|
|
1641
|
-
available: 0,
|
|
1642
|
-
withdrawn: 0
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
async getCommissions() {
|
|
1647
|
-
this.ensureInitialized();
|
|
1648
|
-
const distinctId = identityManager.getDistinctId();
|
|
1649
|
-
try {
|
|
1650
|
-
const response = await this.get("/api/v1/affiliate/commissions", { distinctId });
|
|
1651
|
-
return response.commissions.map(mapRawCommission);
|
|
1652
|
-
} catch (error) {
|
|
1653
|
-
this.log("Failed to get commissions:", error);
|
|
1654
|
-
return [];
|
|
1655
|
-
}
|
|
1656
|
-
}
|
|
1657
|
-
async requestWithdrawal(amount) {
|
|
1658
|
-
this.ensureInitialized();
|
|
1659
|
-
const distinctId = identityManager.getDistinctId();
|
|
1660
|
-
try {
|
|
1661
|
-
const response = await this.post("/api/v1/affiliate/withdraw", {
|
|
1662
|
-
amount,
|
|
1663
|
-
distinctId
|
|
1664
|
-
});
|
|
1665
|
-
if (response.success && response.withdrawal) {
|
|
1666
|
-
this.log("Withdrawal requested:", amount);
|
|
1667
|
-
return { success: true, withdrawal: mapRawWithdrawal(response.withdrawal) };
|
|
1668
|
-
}
|
|
1669
|
-
return {
|
|
1670
|
-
success: false,
|
|
1671
|
-
error: response.error
|
|
1672
|
-
};
|
|
1673
|
-
} catch (error) {
|
|
1674
|
-
this.log("Failed to request withdrawal:", error);
|
|
1675
|
-
return {
|
|
1676
|
-
success: false,
|
|
1677
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1678
|
-
};
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
async getWithdrawals() {
|
|
1682
|
-
this.ensureInitialized();
|
|
1683
|
-
const distinctId = identityManager.getDistinctId();
|
|
1684
|
-
try {
|
|
1685
|
-
const response = await this.get("/api/v1/affiliate/withdrawals", { distinctId });
|
|
1686
|
-
return response.withdrawals.map(mapRawWithdrawal);
|
|
1687
|
-
} catch (error) {
|
|
1688
|
-
this.log("Failed to get withdrawals:", error);
|
|
1689
|
-
return [];
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
async get(path, params) {
|
|
1693
|
-
this.ensureInitialized();
|
|
1694
|
-
return affiliateGet(this.apiClient, path, params);
|
|
1695
|
-
}
|
|
1696
|
-
async post(path, body) {
|
|
1697
|
-
this.ensureInitialized();
|
|
1698
|
-
return affiliatePost(this.apiClient, path, body);
|
|
1699
|
-
}
|
|
1700
|
-
ensureInitialized() {
|
|
1701
|
-
if (!this.apiClient) {
|
|
1702
|
-
throw new AffiliateError(
|
|
1703
|
-
AFFILIATE_ERROR_CODES.NOT_INITIALIZED,
|
|
1704
|
-
"AffiliateManager not initialized. Call initialize() first"
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
log(...args) {
|
|
1709
|
-
if (this.debug) console.log("[Paywallo:Affiliate]", ...args);
|
|
1710
|
-
}
|
|
1711
|
-
};
|
|
1712
|
-
var affiliateManager = new AffiliateManager();
|
|
1713
|
-
|
|
1714
|
-
// src/domains/affiliate/PaywalloAffiliate.ts
|
|
1715
|
-
var PaywalloAffiliate = {
|
|
1716
|
-
registerCoupon: (code) => affiliateManager.registerCoupon(code),
|
|
1717
|
-
applyCoupon: (code) => affiliateManager.applyCoupon(code),
|
|
1718
|
-
getMyCoupon: () => affiliateManager.getMyCoupon(),
|
|
1719
|
-
getAppliedCoupon: () => affiliateManager.getAppliedCoupon(),
|
|
1720
|
-
getBalance: () => affiliateManager.getBalance(),
|
|
1721
|
-
getCommissions: () => affiliateManager.getCommissions(),
|
|
1722
|
-
requestWithdrawal: (amount) => affiliateManager.requestWithdrawal(amount),
|
|
1723
|
-
getWithdrawals: () => affiliateManager.getWithdrawals()
|
|
1724
|
-
};
|
|
1725
|
-
|
|
1726
1736
|
// src/domains/subscription/SubscriptionCache.ts
|
|
1727
|
-
var LEGACY_CACHE_KEY = "subscription_cache";
|
|
1737
|
+
var LEGACY_CACHE_KEY = "@panel:subscription_cache";
|
|
1728
1738
|
var CACHE_KEY_PREFIX = "subscription_cache:";
|
|
1739
|
+
var CACHE_INDEX_KEY = "subscription_cache:__index__";
|
|
1729
1740
|
var DEFAULT_TTL = 24 * 60 * 60 * 1e3;
|
|
1730
1741
|
function keyFor(distinctId) {
|
|
1731
1742
|
return `${CACHE_KEY_PREFIX}${distinctId}`;
|
|
@@ -1805,9 +1816,30 @@ var SubscriptionCache = class {
|
|
|
1805
1816
|
this.memoryCache.set(distinctId, cached);
|
|
1806
1817
|
try {
|
|
1807
1818
|
await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
|
|
1819
|
+
await this.addToIndex(distinctId);
|
|
1820
|
+
} catch {
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async addToIndex(distinctId) {
|
|
1824
|
+
try {
|
|
1825
|
+
const raw = await this.storage.get(CACHE_INDEX_KEY);
|
|
1826
|
+
const ids = raw ? JSON.parse(raw) : [];
|
|
1827
|
+
if (!ids.includes(distinctId)) {
|
|
1828
|
+
ids.push(distinctId);
|
|
1829
|
+
await this.storage.set(CACHE_INDEX_KEY, JSON.stringify(ids));
|
|
1830
|
+
}
|
|
1808
1831
|
} catch {
|
|
1809
1832
|
}
|
|
1810
1833
|
}
|
|
1834
|
+
async readIndex() {
|
|
1835
|
+
try {
|
|
1836
|
+
const raw = await this.storage.get(CACHE_INDEX_KEY);
|
|
1837
|
+
if (!raw) return [];
|
|
1838
|
+
return JSON.parse(raw);
|
|
1839
|
+
} catch {
|
|
1840
|
+
return [];
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1811
1843
|
async invalidate(distinctId) {
|
|
1812
1844
|
if (!distinctId) return;
|
|
1813
1845
|
this.memoryCache.delete(distinctId);
|
|
@@ -1817,11 +1849,15 @@ var SubscriptionCache = class {
|
|
|
1817
1849
|
}
|
|
1818
1850
|
}
|
|
1819
1851
|
async invalidateAll() {
|
|
1852
|
+
const memKeys = Array.from(this.memoryCache.keys());
|
|
1853
|
+
const indexKeys = await this.readIndex();
|
|
1854
|
+
const allKeys = Array.from(/* @__PURE__ */ new Set([...memKeys, ...indexKeys]));
|
|
1820
1855
|
this.memoryCache.clear();
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1856
|
+
await Promise.allSettled([
|
|
1857
|
+
this.storage.remove(LEGACY_CACHE_KEY),
|
|
1858
|
+
this.storage.remove(CACHE_INDEX_KEY),
|
|
1859
|
+
...allKeys.map((k) => this.storage.remove(keyFor(k)))
|
|
1860
|
+
]);
|
|
1825
1861
|
}
|
|
1826
1862
|
isExpired(cached) {
|
|
1827
1863
|
return Date.now() - cached.cachedAt > this.ttl;
|
|
@@ -1832,9 +1868,6 @@ var SubscriptionCache = class {
|
|
|
1832
1868
|
};
|
|
1833
1869
|
var subscriptionCache = new SubscriptionCache();
|
|
1834
1870
|
|
|
1835
|
-
// src/domains/subscription/SubscriptionManager.ts
|
|
1836
|
-
import { Platform as Platform4 } from "react-native";
|
|
1837
|
-
|
|
1838
1871
|
// src/domains/session/SessionError.ts
|
|
1839
1872
|
var SessionError = class extends PaywalloError {
|
|
1840
1873
|
constructor(code, message) {
|
|
@@ -1922,11 +1955,10 @@ var SubscriptionManagerClass = class {
|
|
|
1922
1955
|
if (!this.config) {
|
|
1923
1956
|
return this.getEmptyStatus();
|
|
1924
1957
|
}
|
|
1925
|
-
const url = new URL(`${this.config.serverUrl}/
|
|
1958
|
+
const url = new URL(`${this.config.serverUrl}/purchases/status/${encodeURIComponent(this.config.appKey)}`);
|
|
1926
1959
|
if (this.userId) {
|
|
1927
|
-
url.searchParams.set("
|
|
1960
|
+
url.searchParams.set("distinctId", this.userId);
|
|
1928
1961
|
}
|
|
1929
|
-
url.searchParams.set("platform", Platform4.OS);
|
|
1930
1962
|
const response = await fetch(url.toString(), {
|
|
1931
1963
|
method: "GET",
|
|
1932
1964
|
headers: {
|
|
@@ -2029,7 +2061,7 @@ async function validatePurchase(client, appKey, platform, receipt, productId, tr
|
|
|
2029
2061
|
false
|
|
2030
2062
|
);
|
|
2031
2063
|
if (!response.ok) {
|
|
2032
|
-
throw new
|
|
2064
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.VALIDATION_FAILED, `Server validation failed: HTTP ${response.status}`);
|
|
2033
2065
|
}
|
|
2034
2066
|
return response.data;
|
|
2035
2067
|
}
|
|
@@ -2038,7 +2070,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
|
|
|
2038
2070
|
url.searchParams.set("distinctId", distinctId);
|
|
2039
2071
|
const response = await client.get(url.toString());
|
|
2040
2072
|
if (!response.ok) {
|
|
2041
|
-
throw new
|
|
2073
|
+
throw new PurchaseError(PURCHASE_ERROR_CODES.NETWORK_ERROR, `Subscription status check failed: HTTP ${response.status}`);
|
|
2042
2074
|
}
|
|
2043
2075
|
return response.data;
|
|
2044
2076
|
}
|
|
@@ -2080,7 +2112,7 @@ var HttpClient = class {
|
|
|
2080
2112
|
constructor(baseUrl, config) {
|
|
2081
2113
|
this.config = {
|
|
2082
2114
|
baseUrl,
|
|
2083
|
-
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout,
|
|
2115
|
+
timeout: config?.timeout ?? DEFAULT_HTTP_CONFIG.timeout ?? 1e4,
|
|
2084
2116
|
retry: { ...DEFAULT_RETRY_CONFIG, ...config?.retry },
|
|
2085
2117
|
debug: config?.debug ?? false
|
|
2086
2118
|
};
|
|
@@ -2110,7 +2142,7 @@ var HttpClient = class {
|
|
|
2110
2142
|
if (this.shouldRetry(response.status, retryConfig)) {
|
|
2111
2143
|
if (state.attempt < retryConfig.maxRetries) {
|
|
2112
2144
|
state.attempt++;
|
|
2113
|
-
const delay = this.
|
|
2145
|
+
const delay = this.calculateDelayForResponse(state.attempt, response.headers, retryConfig);
|
|
2114
2146
|
await this.sleep(delay);
|
|
2115
2147
|
continue;
|
|
2116
2148
|
}
|
|
@@ -2164,7 +2196,7 @@ var HttpClient = class {
|
|
|
2164
2196
|
};
|
|
2165
2197
|
} catch (error) {
|
|
2166
2198
|
this.log("Request failed", {
|
|
2167
|
-
url,
|
|
2199
|
+
url: this.redactUrl(url),
|
|
2168
2200
|
error: error instanceof Error ? error.message : String(error)
|
|
2169
2201
|
});
|
|
2170
2202
|
throw error;
|
|
@@ -2177,6 +2209,14 @@ var HttpClient = class {
|
|
|
2177
2209
|
}
|
|
2178
2210
|
buildUrl(path) {
|
|
2179
2211
|
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
2212
|
+
if (path.startsWith("http://")) {
|
|
2213
|
+
const isLocalDev = /^http:\/\/(localhost|127\.0\.0\.1|192\.168\.\d{1,3}\.\d{1,3})/.test(path);
|
|
2214
|
+
if (!isLocalDev) {
|
|
2215
|
+
const sanitizedPath = path.split("?")[0];
|
|
2216
|
+
this.log("Blocked http:// request \u2014 only HTTPS is allowed in production:", sanitizedPath);
|
|
2217
|
+
throw new ClientError(CLIENT_ERROR_CODES.INSECURE_REQUEST, `Insecure HTTP request blocked: ${sanitizedPath}`);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2180
2220
|
return path;
|
|
2181
2221
|
}
|
|
2182
2222
|
return `${this.config.baseUrl}${path}`;
|
|
@@ -2216,9 +2256,28 @@ var HttpClient = class {
|
|
|
2216
2256
|
const jitter = Math.random() * 0.3 * exponentialDelay;
|
|
2217
2257
|
return Math.min(exponentialDelay + jitter, config.maxDelay);
|
|
2218
2258
|
}
|
|
2259
|
+
calculateDelayForResponse(attempt, headers, config) {
|
|
2260
|
+
const retryAfter = headers["retry-after"];
|
|
2261
|
+
if (retryAfter) {
|
|
2262
|
+
const seconds = Number(retryAfter);
|
|
2263
|
+
if (!Number.isNaN(seconds) && seconds > 0) {
|
|
2264
|
+
return Math.min(seconds * 1e3, config.maxDelay);
|
|
2265
|
+
}
|
|
2266
|
+
const date = new Date(retryAfter);
|
|
2267
|
+
if (!Number.isNaN(date.getTime())) {
|
|
2268
|
+
const ms = date.getTime() - Date.now();
|
|
2269
|
+
if (ms > 0) return Math.min(ms, config.maxDelay);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
return this.calculateDelay(attempt, config);
|
|
2273
|
+
}
|
|
2219
2274
|
sleep(ms) {
|
|
2220
2275
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2221
2276
|
}
|
|
2277
|
+
/** Strips path segments that look like app keys (alphanumeric, 20–64 chars) from URLs before logging. */
|
|
2278
|
+
redactUrl(url) {
|
|
2279
|
+
return url.replace(/\/([a-zA-Z0-9_-]{20,64})(\?|$|\/)/g, "/[REDACTED]$2");
|
|
2280
|
+
}
|
|
2222
2281
|
log(...args) {
|
|
2223
2282
|
if (this.config.debug) console.log("[Paywallo:Http]", ...args);
|
|
2224
2283
|
}
|
|
@@ -2270,7 +2329,17 @@ var NetworkMonitorClass = class {
|
|
|
2270
2329
|
setupAppStateListener() {
|
|
2271
2330
|
this.appStateSubscription = AppState.addEventListener("change", (state) => {
|
|
2272
2331
|
if (state === "active") {
|
|
2332
|
+
if (!this.checkIntervalId) {
|
|
2333
|
+
this.checkIntervalId = setInterval(() => {
|
|
2334
|
+
void this.checkConnectivity();
|
|
2335
|
+
}, this.config.checkInterval);
|
|
2336
|
+
}
|
|
2273
2337
|
void this.checkConnectivity();
|
|
2338
|
+
} else if (state === "background" || state === "inactive") {
|
|
2339
|
+
if (this.checkIntervalId) {
|
|
2340
|
+
clearInterval(this.checkIntervalId);
|
|
2341
|
+
this.checkIntervalId = null;
|
|
2342
|
+
}
|
|
2274
2343
|
}
|
|
2275
2344
|
});
|
|
2276
2345
|
}
|
|
@@ -2352,7 +2421,6 @@ var NetworkMonitorClass = class {
|
|
|
2352
2421
|
var networkMonitor = new NetworkMonitorClass();
|
|
2353
2422
|
|
|
2354
2423
|
// src/core/queue/OfflineQueue.ts
|
|
2355
|
-
import AsyncStorage2 from "@react-native-async-storage/async-storage";
|
|
2356
2424
|
init_uuid();
|
|
2357
2425
|
|
|
2358
2426
|
// src/core/queue/deduplication.ts
|
|
@@ -2414,7 +2482,7 @@ var OfflineQueueClass = class {
|
|
|
2414
2482
|
}
|
|
2415
2483
|
async loadFromStorage() {
|
|
2416
2484
|
try {
|
|
2417
|
-
const stored = await
|
|
2485
|
+
const stored = await nativeStorage.get(this.config.storageKey);
|
|
2418
2486
|
if (stored) {
|
|
2419
2487
|
this.queue = JSON.parse(stored);
|
|
2420
2488
|
}
|
|
@@ -2424,7 +2492,7 @@ var OfflineQueueClass = class {
|
|
|
2424
2492
|
}
|
|
2425
2493
|
async saveToStorage() {
|
|
2426
2494
|
try {
|
|
2427
|
-
await
|
|
2495
|
+
await nativeStorage.set(this.config.storageKey, JSON.stringify(this.queue));
|
|
2428
2496
|
return true;
|
|
2429
2497
|
} catch (error) {
|
|
2430
2498
|
this.log("Failed to save queue to storage", error);
|
|
@@ -2439,7 +2507,7 @@ var OfflineQueueClass = class {
|
|
|
2439
2507
|
cleanupExpired() {
|
|
2440
2508
|
const now = Date.now();
|
|
2441
2509
|
const before = this.queue.length;
|
|
2442
|
-
this.queue = this.queue.filter((item) =>
|
|
2510
|
+
this.queue = this.queue.filter((item) => now - item.timestamp < this.config.maxAge);
|
|
2443
2511
|
if (before !== this.queue.length) {
|
|
2444
2512
|
void this.saveToStorage();
|
|
2445
2513
|
}
|
|
@@ -2462,10 +2530,16 @@ var OfflineQueueClass = class {
|
|
|
2462
2530
|
return this.queue[existingIndex];
|
|
2463
2531
|
}
|
|
2464
2532
|
if (this.queue.length >= this.config.maxItems) {
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2533
|
+
let evictIndex = 0;
|
|
2534
|
+
for (let i = 1; i < this.queue.length; i++) {
|
|
2535
|
+
const candidate = this.queue[i];
|
|
2536
|
+
const current = this.queue[evictIndex];
|
|
2537
|
+
if (candidate.attempts > current.attempts || candidate.attempts === current.attempts && candidate.timestamp < current.timestamp) {
|
|
2538
|
+
evictIndex = i;
|
|
2539
|
+
}
|
|
2468
2540
|
}
|
|
2541
|
+
const [removed] = this.queue.splice(evictIndex, 1);
|
|
2542
|
+
this.emit({ type: "item:evicted", item: removed, queueSize: this.queue.length });
|
|
2469
2543
|
}
|
|
2470
2544
|
this.queue.push(item);
|
|
2471
2545
|
await this.saveToStorage();
|
|
@@ -2664,9 +2738,78 @@ var QueueProcessorClass = class {
|
|
|
2664
2738
|
this.log("Offline, skipping queue processing");
|
|
2665
2739
|
return { processed: 0, failed: 0 };
|
|
2666
2740
|
}
|
|
2667
|
-
|
|
2741
|
+
const allItems = offlineQueue.getQueue();
|
|
2742
|
+
const eventItems = allItems.filter(
|
|
2743
|
+
(i) => i.url.includes("/api/v1/events") && !i.url.includes("/batch")
|
|
2744
|
+
);
|
|
2745
|
+
const eventItemIds = new Set(eventItems.map((i) => i.id));
|
|
2746
|
+
const { processed: batchProcessed, failed: batchFailed, retryIds } = await this.processEventBatches(eventItems);
|
|
2747
|
+
const batchResult = { processed: batchProcessed, failed: batchFailed };
|
|
2748
|
+
const individualResult = await offlineQueue.processQueue(async (item) => {
|
|
2749
|
+
if (eventItemIds.has(item.id)) {
|
|
2750
|
+
if (retryIds.has(item.id)) {
|
|
2751
|
+
return { success: false, permanent: false };
|
|
2752
|
+
}
|
|
2753
|
+
return { success: true, permanent: false };
|
|
2754
|
+
}
|
|
2668
2755
|
return this.processItem(item);
|
|
2669
2756
|
});
|
|
2757
|
+
this.log("Queue processing complete", {
|
|
2758
|
+
batchProcessed: batchResult.processed,
|
|
2759
|
+
batchFailed: batchResult.failed,
|
|
2760
|
+
individualProcessed: individualResult.processed,
|
|
2761
|
+
individualFailed: individualResult.failed
|
|
2762
|
+
});
|
|
2763
|
+
return {
|
|
2764
|
+
processed: batchResult.processed + individualResult.processed,
|
|
2765
|
+
failed: batchResult.failed + individualResult.failed
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
async processEventBatches(eventItems) {
|
|
2769
|
+
if (eventItems.length === 0 || !this.httpClient) {
|
|
2770
|
+
return { processed: 0, failed: 0, retryIds: /* @__PURE__ */ new Set() };
|
|
2771
|
+
}
|
|
2772
|
+
const BATCH_SIZE = 50;
|
|
2773
|
+
let processed = 0;
|
|
2774
|
+
let failed = 0;
|
|
2775
|
+
const retryIds = /* @__PURE__ */ new Set();
|
|
2776
|
+
for (let i = 0; i < eventItems.length; i += BATCH_SIZE) {
|
|
2777
|
+
const batch = eventItems.slice(i, i + BATCH_SIZE);
|
|
2778
|
+
const bodies = batch.map((item) => item.body);
|
|
2779
|
+
const headers = batch[0].headers;
|
|
2780
|
+
this.log("Sending event batch", { size: batch.length, batchIndex: Math.floor(i / BATCH_SIZE) });
|
|
2781
|
+
const response = await this.httpClient.request("/api/v1/events/batch", {
|
|
2782
|
+
method: "POST",
|
|
2783
|
+
body: { events: bodies },
|
|
2784
|
+
headers,
|
|
2785
|
+
skipRetry: false
|
|
2786
|
+
});
|
|
2787
|
+
if (response.ok) {
|
|
2788
|
+
for (const item of batch) {
|
|
2789
|
+
await offlineQueue.removeItem(item.id);
|
|
2790
|
+
processed++;
|
|
2791
|
+
}
|
|
2792
|
+
this.log("Event batch processed successfully", { size: batch.length });
|
|
2793
|
+
} else if (response.status >= 400 && response.status < 500) {
|
|
2794
|
+
for (const item of batch) {
|
|
2795
|
+
await offlineQueue.removeItem(item.id);
|
|
2796
|
+
failed++;
|
|
2797
|
+
}
|
|
2798
|
+
this.log("Event batch failed permanently (4xx) - removing from queue", {
|
|
2799
|
+
status: response.status,
|
|
2800
|
+
size: batch.length
|
|
2801
|
+
});
|
|
2802
|
+
} else {
|
|
2803
|
+
for (const item of batch) {
|
|
2804
|
+
retryIds.add(item.id);
|
|
2805
|
+
}
|
|
2806
|
+
this.log("Event batch failed with server error - will retry", {
|
|
2807
|
+
status: response.status,
|
|
2808
|
+
size: batch.length
|
|
2809
|
+
});
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
return { processed, failed, retryIds };
|
|
2670
2813
|
}
|
|
2671
2814
|
async processItem(item) {
|
|
2672
2815
|
if (!this.httpClient) {
|
|
@@ -2676,7 +2819,6 @@ var QueueProcessorClass = class {
|
|
|
2676
2819
|
id: item.id,
|
|
2677
2820
|
url: item.url,
|
|
2678
2821
|
method: item.method,
|
|
2679
|
-
headers: item.headers,
|
|
2680
2822
|
attempts: item.attempts
|
|
2681
2823
|
});
|
|
2682
2824
|
const response = await this.httpClient.request(item.url, {
|
|
@@ -2694,7 +2836,7 @@ var QueueProcessorClass = class {
|
|
|
2694
2836
|
id: item.id,
|
|
2695
2837
|
url: item.url,
|
|
2696
2838
|
status: response.status,
|
|
2697
|
-
|
|
2839
|
+
hasAppKey: item.headers?.["X-App-Key"] != null
|
|
2698
2840
|
});
|
|
2699
2841
|
return { success: false, permanent: true, status: response.status };
|
|
2700
2842
|
}
|
|
@@ -2816,9 +2958,94 @@ var ApiCache = class {
|
|
|
2816
2958
|
}
|
|
2817
2959
|
};
|
|
2818
2960
|
|
|
2961
|
+
// src/core/EventBatcher.ts
|
|
2962
|
+
import { Platform as Platform4 } from "react-native";
|
|
2963
|
+
var BATCH_MAX_SIZE = 10;
|
|
2964
|
+
var BATCH_FLUSH_MS = 5e3;
|
|
2965
|
+
var EventBatcher = class {
|
|
2966
|
+
constructor(post, debug = false) {
|
|
2967
|
+
this.batch = [];
|
|
2968
|
+
this.timer = null;
|
|
2969
|
+
this.post = post;
|
|
2970
|
+
this.debug = debug;
|
|
2971
|
+
}
|
|
2972
|
+
async track(eventName, distinctId, environment, properties, timestamp) {
|
|
2973
|
+
const EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
|
|
2974
|
+
if (!EVENT_NAME_REGEX.test(eventName)) {
|
|
2975
|
+
if (this.debug) console.warn(`[Paywallo] Invalid event name "${eventName}". Must be snake_case or start with $`);
|
|
2976
|
+
}
|
|
2977
|
+
const event = {
|
|
2978
|
+
eventName,
|
|
2979
|
+
distinctId,
|
|
2980
|
+
properties: { ...properties, platform: Platform4.OS },
|
|
2981
|
+
timestamp: timestamp ?? Date.now(),
|
|
2982
|
+
environment: environment.toLowerCase()
|
|
2983
|
+
};
|
|
2984
|
+
this.batch.push(event);
|
|
2985
|
+
if (this.batch.length >= BATCH_MAX_SIZE) {
|
|
2986
|
+
if (this.timer !== null) {
|
|
2987
|
+
clearTimeout(this.timer);
|
|
2988
|
+
this.timer = null;
|
|
2989
|
+
}
|
|
2990
|
+
await this.flush();
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
2994
|
+
this.timer = setTimeout(() => {
|
|
2995
|
+
this.timer = null;
|
|
2996
|
+
void this.flush();
|
|
2997
|
+
}, BATCH_FLUSH_MS);
|
|
2998
|
+
}
|
|
2999
|
+
async flush() {
|
|
3000
|
+
if (this.batch.length === 0) return;
|
|
3001
|
+
const batch = this.batch.splice(0, this.batch.length);
|
|
3002
|
+
if (this.timer !== null) {
|
|
3003
|
+
clearTimeout(this.timer);
|
|
3004
|
+
this.timer = null;
|
|
3005
|
+
}
|
|
3006
|
+
try {
|
|
3007
|
+
await this.post("/api/v1/events/batch", { events: batch }, `event_batch:${batch.length}`);
|
|
3008
|
+
} catch {
|
|
3009
|
+
for (const event of batch) {
|
|
3010
|
+
await this.post("/api/v1/events", event, `event:${event.eventName}`);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
dispose() {
|
|
3015
|
+
if (this.timer !== null) {
|
|
3016
|
+
clearTimeout(this.timer);
|
|
3017
|
+
this.timer = null;
|
|
3018
|
+
}
|
|
3019
|
+
this.batch = [];
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
|
|
3023
|
+
// src/core/FlagStorage.ts
|
|
3024
|
+
var FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3025
|
+
async function readFlagFromStorage(flagKey, distinctId) {
|
|
3026
|
+
try {
|
|
3027
|
+
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3028
|
+
if (!raw) return null;
|
|
3029
|
+
const parsed = JSON.parse(raw);
|
|
3030
|
+
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3031
|
+
const { variant, payload, cachedAt } = parsed;
|
|
3032
|
+
if (Date.now() - cachedAt > FLAG_MAX_AGE_MS) return null;
|
|
3033
|
+
return { variant, payload };
|
|
3034
|
+
} catch {
|
|
3035
|
+
return null;
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
async function writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3039
|
+
try {
|
|
3040
|
+
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3041
|
+
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3042
|
+
} catch {
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
|
|
2819
3046
|
// src/core/ApiClient.ts
|
|
2820
|
-
var SDK_VERSION = "1.
|
|
2821
|
-
var
|
|
3047
|
+
var SDK_VERSION = "1.5.2";
|
|
3048
|
+
var ApiClient = class {
|
|
2822
3049
|
constructor(appKey, apiUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
|
|
2823
3050
|
this.cache = new ApiCache();
|
|
2824
3051
|
this.appKey = appKey;
|
|
@@ -2828,6 +3055,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2828
3055
|
this.environment = environment;
|
|
2829
3056
|
this.offlineQueueEnabled = offlineQueueEnabled;
|
|
2830
3057
|
this.httpClient = new HttpClient(this.baseUrl, { timeout, debug });
|
|
3058
|
+
this.batcher = new EventBatcher((url, payload, label) => this.postWithQueue(url, payload, label), debug);
|
|
2831
3059
|
}
|
|
2832
3060
|
getHttpClient() {
|
|
2833
3061
|
return this.httpClient;
|
|
@@ -2838,6 +3066,9 @@ var _ApiClient = class _ApiClient {
|
|
|
2838
3066
|
getWebUrl() {
|
|
2839
3067
|
return this.webUrl;
|
|
2840
3068
|
}
|
|
3069
|
+
getAppKey() {
|
|
3070
|
+
return this.appKey;
|
|
3071
|
+
}
|
|
2841
3072
|
getEnvironment() {
|
|
2842
3073
|
return this.environment;
|
|
2843
3074
|
}
|
|
@@ -2876,27 +3107,30 @@ var _ApiClient = class _ApiClient {
|
|
|
2876
3107
|
platform: Platform5.OS
|
|
2877
3108
|
}, true);
|
|
2878
3109
|
} catch (err) {
|
|
2879
|
-
if (this.debug) console.
|
|
3110
|
+
if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
|
|
2880
3111
|
}
|
|
2881
3112
|
}
|
|
2882
3113
|
async trackEvent(eventName, distinctId, properties, timestamp) {
|
|
2883
|
-
await this.
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
3114
|
+
await this.batcher.track(eventName, distinctId, this.environment, properties, timestamp);
|
|
3115
|
+
}
|
|
3116
|
+
async flushEventBatch() {
|
|
3117
|
+
await this.batcher.flush();
|
|
3118
|
+
}
|
|
3119
|
+
dispose() {
|
|
3120
|
+
this.batcher.dispose();
|
|
2890
3121
|
}
|
|
2891
3122
|
async identify(distinctId, properties, email, deviceId) {
|
|
2892
3123
|
await this.postWithQueue("/api/v1/identify", { distinctId, properties: properties ?? {}, email, platform: Platform5.OS, ...deviceId && { deviceId } }, "identify");
|
|
2893
3124
|
}
|
|
2894
|
-
async startSession(distinctId, sessionId, platform) {
|
|
3125
|
+
async startSession(distinctId, sessionId, platform, appVersion, deviceModel, osVersion) {
|
|
2895
3126
|
await this.postWithQueue("/api/v1/sessions/start", {
|
|
2896
3127
|
distinctId,
|
|
2897
3128
|
sessionId,
|
|
2898
3129
|
platform: platform ?? Platform5.OS,
|
|
2899
|
-
environment: this.environment.toLowerCase()
|
|
3130
|
+
environment: this.environment.toLowerCase(),
|
|
3131
|
+
appVersion: appVersion ?? "unknown",
|
|
3132
|
+
deviceModel: deviceModel ?? "unknown",
|
|
3133
|
+
osVersion: osVersion ?? String(Platform5.Version)
|
|
2900
3134
|
}, `session.start:${sessionId}`);
|
|
2901
3135
|
return { success: true };
|
|
2902
3136
|
}
|
|
@@ -2908,7 +3142,7 @@ var _ApiClient = class _ApiClient {
|
|
|
2908
3142
|
const key = `${flagKey}:${distinctId}`;
|
|
2909
3143
|
const hit = this.cache.getVariant(key);
|
|
2910
3144
|
if (hit !== void 0) return hit;
|
|
2911
|
-
const persisted = await
|
|
3145
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
2912
3146
|
if (persisted !== null) {
|
|
2913
3147
|
this.cache.setVariant(key, persisted);
|
|
2914
3148
|
return persisted;
|
|
@@ -2916,25 +3150,20 @@ var _ApiClient = class _ApiClient {
|
|
|
2916
3150
|
try {
|
|
2917
3151
|
const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2918
3152
|
if (res.status === 404) {
|
|
2919
|
-
this.log("Flag not found (404):", flagKey);
|
|
2920
3153
|
const v = { variant: null };
|
|
2921
3154
|
this.cache.setVariant(key, v, this.cache.nullTTL);
|
|
2922
3155
|
return v;
|
|
2923
3156
|
}
|
|
2924
3157
|
if (!res.ok) {
|
|
2925
|
-
this.log("Non-OK response for variant:", flagKey, res.status);
|
|
2926
3158
|
const stale = this.cache.getStaleVariant(key);
|
|
2927
|
-
|
|
2928
|
-
return { variant: null };
|
|
3159
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
2929
3160
|
}
|
|
2930
3161
|
this.cache.setVariant(key, res.data);
|
|
2931
|
-
await
|
|
3162
|
+
await writeFlagToStorage(flagKey, distinctId, res.data);
|
|
2932
3163
|
return res.data;
|
|
2933
|
-
} catch
|
|
2934
|
-
this.log("Failed to get variant:", flagKey, error);
|
|
3164
|
+
} catch {
|
|
2935
3165
|
const stale = this.cache.getStaleVariant(key);
|
|
2936
|
-
|
|
2937
|
-
return { variant: null };
|
|
3166
|
+
return stale !== void 0 ? stale : { variant: null };
|
|
2938
3167
|
}
|
|
2939
3168
|
}
|
|
2940
3169
|
async getPaywall(placement) {
|
|
@@ -2943,29 +3172,22 @@ var _ApiClient = class _ApiClient {
|
|
|
2943
3172
|
try {
|
|
2944
3173
|
const res = await this.get(`/api/v1/paywalls/${placement}`);
|
|
2945
3174
|
if (res.status === 404) {
|
|
2946
|
-
this.log("Paywall not found (404):", placement);
|
|
2947
3175
|
this.cache.setPaywall(placement, null, this.cache.nullTTL);
|
|
2948
3176
|
return null;
|
|
2949
3177
|
}
|
|
2950
3178
|
if (!res.ok) {
|
|
2951
|
-
this.log("Non-OK response for paywall:", placement, res.status);
|
|
2952
3179
|
const stale = this.cache.getStalePaywall(placement);
|
|
2953
|
-
|
|
2954
|
-
return null;
|
|
3180
|
+
return stale !== void 0 ? stale : null;
|
|
2955
3181
|
}
|
|
2956
3182
|
this.cache.setPaywall(placement, res.data);
|
|
2957
3183
|
return res.data;
|
|
2958
|
-
} catch
|
|
2959
|
-
this.log("Failed to get paywall:", placement, error);
|
|
3184
|
+
} catch {
|
|
2960
3185
|
const stale = this.cache.getStalePaywall(placement);
|
|
2961
|
-
|
|
2962
|
-
return null;
|
|
3186
|
+
return stale !== void 0 ? stale : null;
|
|
2963
3187
|
}
|
|
2964
3188
|
}
|
|
2965
3189
|
async getEmergencyPaywall() {
|
|
2966
|
-
|
|
2967
|
-
this.log("Got emergency paywall:", result.enabled);
|
|
2968
|
-
return result;
|
|
3190
|
+
return getEmergencyPaywall(this);
|
|
2969
3191
|
}
|
|
2970
3192
|
async getCampaign(placement, distinctId, context) {
|
|
2971
3193
|
const key = `${placement}:${distinctId}`;
|
|
@@ -2974,18 +3196,14 @@ var _ApiClient = class _ApiClient {
|
|
|
2974
3196
|
try {
|
|
2975
3197
|
const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
|
|
2976
3198
|
if (!result) {
|
|
2977
|
-
this.log("No campaign found for placement:", placement);
|
|
2978
3199
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
2979
3200
|
return null;
|
|
2980
3201
|
}
|
|
2981
|
-
this.log("Got campaign:", placement, result.variantKey);
|
|
2982
3202
|
this.cache.setCampaign(key, result);
|
|
2983
3203
|
return result;
|
|
2984
|
-
} catch
|
|
2985
|
-
this.log("Failed to get campaign:", placement, error);
|
|
3204
|
+
} catch {
|
|
2986
3205
|
const stale = this.cache.getStaleCampaign(key);
|
|
2987
|
-
|
|
2988
|
-
return null;
|
|
3206
|
+
return stale !== void 0 ? stale : null;
|
|
2989
3207
|
}
|
|
2990
3208
|
}
|
|
2991
3209
|
async getPrimaryCampaign(distinctId) {
|
|
@@ -2995,63 +3213,34 @@ var _ApiClient = class _ApiClient {
|
|
|
2995
3213
|
try {
|
|
2996
3214
|
const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
|
|
2997
3215
|
if (res.status === 404) {
|
|
2998
|
-
this.log("Primary campaign not found (404)");
|
|
2999
3216
|
this.cache.setCampaign(key, null, this.cache.nullTTL);
|
|
3000
3217
|
return null;
|
|
3001
3218
|
}
|
|
3002
3219
|
if (!res.ok) {
|
|
3003
|
-
this.log("Non-OK response for primary campaign:", res.status);
|
|
3004
3220
|
const stale = this.cache.getStaleCampaign(key);
|
|
3005
|
-
|
|
3006
|
-
return null;
|
|
3221
|
+
return stale !== void 0 ? stale : null;
|
|
3007
3222
|
}
|
|
3008
|
-
this.log("Got primary campaign:", res.data?.campaignId);
|
|
3009
3223
|
this.cache.setCampaign(key, res.data);
|
|
3010
3224
|
return res.data;
|
|
3011
|
-
} catch
|
|
3012
|
-
this.log("Failed to get primary campaign:", error);
|
|
3225
|
+
} catch {
|
|
3013
3226
|
const stale = this.cache.getStaleCampaign(key);
|
|
3014
|
-
|
|
3015
|
-
return null;
|
|
3227
|
+
return stale !== void 0 ? stale : null;
|
|
3016
3228
|
}
|
|
3017
3229
|
}
|
|
3018
3230
|
async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
|
|
3019
|
-
|
|
3020
|
-
this,
|
|
3021
|
-
this.appKey,
|
|
3022
|
-
platform,
|
|
3023
|
-
receipt,
|
|
3024
|
-
productId,
|
|
3025
|
-
transactionId,
|
|
3026
|
-
priceLocal,
|
|
3027
|
-
currency,
|
|
3028
|
-
country,
|
|
3029
|
-
distinctId,
|
|
3030
|
-
paywallPlacement,
|
|
3031
|
-
variantKey
|
|
3032
|
-
);
|
|
3033
|
-
this.log("Purchase validated:", result.success);
|
|
3034
|
-
return result;
|
|
3231
|
+
return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
|
|
3035
3232
|
}
|
|
3036
3233
|
async getSubscriptionStatus(distinctId) {
|
|
3037
|
-
|
|
3038
|
-
this.log("Subscription status:", result.hasActiveSubscription);
|
|
3039
|
-
return result;
|
|
3234
|
+
return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
|
|
3040
3235
|
}
|
|
3041
3236
|
async evaluateFlags(keys, distinctId) {
|
|
3042
3237
|
const query = keys.map(encodeURIComponent).join(",");
|
|
3043
|
-
const path = `/api/v1/flags/evaluate?keys=${query}`;
|
|
3044
3238
|
try {
|
|
3045
|
-
const res = await this.httpClient.get(
|
|
3239
|
+
const res = await this.httpClient.get(`/api/v1/flags/evaluate?keys=${query}`, {
|
|
3046
3240
|
headers: { "X-App-Key": this.appKey, "x-distinct-id": distinctId }
|
|
3047
3241
|
});
|
|
3048
|
-
if (!res.ok) {
|
|
3049
|
-
|
|
3050
|
-
return {};
|
|
3051
|
-
}
|
|
3052
|
-
const raw = res.data;
|
|
3053
|
-
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
3054
|
-
const record = raw;
|
|
3242
|
+
if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
|
|
3243
|
+
const record = res.data;
|
|
3055
3244
|
const result = {};
|
|
3056
3245
|
for (const key of Object.keys(record)) {
|
|
3057
3246
|
const value = record[key];
|
|
@@ -3059,11 +3248,10 @@ var _ApiClient = class _ApiClient {
|
|
|
3059
3248
|
result[key] = variant;
|
|
3060
3249
|
const flagVariant = { variant };
|
|
3061
3250
|
this.cache.setVariant(`${key}:${distinctId}`, flagVariant);
|
|
3062
|
-
await
|
|
3251
|
+
await writeFlagToStorage(key, distinctId, flagVariant);
|
|
3063
3252
|
}
|
|
3064
3253
|
return result;
|
|
3065
|
-
} catch
|
|
3066
|
-
this.log("Failed to evaluateFlags:", error);
|
|
3254
|
+
} catch {
|
|
3067
3255
|
return {};
|
|
3068
3256
|
}
|
|
3069
3257
|
}
|
|
@@ -3071,15 +3259,9 @@ var _ApiClient = class _ApiClient {
|
|
|
3071
3259
|
try {
|
|
3072
3260
|
const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
|
|
3073
3261
|
const response = await this.get(url);
|
|
3074
|
-
if (response.status === 404) return { value: false, flagKey };
|
|
3075
|
-
if (!response.ok) {
|
|
3076
|
-
this.log("Non-OK response for conditional flag:", flagKey, response.status);
|
|
3077
|
-
return { value: false, flagKey };
|
|
3078
|
-
}
|
|
3079
|
-
this.log("Got conditional flag:", flagKey, response.data.value);
|
|
3262
|
+
if (response.status === 404 || !response.ok) return { value: false, flagKey };
|
|
3080
3263
|
return { value: response.data.value, flagKey };
|
|
3081
3264
|
} catch {
|
|
3082
|
-
this.log("Failed to get conditional flag:", flagKey);
|
|
3083
3265
|
return { value: false, flagKey };
|
|
3084
3266
|
}
|
|
3085
3267
|
}
|
|
@@ -3087,34 +3269,13 @@ var _ApiClient = class _ApiClient {
|
|
|
3087
3269
|
const key = `${flagKey}:${distinctId}`;
|
|
3088
3270
|
const hit = this.cache.getVariant(key);
|
|
3089
3271
|
if (hit !== void 0) return hit;
|
|
3090
|
-
const persisted = await
|
|
3272
|
+
const persisted = await readFlagFromStorage(flagKey, distinctId);
|
|
3091
3273
|
if (persisted !== null) {
|
|
3092
3274
|
this.cache.setVariant(key, persisted);
|
|
3093
3275
|
return persisted;
|
|
3094
3276
|
}
|
|
3095
3277
|
return null;
|
|
3096
3278
|
}
|
|
3097
|
-
async readFlagFromStorage(flagKey, distinctId) {
|
|
3098
|
-
try {
|
|
3099
|
-
const raw = await secureStorage.get(`flag:${flagKey}:${distinctId}`);
|
|
3100
|
-
if (!raw) return null;
|
|
3101
|
-
const parsed = JSON.parse(raw);
|
|
3102
|
-
if (typeof parsed !== "object" || parsed === null || !("variant" in parsed) || !("cachedAt" in parsed) || typeof parsed.cachedAt !== "number") return null;
|
|
3103
|
-
const { variant, payload, cachedAt } = parsed;
|
|
3104
|
-
if (Date.now() - cachedAt > _ApiClient.FLAG_MAX_AGE_MS) return null;
|
|
3105
|
-
return { variant, payload };
|
|
3106
|
-
} catch {
|
|
3107
|
-
return null;
|
|
3108
|
-
}
|
|
3109
|
-
}
|
|
3110
|
-
async writeFlagToStorage(flagKey, distinctId, flag) {
|
|
3111
|
-
try {
|
|
3112
|
-
const value = JSON.stringify({ variant: flag.variant, payload: flag.payload, cachedAt: Date.now() });
|
|
3113
|
-
await secureStorage.set(`flag:${flagKey}:${distinctId}`, value);
|
|
3114
|
-
} catch (err) {
|
|
3115
|
-
this.log("writeFlagToStorage failed", err);
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
3279
|
async postWithQueue(url, payload, label) {
|
|
3119
3280
|
if (this.offlineQueueEnabled && !networkMonitor.isOnline()) {
|
|
3120
3281
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
@@ -3127,7 +3288,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3127
3288
|
this.log("Request failed:", label, error);
|
|
3128
3289
|
if (this.offlineQueueEnabled) {
|
|
3129
3290
|
await offlineQueue.enqueue("POST", url, payload, { "X-App-Key": this.appKey });
|
|
3130
|
-
this.log("Queued after failure:", label);
|
|
3131
3291
|
return;
|
|
3132
3292
|
}
|
|
3133
3293
|
throw error;
|
|
@@ -3137,8 +3297,6 @@ var _ApiClient = class _ApiClient {
|
|
|
3137
3297
|
if (this.debug) console.log("[Paywallo:ApiClient]", ...args);
|
|
3138
3298
|
}
|
|
3139
3299
|
};
|
|
3140
|
-
_ApiClient.FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
3141
|
-
var ApiClient = _ApiClient;
|
|
3142
3300
|
|
|
3143
3301
|
// src/domains/campaign/CampaignError.ts
|
|
3144
3302
|
var CampaignError = class extends PaywalloError {
|
|
@@ -3163,6 +3321,7 @@ var CampaignGateService = class {
|
|
|
3163
3321
|
constructor() {
|
|
3164
3322
|
this.preloadedCampaigns = /* @__PURE__ */ new Map();
|
|
3165
3323
|
this.pendingPreloads = /* @__PURE__ */ new Set();
|
|
3324
|
+
this.activePreloadPromises = /* @__PURE__ */ new Map();
|
|
3166
3325
|
}
|
|
3167
3326
|
async getCampaign(apiClient, placement, context) {
|
|
3168
3327
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3202,12 +3361,23 @@ var CampaignGateService = class {
|
|
|
3202
3361
|
}
|
|
3203
3362
|
async requireSubscriptionWithCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context) {
|
|
3204
3363
|
const isActive = await hasActive();
|
|
3205
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
3364
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo] requireSubscription \u2014 hasActive: ${isActive}`);
|
|
3206
3365
|
if (isActive) return true;
|
|
3207
|
-
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, context);
|
|
3366
|
+
const r = await this.presentCampaign(apiClient, placement, hasActive, paywallPresenter, campaignPresenter, { ...context, forceShow: true });
|
|
3208
3367
|
return r.purchased || r.restored;
|
|
3209
3368
|
}
|
|
3210
3369
|
async preloadCampaign(apiClient, placement, context) {
|
|
3370
|
+
const existing = this.activePreloadPromises.get(placement);
|
|
3371
|
+
if (existing) return existing;
|
|
3372
|
+
const promise = this._doPreload(apiClient, placement, context);
|
|
3373
|
+
this.activePreloadPromises.set(placement, promise);
|
|
3374
|
+
try {
|
|
3375
|
+
return await promise;
|
|
3376
|
+
} finally {
|
|
3377
|
+
this.activePreloadPromises.delete(placement);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async _doPreload(apiClient, placement, context) {
|
|
3211
3381
|
this.pendingPreloads.add(placement);
|
|
3212
3382
|
try {
|
|
3213
3383
|
const campaign = await this.getCampaign(apiClient, placement, context);
|
|
@@ -3216,7 +3386,7 @@ var CampaignGateService = class {
|
|
|
3216
3386
|
}
|
|
3217
3387
|
this.preloadedCampaigns.set(placement, { campaign, preloadedAt: Date.now() });
|
|
3218
3388
|
if (PaywalloClient.getConfig()?.debug) {
|
|
3219
|
-
console.
|
|
3389
|
+
console.log(`[Paywallo PRELOAD] data cached \u2014 placement: ${placement}`);
|
|
3220
3390
|
}
|
|
3221
3391
|
return { success: true };
|
|
3222
3392
|
} catch (error) {
|
|
@@ -3273,9 +3443,7 @@ var AdvertisingIdManager = class {
|
|
|
3273
3443
|
return this.cached;
|
|
3274
3444
|
}
|
|
3275
3445
|
if (Platform6.OS === "android") {
|
|
3276
|
-
const
|
|
3277
|
-
const optedIn = androidStatus.granted;
|
|
3278
|
-
const rawGaid = optedIn ? tracking.getAdvertisingId() : null;
|
|
3446
|
+
const rawGaid = tracking.getAdvertisingId();
|
|
3279
3447
|
const gaid = this.sanitizeId(rawGaid);
|
|
3280
3448
|
this.cached = { idfv: null, idfa: null, gaid, attStatus: "unavailable" };
|
|
3281
3449
|
return this.cached;
|
|
@@ -3289,9 +3457,8 @@ var AdvertisingIdManager = class {
|
|
|
3289
3457
|
}
|
|
3290
3458
|
async collectIdfv() {
|
|
3291
3459
|
try {
|
|
3292
|
-
const
|
|
3293
|
-
|
|
3294
|
-
return this.sanitizeId(idfv);
|
|
3460
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
3461
|
+
return this.sanitizeId(info.idfv);
|
|
3295
3462
|
} catch {
|
|
3296
3463
|
return null;
|
|
3297
3464
|
}
|
|
@@ -3304,13 +3471,11 @@ var AdvertisingIdManager = class {
|
|
|
3304
3471
|
|
|
3305
3472
|
// src/domains/identity/InstallTracker.ts
|
|
3306
3473
|
import { Dimensions as Dimensions3, Platform as Platform8 } from "react-native";
|
|
3307
|
-
import AsyncStorage3 from "@react-native-async-storage/async-storage";
|
|
3308
|
-
import RNDeviceInfo from "react-native-device-info";
|
|
3309
3474
|
|
|
3310
3475
|
// src/domains/identity/MetaBridge.ts
|
|
3311
|
-
import { NativeModules as
|
|
3476
|
+
import { NativeModules as NativeModules6 } from "react-native";
|
|
3312
3477
|
var TIMEOUT_MS = 2e3;
|
|
3313
|
-
var NativeBridge =
|
|
3478
|
+
var NativeBridge = NativeModules6.PaywalloFBBridge ?? null;
|
|
3314
3479
|
var MetaBridge = class {
|
|
3315
3480
|
async getAnonymousID() {
|
|
3316
3481
|
if (!NativeBridge) return null;
|
|
@@ -3376,8 +3541,7 @@ var InstallReferrerManager = class {
|
|
|
3376
3541
|
}
|
|
3377
3542
|
async fetchReferrer() {
|
|
3378
3543
|
try {
|
|
3379
|
-
const
|
|
3380
|
-
const referrer = await Application.getInstallReferrerAsync();
|
|
3544
|
+
const referrer = await nativeDeviceInfo.getInstallReferrer();
|
|
3381
3545
|
if (!referrer) return this.emptyResult();
|
|
3382
3546
|
const capped = referrer.slice(0, 2048);
|
|
3383
3547
|
const params = this.parseReferrer(capped);
|
|
@@ -3425,14 +3589,42 @@ var installReferrerManager = new InstallReferrerManager();
|
|
|
3425
3589
|
import { AppState as AppState2 } from "react-native";
|
|
3426
3590
|
init_uuid();
|
|
3427
3591
|
|
|
3592
|
+
// src/utils/deviceInfo.ts
|
|
3593
|
+
async function collectDeviceInfo() {
|
|
3594
|
+
const result = await nativeDeviceInfo.getDeviceInfo();
|
|
3595
|
+
return {
|
|
3596
|
+
appVersion: result.appVersion,
|
|
3597
|
+
deviceModel: result.model,
|
|
3598
|
+
osVersion: result.systemVersion
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3428
3602
|
// src/domains/session/sessionTracking.ts
|
|
3429
3603
|
async function trackSessionStart(apiClient, sessionId, sessionStartedAt) {
|
|
3430
3604
|
const distinctId = identityManager.getDistinctId();
|
|
3431
|
-
await
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
});
|
|
3605
|
+
const deviceInfo = await collectDeviceInfo().catch(() => ({
|
|
3606
|
+
appVersion: "unknown",
|
|
3607
|
+
deviceModel: "unknown",
|
|
3608
|
+
osVersion: "unknown"
|
|
3609
|
+
}));
|
|
3610
|
+
await Promise.all([
|
|
3611
|
+
apiClient.startSession(
|
|
3612
|
+
distinctId,
|
|
3613
|
+
sessionId,
|
|
3614
|
+
void 0,
|
|
3615
|
+
deviceInfo.appVersion,
|
|
3616
|
+
deviceInfo.deviceModel,
|
|
3617
|
+
deviceInfo.osVersion
|
|
3618
|
+
),
|
|
3619
|
+
apiClient.trackEvent("$session_start", distinctId, {
|
|
3620
|
+
sessionId,
|
|
3621
|
+
timestamp: sessionStartedAt?.toISOString() ?? null,
|
|
3622
|
+
appVersion: deviceInfo.appVersion,
|
|
3623
|
+
deviceModel: deviceInfo.deviceModel,
|
|
3624
|
+
osVersion: deviceInfo.osVersion
|
|
3625
|
+
}).catch(() => {
|
|
3626
|
+
})
|
|
3627
|
+
]);
|
|
3436
3628
|
}
|
|
3437
3629
|
async function trackSessionEnd(apiClient, sessionId, durationSeconds, sessionStartedAt) {
|
|
3438
3630
|
const distinctId = identityManager.getDistinctId();
|
|
@@ -3529,6 +3721,9 @@ var SessionManager = class {
|
|
|
3529
3721
|
if (!this.sessionId || !this.sessionStartedAt) {
|
|
3530
3722
|
return;
|
|
3531
3723
|
}
|
|
3724
|
+
if (this.apiClient) {
|
|
3725
|
+
await this.apiClient.flushEventBatch();
|
|
3726
|
+
}
|
|
3532
3727
|
await this.trackSessionEnd();
|
|
3533
3728
|
this.sessionId = null;
|
|
3534
3729
|
this.sessionStartedAt = null;
|
|
@@ -3613,6 +3808,9 @@ var SessionManager = class {
|
|
|
3613
3808
|
async handleBackground() {
|
|
3614
3809
|
this.backgroundTimestamp = Date.now();
|
|
3615
3810
|
await this.trackAppBackground();
|
|
3811
|
+
if (this.apiClient) {
|
|
3812
|
+
await this.apiClient.flushEventBatch();
|
|
3813
|
+
}
|
|
3616
3814
|
}
|
|
3617
3815
|
async checkEmergencyPaywall() {
|
|
3618
3816
|
if (!this.apiClient || !this.emergencyPaywallHandler) {
|
|
@@ -3661,16 +3859,26 @@ var sessionManager = new SessionManager();
|
|
|
3661
3859
|
// src/domains/identity/InstallTracker.ts
|
|
3662
3860
|
var InstallTracker = class {
|
|
3663
3861
|
constructor(debug = false, advertisingIdManager) {
|
|
3862
|
+
this._trackingInProgress = false;
|
|
3664
3863
|
this.debug = debug;
|
|
3665
3864
|
this.advertisingIdManager = advertisingIdManager ?? new AdvertisingIdManager();
|
|
3666
3865
|
}
|
|
3667
3866
|
async trackIfNeeded(apiClient, requestATT = false) {
|
|
3867
|
+
if (this._trackingInProgress) return;
|
|
3868
|
+
this._trackingInProgress = true;
|
|
3869
|
+
try {
|
|
3870
|
+
await this._doTrack(apiClient, requestATT);
|
|
3871
|
+
} finally {
|
|
3872
|
+
this._trackingInProgress = false;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
async _doTrack(apiClient, requestATT) {
|
|
3668
3876
|
const storage = new SecureStorage(this.debug);
|
|
3669
3877
|
const alreadyTracked = await storage.get("@panel:install_tracked");
|
|
3670
3878
|
if (alreadyTracked) return;
|
|
3671
3879
|
let fallbackTracked = null;
|
|
3672
3880
|
try {
|
|
3673
|
-
fallbackTracked = await
|
|
3881
|
+
fallbackTracked = await nativeStorage.get("@paywallo:install_tracked");
|
|
3674
3882
|
} catch {
|
|
3675
3883
|
}
|
|
3676
3884
|
if (fallbackTracked) return;
|
|
@@ -3678,7 +3886,6 @@ var InstallTracker = class {
|
|
|
3678
3886
|
const sessionId = sessionManager.getSessionId();
|
|
3679
3887
|
const installedAt = Date.now();
|
|
3680
3888
|
if (!distinctId) return;
|
|
3681
|
-
const deviceInfo = RNDeviceInfo;
|
|
3682
3889
|
let deviceData = {};
|
|
3683
3890
|
try {
|
|
3684
3891
|
const screen = Dimensions3.get("screen");
|
|
@@ -3686,39 +3893,40 @@ var InstallTracker = class {
|
|
|
3686
3893
|
const screenHeight = Math.round(screen.height ?? 0);
|
|
3687
3894
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
|
|
3688
3895
|
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
|
|
3689
|
-
const
|
|
3690
|
-
const
|
|
3691
|
-
const
|
|
3692
|
-
const
|
|
3896
|
+
const info = await nativeDeviceInfo.getDeviceInfo();
|
|
3897
|
+
const deviceModel = info.modelId || info.model || "unknown";
|
|
3898
|
+
const osVersion = info.systemVersion || "unknown";
|
|
3899
|
+
const appVersion = info.appVersion || "unknown";
|
|
3900
|
+
const buildNumber = info.buildNumber || "0";
|
|
3693
3901
|
let carrier = "unknown";
|
|
3694
3902
|
try {
|
|
3695
|
-
carrier =
|
|
3903
|
+
carrier = info.carrier ?? "unknown";
|
|
3696
3904
|
} catch {
|
|
3697
3905
|
}
|
|
3698
3906
|
let totalDisk = 0;
|
|
3699
3907
|
try {
|
|
3700
|
-
totalDisk = Number(
|
|
3908
|
+
totalDisk = Number(info.totalDisk) || 0;
|
|
3701
3909
|
} catch {
|
|
3702
3910
|
}
|
|
3703
3911
|
let freeDisk = 0;
|
|
3704
3912
|
try {
|
|
3705
|
-
freeDisk = Number(
|
|
3913
|
+
freeDisk = Number(info.freeDisk) || 0;
|
|
3706
3914
|
} catch {
|
|
3707
3915
|
}
|
|
3708
3916
|
let totalRam = 0;
|
|
3709
3917
|
try {
|
|
3710
|
-
totalRam = Number(
|
|
3918
|
+
totalRam = Number(info.totalRam) || 0;
|
|
3711
3919
|
} catch {
|
|
3712
3920
|
}
|
|
3713
3921
|
let brand = "unknown";
|
|
3714
3922
|
try {
|
|
3715
|
-
brand =
|
|
3923
|
+
brand = info.brand ?? "unknown";
|
|
3716
3924
|
} catch {
|
|
3717
3925
|
}
|
|
3718
3926
|
let installerPackage = null;
|
|
3719
3927
|
try {
|
|
3720
3928
|
if (Platform8.OS === "android") {
|
|
3721
|
-
installerPackage =
|
|
3929
|
+
installerPackage = info.installerPackage ?? null;
|
|
3722
3930
|
}
|
|
3723
3931
|
} catch {
|
|
3724
3932
|
}
|
|
@@ -3757,37 +3965,34 @@ var InstallTracker = class {
|
|
|
3757
3965
|
});
|
|
3758
3966
|
}
|
|
3759
3967
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3968
|
+
await apiClient.trackEvent("$app_installed", distinctId, {
|
|
3969
|
+
installedAt,
|
|
3970
|
+
platform: Platform8.OS,
|
|
3971
|
+
...sessionId && { sessionId },
|
|
3972
|
+
...deviceData,
|
|
3973
|
+
...effectiveAnonId && { fbAnonId: effectiveAnonId },
|
|
3974
|
+
...referrer.rawReferrer && { installReferrer: referrer.rawReferrer },
|
|
3975
|
+
...referrer.trackingId && { referrerTrackingId: referrer.trackingId },
|
|
3976
|
+
...referrer.fbclid && { referrerFbclid: referrer.fbclid },
|
|
3977
|
+
...referrer.utmSource && { referrerUtmSource: referrer.utmSource },
|
|
3978
|
+
...referrer.utmMedium && { referrerUtmMedium: referrer.utmMedium },
|
|
3979
|
+
...referrer.utmCampaign && { referrerUtmCampaign: referrer.utmCampaign },
|
|
3980
|
+
...adIds.idfa && { idfa: adIds.idfa },
|
|
3981
|
+
...adIds.idfv && { idfv: adIds.idfv },
|
|
3982
|
+
...adIds.gaid && { gaid: adIds.gaid },
|
|
3983
|
+
attStatus: adIds.attStatus
|
|
3984
|
+
});
|
|
3985
|
+
if (Platform8.OS === "ios") {
|
|
3986
|
+
void this.callDeferredMatch(apiClient, deviceData, adIds, effectiveAnonId, installedAt).catch(() => {
|
|
3777
3987
|
});
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
await storage.set("@panel:install_tracked", String(installedAt));
|
|
3783
|
-
try {
|
|
3784
|
-
await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
|
|
3785
|
-
} catch {
|
|
3786
|
-
}
|
|
3988
|
+
}
|
|
3989
|
+
await storage.set("@panel:install_tracked", String(installedAt));
|
|
3990
|
+
try {
|
|
3991
|
+
await nativeStorage.set("@paywallo:install_tracked", String(installedAt));
|
|
3787
3992
|
} catch {
|
|
3788
3993
|
}
|
|
3789
3994
|
}
|
|
3790
|
-
async callDeferredMatch(apiClient, deviceData, adIds, anonId) {
|
|
3995
|
+
async callDeferredMatch(apiClient, deviceData, adIds, anonId, installedAt) {
|
|
3791
3996
|
const storage = new SecureStorage(this.debug);
|
|
3792
3997
|
const alreadyMatched = await storage.get("@panel:deferred_match_done");
|
|
3793
3998
|
if (alreadyMatched) return;
|
|
@@ -3795,7 +4000,7 @@ var InstallTracker = class {
|
|
|
3795
4000
|
const timer = setTimeout(() => controller.abort(), 5e3);
|
|
3796
4001
|
try {
|
|
3797
4002
|
const baseUrl = apiClient.getBaseUrl();
|
|
3798
|
-
const appKey = apiClient.
|
|
4003
|
+
const appKey = apiClient.getAppKey();
|
|
3799
4004
|
await fetch(`${baseUrl}/api/v1/attribution/deferred-match`, {
|
|
3800
4005
|
method: "POST",
|
|
3801
4006
|
headers: { "Content-Type": "application/json", "X-App-Key": appKey },
|
|
@@ -3807,7 +4012,8 @@ var InstallTracker = class {
|
|
|
3807
4012
|
screenHeight: deviceData.screenHeight,
|
|
3808
4013
|
timezone: deviceData.timezone,
|
|
3809
4014
|
locale: deviceData.locale,
|
|
3810
|
-
fbAnonId: anonId
|
|
4015
|
+
fbAnonId: anonId,
|
|
4016
|
+
installTimestamp: new Date(installedAt).toISOString()
|
|
3811
4017
|
}),
|
|
3812
4018
|
signal: controller.signal
|
|
3813
4019
|
});
|
|
@@ -3932,7 +4138,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3932
4138
|
error: error instanceof Error ? error.message : String(error)
|
|
3933
4139
|
});
|
|
3934
4140
|
} catch (err) {
|
|
3935
|
-
if (config.debug) console.
|
|
4141
|
+
if (config.debug) console.error("[Paywallo] reportInitFailure error", err);
|
|
3936
4142
|
}
|
|
3937
4143
|
}
|
|
3938
4144
|
reportInitSuccess(config, attempts) {
|
|
@@ -3941,7 +4147,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3941
4147
|
void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
|
|
3942
4148
|
}
|
|
3943
4149
|
} catch (err) {
|
|
3944
|
-
if (config.debug) console.
|
|
4150
|
+
if (config.debug) console.error("[Paywallo] reportInitSuccess error", err);
|
|
3945
4151
|
}
|
|
3946
4152
|
}
|
|
3947
4153
|
async resolveSessionFlags(keys, apiClient, distinctId) {
|
|
@@ -3968,7 +4174,6 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3968
4174
|
]);
|
|
3969
4175
|
this.apiClient = new ApiClient(config.appKey, config.apiUrl ?? "https://paywallo.com.br", config.debug, environment, offlineQueueEnabled, config.timeout);
|
|
3970
4176
|
queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
|
|
3971
|
-
affiliateManager.initialize(this.apiClient, config.debug);
|
|
3972
4177
|
subscriptionManager.init({
|
|
3973
4178
|
serverUrl: this.apiClient.getBaseUrl(),
|
|
3974
4179
|
appKey: config.appKey,
|
|
@@ -3989,18 +4194,26 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
3989
4194
|
if (config.sessionFlags && config.sessionFlags.length > 0) {
|
|
3990
4195
|
await this.resolveSessionFlags(config.sessionFlags, this.apiClient, distinctId);
|
|
3991
4196
|
}
|
|
4197
|
+
setNativeStorageDebug(config.debug ?? false);
|
|
4198
|
+
setNativeDeviceInfoDebug(config.debug ?? false);
|
|
3992
4199
|
this.config = config;
|
|
3993
4200
|
if (this.resolveReady) {
|
|
3994
4201
|
this.resolveReady();
|
|
3995
4202
|
this.resolveReady = null;
|
|
3996
4203
|
}
|
|
3997
|
-
if (config.debug) console.
|
|
3998
|
-
if (
|
|
4204
|
+
if (config.debug) console.log("[Paywallo INIT] complete");
|
|
4205
|
+
if (distinctId) {
|
|
4206
|
+
this.apiClient.getSubscriptionStatus(distinctId).then(() => {
|
|
4207
|
+
if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
|
|
4208
|
+
}).catch(() => {
|
|
4209
|
+
});
|
|
4210
|
+
}
|
|
4211
|
+
{
|
|
3999
4212
|
const apiClientRef = this.apiClient;
|
|
4000
4213
|
if (config.autoPreloadCampaign) {
|
|
4001
4214
|
this.autoPreloadedPlacement = config.autoPreloadCampaign;
|
|
4002
4215
|
this.autoPreloadPlacementPromise = Promise.resolve(config.autoPreloadCampaign);
|
|
4003
|
-
if (config.debug) console.
|
|
4216
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${config.autoPreloadCampaign}`);
|
|
4004
4217
|
void campaignGateService.preloadCampaign(apiClientRef, config.autoPreloadCampaign).catch(() => {
|
|
4005
4218
|
});
|
|
4006
4219
|
} else {
|
|
@@ -4008,7 +4221,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4008
4221
|
const placement = primary?.placement ?? primary?.paywall?.placement;
|
|
4009
4222
|
if (placement) {
|
|
4010
4223
|
this.autoPreloadedPlacement = placement;
|
|
4011
|
-
if (config.debug) console.
|
|
4224
|
+
if (config.debug) console.log(`[Paywallo PRELOAD] started \u2014 placement: ${placement}`);
|
|
4012
4225
|
void campaignGateService.preloadCampaign(apiClientRef, placement).catch(() => {
|
|
4013
4226
|
});
|
|
4014
4227
|
return placement;
|
|
@@ -4020,21 +4233,21 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4020
4233
|
}
|
|
4021
4234
|
async identify(options) {
|
|
4022
4235
|
this.ensureInitialized();
|
|
4023
|
-
if (this.config?.debug) console.
|
|
4236
|
+
if (this.config?.debug) console.log("[Paywallo IDENTIFY]", options ? { hasEmail: "email" in options && !!options.email, hasProperties: "properties" in options } : void 0);
|
|
4024
4237
|
await identityManager.identify(options);
|
|
4025
4238
|
}
|
|
4026
4239
|
async track(eventName, options) {
|
|
4027
4240
|
const apiClient = this.getApiClientOrThrow();
|
|
4028
4241
|
const distinctId = identityManager.getDistinctId();
|
|
4029
4242
|
if (!distinctId) {
|
|
4030
|
-
if (this.config?.debug) console.
|
|
4243
|
+
if (this.config?.debug) console.log(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
|
|
4031
4244
|
return;
|
|
4032
4245
|
}
|
|
4033
4246
|
if (!isValidEventName(eventName)) {
|
|
4034
4247
|
const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
|
|
4035
4248
|
throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
|
|
4036
4249
|
}
|
|
4037
|
-
if (this.config?.debug && eventName.startsWith("$purchase")) console.
|
|
4250
|
+
if (this.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
|
|
4038
4251
|
const sessionId = sessionManager.getSessionId();
|
|
4039
4252
|
await apiClient.trackEvent(eventName, distinctId, {
|
|
4040
4253
|
...identityManager.getProperties(),
|
|
@@ -4093,14 +4306,14 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4093
4306
|
try {
|
|
4094
4307
|
const distinctId = identityManager.getDistinctId();
|
|
4095
4308
|
if (!distinctId) {
|
|
4096
|
-
if (this.config?.debug) console.
|
|
4309
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
|
|
4097
4310
|
return { variant: null };
|
|
4098
4311
|
}
|
|
4099
4312
|
const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
|
|
4100
|
-
if (this.config?.debug) console.
|
|
4313
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
|
|
4101
4314
|
return result;
|
|
4102
4315
|
} catch (error) {
|
|
4103
|
-
if (this.config?.debug) console.
|
|
4316
|
+
if (this.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
|
|
4104
4317
|
if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
|
|
4105
4318
|
return { variant: null };
|
|
4106
4319
|
}
|
|
@@ -4133,11 +4346,11 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4133
4346
|
}
|
|
4134
4347
|
async presentCampaign(placement, context) {
|
|
4135
4348
|
this.ensureInitialized();
|
|
4136
|
-
if (this.config?.debug) console.
|
|
4349
|
+
if (this.config?.debug) console.log(`[Paywallo CAMPAIGN] presenting ${placement}`);
|
|
4137
4350
|
const result = await campaignGateService.presentCampaign(this.getApiClientOrThrow(), placement, () => this.hasActiveSubscription(), this.paywallPresenter, this.campaignPresenter, context);
|
|
4138
4351
|
if (this.config?.debug) {
|
|
4139
|
-
if (result.error) console.
|
|
4140
|
-
else console.
|
|
4352
|
+
if (result.error) console.log(`[Paywallo CAMPAIGN] ${placement} error:`, result.error.message);
|
|
4353
|
+
else console.log(`[Paywallo CAMPAIGN] ${placement} result:`, { purchased: result.purchased, cancelled: result.cancelled, restored: result.restored, skippedReason: result.skippedReason });
|
|
4141
4354
|
}
|
|
4142
4355
|
return result;
|
|
4143
4356
|
}
|
|
@@ -4147,20 +4360,20 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4147
4360
|
if (this.activeChecker) {
|
|
4148
4361
|
const result = await this.activeChecker();
|
|
4149
4362
|
const isActive2 = result === true;
|
|
4150
|
-
if (this.config?.debug) console.
|
|
4363
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (checker):", isActive2);
|
|
4151
4364
|
return isActive2;
|
|
4152
4365
|
}
|
|
4153
4366
|
const distinctId = identityManager.getDistinctId();
|
|
4154
4367
|
if (!distinctId || !this.apiClient) {
|
|
4155
|
-
if (this.config?.debug) console.
|
|
4368
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription: false (no distinctId/apiClient)");
|
|
4156
4369
|
return false;
|
|
4157
4370
|
}
|
|
4158
4371
|
const status = await this.apiClient.getSubscriptionStatus(distinctId);
|
|
4159
4372
|
const isActive = status.hasActiveSubscription === true;
|
|
4160
|
-
if (this.config?.debug) console.
|
|
4373
|
+
if (this.config?.debug) console.log("[Paywallo SUB] hasActiveSubscription (server):", isActive);
|
|
4161
4374
|
return isActive;
|
|
4162
4375
|
} catch (error) {
|
|
4163
|
-
if (this.config?.debug) console.
|
|
4376
|
+
if (this.config?.debug) console.error("[Paywallo SUB] hasActiveSubscription error", error);
|
|
4164
4377
|
if (this.apiClient) void this.apiClient.reportError("SUBSCRIPTION_CHECK_FAILED", error instanceof Error ? error.message : "hasActiveSubscription failed");
|
|
4165
4378
|
return false;
|
|
4166
4379
|
}
|
|
@@ -4181,9 +4394,9 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4181
4394
|
async restorePurchases() {
|
|
4182
4395
|
this.ensureInitialized();
|
|
4183
4396
|
if (!this.restoreHandler) throw new PaywallError(PAYWALL_ERROR_CODES.NOT_INITIALIZED, "PaywalloProvider not mounted. Wrap your app with <PaywalloProvider>");
|
|
4184
|
-
if (this.config?.debug) console.
|
|
4397
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] starting");
|
|
4185
4398
|
const result = await this.restoreHandler();
|
|
4186
|
-
if (this.config?.debug) console.
|
|
4399
|
+
if (this.config?.debug) console.log("[Paywallo RESTORE] result:", { success: result.success, restoredProducts: result.restoredProducts?.length ?? 0 });
|
|
4187
4400
|
return result;
|
|
4188
4401
|
}
|
|
4189
4402
|
async reset() {
|
|
@@ -4193,6 +4406,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4193
4406
|
async fullReset() {
|
|
4194
4407
|
const wasDebug = this.config?.debug ?? false;
|
|
4195
4408
|
await sessionManager.endSession();
|
|
4409
|
+
sessionManager.destroy();
|
|
4196
4410
|
await identityManager.reset();
|
|
4197
4411
|
await subscriptionCache.invalidateAll();
|
|
4198
4412
|
queueProcessor.dispose();
|
|
@@ -4201,6 +4415,7 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4201
4415
|
campaignGateService.clearPreloaded();
|
|
4202
4416
|
this.cleanupNetworkRecovery();
|
|
4203
4417
|
this.sessionFlagsMap.clear();
|
|
4418
|
+
this.apiClient?.dispose();
|
|
4204
4419
|
this.apiClient = null;
|
|
4205
4420
|
this.config = null;
|
|
4206
4421
|
this.initPromise = null;
|
|
@@ -4279,6 +4494,10 @@ var _PaywalloClientClass = class _PaywalloClientClass {
|
|
|
4279
4494
|
registerEmergencyPaywallHandler(h) {
|
|
4280
4495
|
sessionManager.registerEmergencyPaywallHandler(h);
|
|
4281
4496
|
}
|
|
4497
|
+
async getEmergencyPaywall() {
|
|
4498
|
+
if (!this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized");
|
|
4499
|
+
return this.apiClient.getEmergencyPaywall();
|
|
4500
|
+
}
|
|
4282
4501
|
async requireSubscription(paywallPlacement) {
|
|
4283
4502
|
this.ensureInitialized();
|
|
4284
4503
|
if (await this.hasActiveSubscription()) return true;
|
|
@@ -4533,6 +4752,7 @@ function usePurchase() {
|
|
|
4533
4752
|
|
|
4534
4753
|
// src/hooks/useSubscription.ts
|
|
4535
4754
|
import { useCallback as useCallback8, useEffect as useEffect6, useState as useState8 } from "react";
|
|
4755
|
+
var EMPTY_ENTITLEMENTS = [];
|
|
4536
4756
|
function useSubscription() {
|
|
4537
4757
|
const [status, setStatus] = useState8(null);
|
|
4538
4758
|
const [isLoading, setIsLoading] = useState8(true);
|
|
@@ -4568,7 +4788,8 @@ function useSubscription() {
|
|
|
4568
4788
|
setStatus(s);
|
|
4569
4789
|
setIsLoading(false);
|
|
4570
4790
|
}).catch((err) => {
|
|
4571
|
-
|
|
4791
|
+
const e = err instanceof PurchaseError ? err : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, String(err));
|
|
4792
|
+
setError(e);
|
|
4572
4793
|
setIsLoading(false);
|
|
4573
4794
|
});
|
|
4574
4795
|
const removeListener = subscriptionManager.addListener((newStatus) => {
|
|
@@ -4583,7 +4804,7 @@ function useSubscription() {
|
|
|
4583
4804
|
isActive: status?.hasActiveSubscription ?? false,
|
|
4584
4805
|
isLoading,
|
|
4585
4806
|
error,
|
|
4586
|
-
entitlements: status?.entitlements ??
|
|
4807
|
+
entitlements: status?.entitlements ?? EMPTY_ENTITLEMENTS,
|
|
4587
4808
|
refresh,
|
|
4588
4809
|
restore
|
|
4589
4810
|
};
|
|
@@ -4715,7 +4936,7 @@ function useCampaignPreload() {
|
|
|
4715
4936
|
campaignId: campaign.campaignId,
|
|
4716
4937
|
variantKey: campaign.variantKey
|
|
4717
4938
|
});
|
|
4718
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
4939
|
+
if (PaywalloClient.getConfig()?.debug) console.log(`[Paywallo WEBVIEW] mounting hidden \u2014 placement: ${placement}`);
|
|
4719
4940
|
return { success: true };
|
|
4720
4941
|
} catch (error) {
|
|
4721
4942
|
return {
|
|
@@ -4750,7 +4971,7 @@ function useProductLoader() {
|
|
|
4750
4971
|
for (const p of loaded) map.set(p.productId, p);
|
|
4751
4972
|
setProducts(map);
|
|
4752
4973
|
} catch (err) {
|
|
4753
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
4974
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] refreshProducts failed", err);
|
|
4754
4975
|
} finally {
|
|
4755
4976
|
setIsLoadingProducts(false);
|
|
4756
4977
|
}
|
|
@@ -4790,7 +5011,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4790
5011
|
clearPreloadedWebView();
|
|
4791
5012
|
resolvePreloadedPaywall({ presented: true, purchased: true, cancelled: false, restored: false });
|
|
4792
5013
|
} catch (error) {
|
|
4793
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5014
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded purchase failed", error);
|
|
4794
5015
|
setShowingPreloadedWebView(false);
|
|
4795
5016
|
clearPreloadedWebView();
|
|
4796
5017
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4811,7 +5032,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4811
5032
|
clearPreloadedWebView();
|
|
4812
5033
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: true });
|
|
4813
5034
|
} catch (error) {
|
|
4814
|
-
if (PaywalloClient.getConfig()?.debug) console.
|
|
5035
|
+
if (PaywalloClient.getConfig()?.debug) console.error("[Paywallo] preloaded restore failed", error);
|
|
4815
5036
|
setShowingPreloadedWebView(false);
|
|
4816
5037
|
clearPreloadedWebView();
|
|
4817
5038
|
resolvePreloadedPaywall({ presented: true, purchased: false, cancelled: false, restored: false });
|
|
@@ -4823,12 +5044,29 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
|
|
|
4823
5044
|
// src/hooks/useSubscriptionSync.ts
|
|
4824
5045
|
import { useCallback as useCallback12, useRef as useRef7, useState as useState11 } from "react";
|
|
4825
5046
|
var CACHE_TTL_MS2 = 5 * 60 * 1e3;
|
|
4826
|
-
var ACTIVE_STATUSES = ["active", "
|
|
5047
|
+
var ACTIVE_STATUSES = ["active", "in_grace_period"];
|
|
5048
|
+
var PUBLIC_TO_DOMAIN_STATUS = {
|
|
5049
|
+
active: "active",
|
|
5050
|
+
expired: "expired",
|
|
5051
|
+
in_billing_retry: "billing_retry",
|
|
5052
|
+
in_grace_period: "grace_period",
|
|
5053
|
+
revoked: "unknown",
|
|
5054
|
+
cancelled: "canceled"
|
|
5055
|
+
};
|
|
5056
|
+
var DOMAIN_TO_PUBLIC_STATUS = {
|
|
5057
|
+
active: "active",
|
|
5058
|
+
expired: "expired",
|
|
5059
|
+
billing_retry: "in_billing_retry",
|
|
5060
|
+
grace_period: "in_grace_period",
|
|
5061
|
+
canceled: "cancelled",
|
|
5062
|
+
paused: "expired",
|
|
5063
|
+
unknown: "expired"
|
|
5064
|
+
};
|
|
4827
5065
|
function toDomainSubscriptionInfo(sub) {
|
|
4828
5066
|
return {
|
|
4829
5067
|
id: "",
|
|
4830
5068
|
productId: sub.productId,
|
|
4831
|
-
status: sub.status,
|
|
5069
|
+
status: PUBLIC_TO_DOMAIN_STATUS[sub.status] ?? "unknown",
|
|
4832
5070
|
expiresAt: sub.expiresAt ?? /* @__PURE__ */ new Date(0),
|
|
4833
5071
|
originalPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
4834
5072
|
latestPurchaseDate: /* @__PURE__ */ new Date(0),
|
|
@@ -4842,7 +5080,7 @@ function toDomainResponse(status) {
|
|
|
4842
5080
|
return {
|
|
4843
5081
|
hasActiveSubscription: status.hasActiveSubscription,
|
|
4844
5082
|
subscription: status.subscription ? toDomainSubscriptionInfo(status.subscription) : null,
|
|
4845
|
-
entitlements: []
|
|
5083
|
+
entitlements: status.entitlements ?? []
|
|
4846
5084
|
};
|
|
4847
5085
|
}
|
|
4848
5086
|
function isSubscriptionStillActive(sub) {
|
|
@@ -4851,6 +5089,10 @@ function isSubscriptionStillActive(sub) {
|
|
|
4851
5089
|
if (sub.expiresAt && sub.expiresAt.getTime() !== 0 && sub.expiresAt < /* @__PURE__ */ new Date()) return false;
|
|
4852
5090
|
return true;
|
|
4853
5091
|
}
|
|
5092
|
+
function subscriptionInfoToSub(s) {
|
|
5093
|
+
const status = DOMAIN_TO_PUBLIC_STATUS[s.status] ?? "expired";
|
|
5094
|
+
return { productId: s.productId, status, expiresAt: s.expiresAt ?? null, platform: s.platform, autoRenewEnabled: s.willRenew, inGracePeriod: status === "in_grace_period" };
|
|
5095
|
+
}
|
|
4854
5096
|
async function resolveOfflineFromCache(distinctId) {
|
|
4855
5097
|
try {
|
|
4856
5098
|
const cached = await subscriptionCache.get(distinctId);
|
|
@@ -4891,23 +5133,12 @@ function useSubscriptionSync() {
|
|
|
4891
5133
|
const distinctId = PaywalloClient.getDistinctId();
|
|
4892
5134
|
if (!apiClient || !distinctId) return false;
|
|
4893
5135
|
const cached = cacheRef.current;
|
|
4894
|
-
if (cached && cached.expiresAt > Date.now())
|
|
4895
|
-
return isActiveCacheEntry(cached);
|
|
4896
|
-
}
|
|
5136
|
+
if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
|
|
4897
5137
|
try {
|
|
4898
5138
|
const persisted = await subscriptionCache.get(distinctId);
|
|
4899
5139
|
if (persisted && !persisted.isStale) {
|
|
4900
|
-
const
|
|
4901
|
-
|
|
4902
|
-
productId: persistedSub.productId,
|
|
4903
|
-
status: persistedSub.status,
|
|
4904
|
-
expiresAt: persistedSub.expiresAt ?? null,
|
|
4905
|
-
platform: persistedSub.platform,
|
|
4906
|
-
autoRenewEnabled: persistedSub.willRenew,
|
|
4907
|
-
inGracePeriod: persistedSub.status === "grace_period"
|
|
4908
|
-
} : null;
|
|
4909
|
-
const stillActive = isSubscriptionStillActive(sub);
|
|
4910
|
-
if (stillActive) {
|
|
5140
|
+
const sub = persisted.data.subscription ? subscriptionInfoToSub(persisted.data.subscription) : null;
|
|
5141
|
+
if (isSubscriptionStillActive(sub)) {
|
|
4911
5142
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4912
5143
|
setSubscription(sub);
|
|
4913
5144
|
return true;
|
|
@@ -4916,17 +5147,14 @@ function useSubscriptionSync() {
|
|
|
4916
5147
|
} catch {
|
|
4917
5148
|
}
|
|
4918
5149
|
try {
|
|
4919
|
-
const
|
|
4920
|
-
const sub =
|
|
4921
|
-
...status.subscription,
|
|
4922
|
-
expiresAt: status.subscription.expiresAt ? new Date(status.subscription.expiresAt) : null
|
|
4923
|
-
} : null;
|
|
5150
|
+
const apiStatus = await apiClient.getSubscriptionStatus(distinctId);
|
|
5151
|
+
const sub = apiStatus.subscription ? { ...apiStatus.subscription, expiresAt: apiStatus.subscription.expiresAt ? new Date(apiStatus.subscription.expiresAt) : null } : null;
|
|
4924
5152
|
cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
|
|
4925
5153
|
setSubscription(sub);
|
|
4926
|
-
void subscriptionCache.set(distinctId, toDomainResponse(
|
|
4927
|
-
return
|
|
5154
|
+
void subscriptionCache.set(distinctId, toDomainResponse(apiStatus));
|
|
5155
|
+
return apiStatus.hasActiveSubscription === true;
|
|
4928
5156
|
} catch {
|
|
4929
|
-
return
|
|
5157
|
+
return resolveOfflineFromCache(distinctId);
|
|
4930
5158
|
}
|
|
4931
5159
|
}, []);
|
|
4932
5160
|
const getSubscription = useCallback12(async () => {
|
|
@@ -4941,16 +5169,7 @@ function useSubscriptionSync() {
|
|
|
4941
5169
|
try {
|
|
4942
5170
|
const persisted = await subscriptionCache.get(distinctId);
|
|
4943
5171
|
if (!persisted?.data.subscription) return null;
|
|
4944
|
-
|
|
4945
|
-
const normalisedStatus = s.status === "grace_period" ? "in_grace_period" : s.status;
|
|
4946
|
-
return {
|
|
4947
|
-
productId: s.productId,
|
|
4948
|
-
status: normalisedStatus,
|
|
4949
|
-
expiresAt: s.expiresAt ?? null,
|
|
4950
|
-
platform: s.platform,
|
|
4951
|
-
autoRenewEnabled: s.willRenew,
|
|
4952
|
-
inGracePeriod: normalisedStatus === "in_grace_period"
|
|
4953
|
-
};
|
|
5172
|
+
return subscriptionInfoToSub(persisted.data.subscription);
|
|
4954
5173
|
} catch {
|
|
4955
5174
|
return null;
|
|
4956
5175
|
}
|
|
@@ -4972,6 +5191,8 @@ function PaywalloProvider({ children, config }) {
|
|
|
4972
5191
|
const clearPreloadedWebView = useCallback13(() => setPreloadedWebView(null), [setPreloadedWebView]);
|
|
4973
5192
|
const autoPreloadTriggeredRef = useRef8(false);
|
|
4974
5193
|
const preloadedWebViewRef = useRef8(preloadedWebView);
|
|
5194
|
+
const pollTimerRef = useRef8(null);
|
|
5195
|
+
const pollCancelledRef = useRef8(false);
|
|
4975
5196
|
useEffect7(() => {
|
|
4976
5197
|
preloadedWebViewRef.current = preloadedWebView;
|
|
4977
5198
|
}, [preloadedWebView]);
|
|
@@ -4980,21 +5201,21 @@ function PaywalloProvider({ children, config }) {
|
|
|
4980
5201
|
});
|
|
4981
5202
|
}, []);
|
|
4982
5203
|
const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
|
|
4983
|
-
const
|
|
4984
|
-
const slideAnim = useRef8(new Animated3.Value(
|
|
5204
|
+
const SCREEN_HEIGHT = useMemo2(() => Dimensions4.get("window").height, []);
|
|
5205
|
+
const slideAnim = useRef8(new Animated3.Value(SCREEN_HEIGHT)).current;
|
|
4985
5206
|
const handlePreloadedWebViewReady = useCallback13(() => {
|
|
4986
5207
|
if (PaywalloClient.getConfig()?.debug) {
|
|
4987
|
-
console.
|
|
5208
|
+
console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
|
|
4988
5209
|
}
|
|
4989
5210
|
baseHandlePreloadedWebViewReady();
|
|
4990
|
-
}, [
|
|
5211
|
+
}, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
|
|
4991
5212
|
const handlePreloadedClose = useCallback13(() => {
|
|
4992
5213
|
const placement = preloadedWebView?.placement;
|
|
4993
|
-
Animated3.timing(slideAnim, { toValue:
|
|
5214
|
+
Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
|
|
4994
5215
|
baseHandlePreloadedClose();
|
|
4995
5216
|
if (placement) triggerRepreload(placement);
|
|
4996
5217
|
});
|
|
4997
|
-
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim,
|
|
5218
|
+
}, [baseHandlePreloadedClose, preloadedWebView, triggerRepreload, slideAnim, SCREEN_HEIGHT]);
|
|
4998
5219
|
const { handlePreloadedPurchase: rawPreloadedPurchase, handlePreloadedRestore } = usePurchaseOrchestrator(
|
|
4999
5220
|
preloadedWebView,
|
|
5000
5221
|
resolvePreloadedPaywall,
|
|
@@ -5012,21 +5233,28 @@ function PaywalloProvider({ children, config }) {
|
|
|
5012
5233
|
setIsPreloadPurchasing(false);
|
|
5013
5234
|
}
|
|
5014
5235
|
}, [rawPreloadedPurchase]);
|
|
5236
|
+
const configRef = useRef8(config);
|
|
5015
5237
|
useEffect7(() => {
|
|
5238
|
+
let mounted = true;
|
|
5016
5239
|
initLocalization({ detectDevice: true });
|
|
5017
|
-
PaywalloClient.init(
|
|
5240
|
+
PaywalloClient.init(configRef.current).then(async () => {
|
|
5241
|
+
if (!mounted) return;
|
|
5018
5242
|
setDistinctId(PaywalloClient.getDistinctId());
|
|
5019
5243
|
setIsInitialized(true);
|
|
5020
5244
|
if (!autoPreloadTriggeredRef.current) {
|
|
5021
5245
|
autoPreloadTriggeredRef.current = true;
|
|
5022
5246
|
const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
|
|
5023
|
-
if (placement) void preloadCampaign(placement).catch(() => {
|
|
5247
|
+
if (mounted && placement) void preloadCampaign(placement).catch(() => {
|
|
5024
5248
|
});
|
|
5025
5249
|
}
|
|
5026
5250
|
}).catch((err) => {
|
|
5251
|
+
if (!mounted) return;
|
|
5027
5252
|
setInitError(err instanceof Error ? err : new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, String(err)));
|
|
5028
5253
|
});
|
|
5029
|
-
|
|
5254
|
+
return () => {
|
|
5255
|
+
mounted = false;
|
|
5256
|
+
};
|
|
5257
|
+
}, [preloadCampaign]);
|
|
5030
5258
|
const getPaywallConfig = useCallback13(async (p) => {
|
|
5031
5259
|
const api = PaywalloClient.getApiClient();
|
|
5032
5260
|
return api ? api.getPaywall(p) : null;
|
|
@@ -5127,15 +5355,17 @@ function PaywalloProvider({ children, config }) {
|
|
|
5127
5355
|
}, [activePaywall]);
|
|
5128
5356
|
const bridgePaywallPresenter = useCallback13((placement) => {
|
|
5129
5357
|
if (PaywalloClient.getConfig()?.debug) {
|
|
5130
|
-
console.
|
|
5358
|
+
console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
|
|
5131
5359
|
}
|
|
5132
5360
|
if (preloadedWebViewRef.current?.placement === placement) {
|
|
5133
5361
|
if (preloadedWebViewRef.current.loaded) {
|
|
5134
5362
|
return presentPreloadedWebView();
|
|
5135
5363
|
}
|
|
5364
|
+
pollCancelledRef.current = false;
|
|
5136
5365
|
return new Promise((resolve) => {
|
|
5137
5366
|
let elapsed = 0;
|
|
5138
5367
|
const poll = () => {
|
|
5368
|
+
if (pollCancelledRef.current) return;
|
|
5139
5369
|
if (preloadedWebViewRef.current?.loaded) {
|
|
5140
5370
|
resolve(presentPreloadedWebView());
|
|
5141
5371
|
return;
|
|
@@ -5145,9 +5375,9 @@ function PaywalloProvider({ children, config }) {
|
|
|
5145
5375
|
resolve(presentCampaign(placement));
|
|
5146
5376
|
return;
|
|
5147
5377
|
}
|
|
5148
|
-
setTimeout(poll, 100);
|
|
5378
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5149
5379
|
};
|
|
5150
|
-
setTimeout(poll, 100);
|
|
5380
|
+
pollTimerRef.current = setTimeout(poll, 100);
|
|
5151
5381
|
});
|
|
5152
5382
|
}
|
|
5153
5383
|
return presentCampaign(placement);
|
|
@@ -5165,6 +5395,18 @@ function PaywalloProvider({ children, config }) {
|
|
|
5165
5395
|
PaywalloClient.registerActiveChecker(hasActiveSubscription);
|
|
5166
5396
|
PaywalloClient.registerRestoreHandler(restorePurchases);
|
|
5167
5397
|
PaywalloClient.registerEmergencyPaywallHandler(onEmergency);
|
|
5398
|
+
return () => {
|
|
5399
|
+
pollCancelledRef.current = true;
|
|
5400
|
+
if (pollTimerRef.current !== null) {
|
|
5401
|
+
clearTimeout(pollTimerRef.current);
|
|
5402
|
+
pollTimerRef.current = null;
|
|
5403
|
+
}
|
|
5404
|
+
PaywalloClient.registerPaywallPresenter(null);
|
|
5405
|
+
PaywalloClient.registerSubscriptionGetter(null);
|
|
5406
|
+
PaywalloClient.registerActiveChecker(null);
|
|
5407
|
+
PaywalloClient.registerRestoreHandler(null);
|
|
5408
|
+
PaywalloClient.registerEmergencyPaywallHandler(null);
|
|
5409
|
+
};
|
|
5168
5410
|
}, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
|
|
5169
5411
|
const isReady = isInitialized && !isLoadingProducts;
|
|
5170
5412
|
const noOp = useCallback13(() => {
|
|
@@ -5205,10 +5447,10 @@ function PaywalloProvider({ children, config }) {
|
|
|
5205
5447
|
]);
|
|
5206
5448
|
useLayoutEffect(() => {
|
|
5207
5449
|
if (showingPreloadedWebView) {
|
|
5208
|
-
slideAnim.setValue(
|
|
5450
|
+
slideAnim.setValue(SCREEN_HEIGHT);
|
|
5209
5451
|
Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
|
|
5210
5452
|
}
|
|
5211
|
-
}, [showingPreloadedWebView, slideAnim,
|
|
5453
|
+
}, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
|
|
5212
5454
|
const preloadStyle = [
|
|
5213
5455
|
styles3.preloadOverlay,
|
|
5214
5456
|
showingPreloadedWebView ? styles3.visible : styles3.hidden,
|
|
@@ -5374,10 +5616,7 @@ function hasVariables(text) {
|
|
|
5374
5616
|
var Paywallo = PaywalloClient;
|
|
5375
5617
|
var index_default = Paywallo;
|
|
5376
5618
|
export {
|
|
5377
|
-
AFFILIATE_ERROR_CODES,
|
|
5378
5619
|
ANALYTICS_ERROR_CODES,
|
|
5379
|
-
AffiliateError,
|
|
5380
|
-
AffiliateManager,
|
|
5381
5620
|
AnalyticsError,
|
|
5382
5621
|
ApiClient,
|
|
5383
5622
|
CAMPAIGN_ERROR_CODES,
|
|
@@ -5395,7 +5634,6 @@ export {
|
|
|
5395
5634
|
PaywallError,
|
|
5396
5635
|
PaywallModal,
|
|
5397
5636
|
Paywallo,
|
|
5398
|
-
PaywalloAffiliate,
|
|
5399
5637
|
PaywalloClient,
|
|
5400
5638
|
PaywalloContext,
|
|
5401
5639
|
PaywalloError,
|
|
@@ -5405,7 +5643,6 @@ export {
|
|
|
5405
5643
|
SessionError,
|
|
5406
5644
|
SessionManager,
|
|
5407
5645
|
SubscriptionCache,
|
|
5408
|
-
affiliateManager,
|
|
5409
5646
|
createPurchaseError,
|
|
5410
5647
|
index_default as default,
|
|
5411
5648
|
detectDeviceLanguage,
|