@virex-tech/paywallo-sdk 1.0.1 → 1.1.0

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