@virex-tech/paywallo-sdk 1.0.1 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -860,6 +860,11 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
860
860
  const storeProducts = await getIAPService().loadProducts(productIds);
861
861
  const map = /* @__PURE__ */ new Map();
862
862
  for (const p of storeProducts) map.set(p.productId, p);
863
+ if (PaywalloClient.getConfig()?.debug) {
864
+ for (const [id, p] of map) {
865
+ console.warn(`[Paywallo][CURRENCY-CHECK] product=${id} currency=${p.currency} price="${p.price}" localizedPrice="${p.localizedPrice}" priceValue=${p.priceValue} fallbackUSD=${!p.currency || p.currency === "USD" ? "POSSIBLE" : "NO"}`);
866
+ }
867
+ }
863
868
  setProducts(map);
864
869
  } catch {
865
870
  setProducts(/* @__PURE__ */ new Map());
@@ -2252,6 +2257,8 @@ var ApiCache = class {
2252
2257
  constructor() {
2253
2258
  this.CACHE_TTL = 3e5;
2254
2259
  // 5 minutes
2260
+ this.CACHE_NULL_TTL = 3e4;
2261
+ // 30 seconds — for null/404 variants
2255
2262
  this.MAX_SIZE = 200;
2256
2263
  this.variantCache = /* @__PURE__ */ new Map();
2257
2264
  this.paywallCache = /* @__PURE__ */ new Map();
@@ -2260,27 +2267,34 @@ var ApiCache = class {
2260
2267
  getCached(cache, key) {
2261
2268
  const entry = cache.get(key);
2262
2269
  if (!entry) return void 0;
2263
- if (Date.now() - entry.ts < this.CACHE_TTL) return entry.value;
2270
+ const ttl = entry.ttl ?? this.CACHE_TTL;
2271
+ if (Date.now() - entry.ts < ttl) return entry.value;
2264
2272
  cache.delete(key);
2265
2273
  return void 0;
2266
2274
  }
2267
- setCached(cache, key, value) {
2268
- cache.set(key, { value, ts: Date.now() });
2275
+ setCached(cache, key, value, ttlOverride) {
2276
+ const entry = { value, ts: Date.now() };
2277
+ if (ttlOverride !== void 0) entry.ttl = ttlOverride;
2278
+ cache.set(key, entry);
2269
2279
  }
2270
2280
  evictExpiredEntries(cache) {
2271
2281
  if (cache.size <= this.MAX_SIZE) return;
2272
2282
  const now = Date.now();
2273
2283
  for (const [key, entry] of cache) {
2274
- if (now - entry.ts > this.CACHE_TTL) cache.delete(key);
2284
+ const ttl = entry.ttl ?? this.CACHE_TTL;
2285
+ if (now - entry.ts > ttl) cache.delete(key);
2275
2286
  }
2276
2287
  }
2277
2288
  getVariant(key) {
2278
2289
  return this.getCached(this.variantCache, key);
2279
2290
  }
2280
- setVariant(key, value) {
2281
- this.setCached(this.variantCache, key, value);
2291
+ setVariant(key, value, ttlOverride) {
2292
+ this.setCached(this.variantCache, key, value, ttlOverride);
2282
2293
  this.evictExpiredEntries(this.variantCache);
2283
2294
  }
2295
+ get nullTTL() {
2296
+ return this.CACHE_NULL_TTL;
2297
+ }
2284
2298
  getStaleVariant(key) {
2285
2299
  const entry = this.variantCache.get(key);
2286
2300
  if (entry && Date.now() - entry.ts < 864e5) return entry.value;
@@ -2289,8 +2303,8 @@ var ApiCache = class {
2289
2303
  getPaywall(key) {
2290
2304
  return this.getCached(this.paywallCache, key);
2291
2305
  }
2292
- setPaywall(key, value) {
2293
- this.setCached(this.paywallCache, key, value);
2306
+ setPaywall(key, value, ttlOverride) {
2307
+ this.setCached(this.paywallCache, key, value, ttlOverride);
2294
2308
  this.evictExpiredEntries(this.paywallCache);
2295
2309
  }
2296
2310
  getStalePaywall(key) {
@@ -2316,10 +2330,10 @@ var ApiCache = class {
2316
2330
  var SDK_VERSION = "1.0.0";
2317
2331
  var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
2318
2332
  var ApiClient = class {
2319
- constructor(appKey, baseUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2333
+ constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2320
2334
  this.cache = new ApiCache();
2321
2335
  this.appKey = appKey;
2322
- this.baseUrl = baseUrl ?? DEFAULT_BASE_URL;
2336
+ this.baseUrl = DEFAULT_BASE_URL;
2323
2337
  this.webUrl = deriveWebUrl(this.baseUrl);
2324
2338
  this.debug = debug;
2325
2339
  this.environment = environment;
@@ -2406,14 +2420,18 @@ var ApiClient = class {
2406
2420
  const hit = this.cache.getVariant(key);
2407
2421
  if (hit !== void 0) return hit;
2408
2422
  try {
2409
- const url = new URL(`${this.baseUrl}/api/v1/flags/${flagKey}`);
2410
- url.searchParams.set("distinctId", distinctId);
2411
- const res = await this.get(url.toString());
2423
+ const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
2412
2424
  if (res.status === 404) {
2413
2425
  const v = { variant: null };
2414
- this.cache.setVariant(key, v);
2426
+ this.cache.setVariant(key, v, this.cache.nullTTL);
2415
2427
  return v;
2416
2428
  }
2429
+ if (!res.ok) {
2430
+ this.log("Non-OK response for variant:", flagKey, res.status);
2431
+ const stale = this.cache.getStaleVariant(key);
2432
+ if (stale !== void 0) return stale;
2433
+ return { variant: null };
2434
+ }
2417
2435
  this.cache.setVariant(key, res.data);
2418
2436
  return res.data;
2419
2437
  } catch (error) {
@@ -2429,7 +2447,13 @@ var ApiClient = class {
2429
2447
  try {
2430
2448
  const res = await this.get(`/api/v1/paywalls/${placement}`);
2431
2449
  if (res.status === 404) {
2432
- this.cache.setPaywall(placement, null);
2450
+ this.cache.setPaywall(placement, null, this.cache.nullTTL);
2451
+ return null;
2452
+ }
2453
+ if (!res.ok) {
2454
+ this.log("Non-OK response for paywall:", placement, res.status);
2455
+ const stale = this.cache.getStalePaywall(placement);
2456
+ if (stale !== void 0) return stale;
2433
2457
  return null;
2434
2458
  }
2435
2459
  this.cache.setPaywall(placement, res.data);
@@ -2495,6 +2519,10 @@ var ApiClient = class {
2495
2519
  const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
2496
2520
  const response = await this.get(url);
2497
2521
  if (response.status === 404) return { value: false, flagKey };
2522
+ if (!response.ok) {
2523
+ this.log("Non-OK response for conditional flag:", flagKey, response.status);
2524
+ return { value: false, flagKey };
2525
+ }
2498
2526
  this.log("Got conditional flag:", flagKey, response.data.value);
2499
2527
  return { value: response.data.value, flagKey };
2500
2528
  } catch {
@@ -3015,6 +3043,13 @@ var InstallTracker = class {
3015
3043
  const storage = new SecureStorage(this.debug);
3016
3044
  const alreadyTracked = await storage.get("install_tracked");
3017
3045
  if (alreadyTracked) return;
3046
+ let fallbackTracked = null;
3047
+ try {
3048
+ const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3049
+ fallbackTracked = await AsyncStorage3.getItem("@paywallo:install_tracked");
3050
+ } catch {
3051
+ }
3052
+ if (fallbackTracked) return;
3018
3053
  const distinctId = identityManager.getDistinctId();
3019
3054
  const sessionId = sessionManager.getSessionId();
3020
3055
  const installedAt = Date.now();
@@ -3076,6 +3111,11 @@ var InstallTracker = class {
3076
3111
  attStatus: adIds.attStatus
3077
3112
  });
3078
3113
  const stored = await storage.set("install_tracked", String(installedAt));
3114
+ try {
3115
+ const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3116
+ await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
3117
+ } catch {
3118
+ }
3079
3119
  if (this.debug && !stored) {
3080
3120
  console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
3081
3121
  }
@@ -3092,7 +3132,7 @@ function isValidEventName(name) {
3092
3132
  if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3093
3133
  return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3094
3134
  }
3095
- var PaywalloClientClass = class {
3135
+ var _PaywalloClientClass = class _PaywalloClientClass {
3096
3136
  constructor() {
3097
3137
  this.config = null;
3098
3138
  this.apiClient = null;
@@ -3103,45 +3143,121 @@ var PaywalloClientClass = class {
3103
3143
  this.activeChecker = null;
3104
3144
  this.restoreHandler = null;
3105
3145
  this.lastOnboardingStepTimestamp = null;
3146
+ this.pendingConfig = null;
3147
+ this.networkCleanup = null;
3148
+ this.initAttempts = 0;
3106
3149
  }
3107
3150
  async init(config) {
3108
- if (this.config && this.apiClient) return;
3151
+ console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
3152
+ if (this.config && this.apiClient) {
3153
+ console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
3154
+ return;
3155
+ }
3109
3156
  if (this.initPromise) {
3157
+ console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
3110
3158
  await this.initPromise;
3159
+ console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
3111
3160
  return;
3112
3161
  }
3113
3162
  if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
3114
- this.initPromise = this.doInit(config);
3163
+ console.warn("[Paywallo INIT]", "Starting fresh init...");
3164
+ this.pendingConfig = config;
3165
+ this.initAttempts = 0;
3166
+ this.initPromise = this.initWithRetry(config);
3115
3167
  try {
3116
3168
  await this.initPromise;
3169
+ console.warn("[Paywallo INIT]", "init() SUCCESS");
3117
3170
  } catch (error) {
3171
+ console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
3118
3172
  this.initPromise = null;
3119
3173
  this.config = null;
3120
3174
  this.apiClient = null;
3175
+ this.reportInitFailure(config, error, "ALL_RETRIES_EXHAUSTED");
3176
+ this.setupNetworkRecovery(config);
3177
+ throw error;
3178
+ }
3179
+ }
3180
+ async initWithRetry(config) {
3181
+ while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
3121
3182
  try {
3122
- const tempClient = new ApiClient(config.appKey, config.baseUrl, config.debug);
3123
- void tempClient.reportError("INIT_FAILED", error instanceof Error ? error.message : String(error));
3124
- } catch {
3183
+ await this.doInit(config);
3184
+ if (this.initAttempts > 0) {
3185
+ this.reportInitSuccess(config, this.initAttempts);
3186
+ }
3187
+ this.cleanupNetworkRecovery();
3188
+ this.pendingConfig = null;
3189
+ return;
3190
+ } catch (error) {
3191
+ this.initAttempts++;
3192
+ if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
3193
+ const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
3194
+ if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
3195
+ await new Promise((resolve) => setTimeout(resolve, delay));
3125
3196
  }
3126
- throw error;
3197
+ }
3198
+ }
3199
+ setupNetworkRecovery(config) {
3200
+ if (this.networkCleanup) return;
3201
+ if (!networkMonitor.isInitialized()) return;
3202
+ this.networkCleanup = networkMonitor.addListener((state) => {
3203
+ if (state !== "online") return;
3204
+ if (this.config && this.apiClient) return;
3205
+ if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
3206
+ this.initPromise = this.initWithRetry(config);
3207
+ this.initPromise.then(() => {
3208
+ if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
3209
+ }).catch(() => {
3210
+ this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
3211
+ });
3212
+ });
3213
+ }
3214
+ cleanupNetworkRecovery() {
3215
+ if (this.networkCleanup) {
3216
+ this.networkCleanup();
3217
+ this.networkCleanup = null;
3218
+ }
3219
+ }
3220
+ reportInitFailure(config, error, reason) {
3221
+ try {
3222
+ const tempClient = new ApiClient(config.appKey, config.debug);
3223
+ void tempClient.reportError("INIT_FAILED", reason, {
3224
+ attempts: this.initAttempts,
3225
+ error: error instanceof Error ? error.message : String(error)
3226
+ });
3227
+ } catch {
3228
+ }
3229
+ }
3230
+ reportInitSuccess(config, attempts) {
3231
+ try {
3232
+ if (this.apiClient) {
3233
+ void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
3234
+ }
3235
+ } catch {
3127
3236
  }
3128
3237
  }
3129
3238
  async doInit(config) {
3130
3239
  const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
3131
3240
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
3241
+ console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
3132
3242
  await Promise.all([
3133
3243
  networkMonitor.initialize({ debug: config.debug }),
3134
3244
  offlineQueue.initialize({ debug: config.debug }).then(
3135
3245
  () => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
3136
3246
  )
3137
3247
  ]);
3138
- this.apiClient = new ApiClient(config.appKey, config.baseUrl, config.debug, environment, offlineQueueEnabled, config.timeout);
3248
+ console.warn("[Paywallo INIT]", "Step 1 DONE");
3249
+ console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
3250
+ this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
3139
3251
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3140
3252
  affiliateManager.initialize(this.apiClient, config.debug);
3253
+ console.warn("[Paywallo INIT]", "Step 2 DONE");
3254
+ console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
3141
3255
  await Promise.all([
3142
3256
  identityManager.initialize(this.apiClient, config.debug),
3143
3257
  sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
3144
3258
  ]);
3259
+ const distinctId = identityManager.getDistinctId();
3260
+ console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
3145
3261
  if (config.autoStartSession !== false) {
3146
3262
  sessionManager.startSession().catch(() => {
3147
3263
  if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
@@ -3150,6 +3266,7 @@ var PaywalloClientClass = class {
3150
3266
  new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
3151
3267
  });
3152
3268
  this.config = config;
3269
+ console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
3153
3270
  }
3154
3271
  async identify(options) {
3155
3272
  this.ensureInitialized();
@@ -3159,7 +3276,7 @@ var PaywalloClientClass = class {
3159
3276
  const apiClient = this.getApiClientOrThrow();
3160
3277
  const distinctId = identityManager.getDistinctId();
3161
3278
  if (!distinctId) {
3162
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3279
+ console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
3163
3280
  return;
3164
3281
  }
3165
3282
  if (!isValidEventName(eventName)) {
@@ -3167,6 +3284,25 @@ var PaywalloClientClass = class {
3167
3284
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
3168
3285
  }
3169
3286
  const sessionId = sessionManager.getSessionId();
3287
+ if (eventName === "$purchase_completed") {
3288
+ console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
3289
+ distinctId: distinctId.substring(0, 8) + "...",
3290
+ productId: options?.properties?.productId,
3291
+ priceUsd: options?.properties?.priceUsd,
3292
+ price: options?.properties?.price,
3293
+ currency: options?.properties?.currency,
3294
+ placement: options?.properties?.placement
3295
+ }));
3296
+ } else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
3297
+ console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
3298
+ distinctId: distinctId.substring(0, 8) + "...",
3299
+ placement: options?.properties?.placement,
3300
+ variantKey: options?.properties?.variantKey,
3301
+ timeOnPaywall: options?.properties?.timeOnPaywall
3302
+ }));
3303
+ } else {
3304
+ console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
3305
+ }
3170
3306
  await apiClient.trackEvent(eventName, distinctId, {
3171
3307
  ...identityManager.getProperties(),
3172
3308
  ...options?.properties,
@@ -3217,15 +3353,32 @@ var PaywalloClientClass = class {
3217
3353
  });
3218
3354
  }
3219
3355
  async getVariant(flagKey) {
3220
- const distinctId = identityManager.getDistinctId();
3221
- if (!distinctId) return { variant: null };
3222
- return this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3356
+ try {
3357
+ const distinctId = identityManager.getDistinctId();
3358
+ if (!distinctId) {
3359
+ console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
3360
+ return { variant: null };
3361
+ }
3362
+ const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3363
+ console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
3364
+ return result;
3365
+ } catch (error) {
3366
+ if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
3367
+ if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
3368
+ return { variant: null };
3369
+ }
3223
3370
  }
3224
3371
  async getConditionalFlag(flagKey, context) {
3225
- const distinctId = identityManager.getDistinctId();
3226
- if (!distinctId) return false;
3227
- const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3228
- return result.value;
3372
+ try {
3373
+ const distinctId = identityManager.getDistinctId();
3374
+ if (!distinctId) return false;
3375
+ const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3376
+ return result.value;
3377
+ } catch (error) {
3378
+ if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
3379
+ if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
3380
+ return false;
3381
+ }
3229
3382
  }
3230
3383
  async getPaywall(placement) {
3231
3384
  return this.getApiClientOrThrow().getPaywall(placement);
@@ -3285,9 +3438,12 @@ var PaywalloClientClass = class {
3285
3438
  offlineQueue.dispose();
3286
3439
  networkMonitor.dispose();
3287
3440
  campaignGateService.clearPreloaded();
3441
+ this.cleanupNetworkRecovery();
3288
3442
  this.apiClient = null;
3289
3443
  this.config = null;
3290
3444
  this.initPromise = null;
3445
+ this.pendingConfig = null;
3446
+ this.initAttempts = 0;
3291
3447
  this.paywallPresenter = null;
3292
3448
  this.campaignPresenter = null;
3293
3449
  this.subscriptionGetter = null;
@@ -3403,6 +3559,9 @@ var PaywalloClientClass = class {
3403
3559
  if (!this.config || !this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized. Call Paywallo.init() first");
3404
3560
  }
3405
3561
  };
3562
+ _PaywalloClientClass.MAX_INIT_RETRIES = 3;
3563
+ _PaywalloClientClass.RETRY_DELAYS = [2e3, 5e3, 1e4];
3564
+ var PaywalloClientClass = _PaywalloClientClass;
3406
3565
  var PaywalloClient = new PaywalloClientClass();
3407
3566
 
3408
3567
  // src/context/PaywalloContext.ts