@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.d.mts CHANGED
@@ -246,7 +246,6 @@ interface RestoreResult {
246
246
  type Environment = "Production" | "Sandbox";
247
247
  interface ApiClientConfig {
248
248
  appKey: string;
249
- baseUrl?: string;
250
249
  debug?: boolean;
251
250
  environment?: Environment;
252
251
  offlineQueueEnabled?: boolean;
@@ -254,7 +253,6 @@ interface ApiClientConfig {
254
253
  }
255
254
  interface PaywalloConfig {
256
255
  appKey: string;
257
- baseUrl?: string;
258
256
  debug?: boolean;
259
257
  environment?: Environment;
260
258
  }
@@ -459,7 +457,7 @@ declare class ApiClient {
459
457
  private offlineQueueEnabled;
460
458
  private httpClient;
461
459
  private readonly cache;
462
- constructor(appKey: string, baseUrl?: string, debug?: boolean, environment?: Environment, offlineQueueEnabled?: boolean, timeout?: number);
460
+ constructor(appKey: string, debug?: boolean, environment?: Environment, offlineQueueEnabled?: boolean, timeout?: number);
463
461
  /** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
464
462
  getHttpClient(): HttpClient;
465
463
  getWebUrl(): string;
package/dist/index.d.ts CHANGED
@@ -246,7 +246,6 @@ interface RestoreResult {
246
246
  type Environment = "Production" | "Sandbox";
247
247
  interface ApiClientConfig {
248
248
  appKey: string;
249
- baseUrl?: string;
250
249
  debug?: boolean;
251
250
  environment?: Environment;
252
251
  offlineQueueEnabled?: boolean;
@@ -254,7 +253,6 @@ interface ApiClientConfig {
254
253
  }
255
254
  interface PaywalloConfig {
256
255
  appKey: string;
257
- baseUrl?: string;
258
256
  debug?: boolean;
259
257
  environment?: Environment;
260
258
  }
@@ -459,7 +457,7 @@ declare class ApiClient {
459
457
  private offlineQueueEnabled;
460
458
  private httpClient;
461
459
  private readonly cache;
462
- constructor(appKey: string, baseUrl?: string, debug?: boolean, environment?: Environment, offlineQueueEnabled?: boolean, timeout?: number);
460
+ constructor(appKey: string, debug?: boolean, environment?: Environment, offlineQueueEnabled?: boolean, timeout?: number);
463
461
  /** Exposed only for QueueProcessor initialization. Prefer get()/post() for requests. */
464
462
  getHttpClient(): HttpClient;
465
463
  getWebUrl(): string;
package/dist/index.js CHANGED
@@ -2336,6 +2336,8 @@ var ApiCache = class {
2336
2336
  constructor() {
2337
2337
  this.CACHE_TTL = 3e5;
2338
2338
  // 5 minutes
2339
+ this.CACHE_NULL_TTL = 3e4;
2340
+ // 30 seconds — for null/404 variants
2339
2341
  this.MAX_SIZE = 200;
2340
2342
  this.variantCache = /* @__PURE__ */ new Map();
2341
2343
  this.paywallCache = /* @__PURE__ */ new Map();
@@ -2344,27 +2346,34 @@ var ApiCache = class {
2344
2346
  getCached(cache, key) {
2345
2347
  const entry = cache.get(key);
2346
2348
  if (!entry) return void 0;
2347
- if (Date.now() - entry.ts < this.CACHE_TTL) return entry.value;
2349
+ const ttl = entry.ttl ?? this.CACHE_TTL;
2350
+ if (Date.now() - entry.ts < ttl) return entry.value;
2348
2351
  cache.delete(key);
2349
2352
  return void 0;
2350
2353
  }
2351
- setCached(cache, key, value) {
2352
- cache.set(key, { value, ts: Date.now() });
2354
+ setCached(cache, key, value, ttlOverride) {
2355
+ const entry = { value, ts: Date.now() };
2356
+ if (ttlOverride !== void 0) entry.ttl = ttlOverride;
2357
+ cache.set(key, entry);
2353
2358
  }
2354
2359
  evictExpiredEntries(cache) {
2355
2360
  if (cache.size <= this.MAX_SIZE) return;
2356
2361
  const now = Date.now();
2357
2362
  for (const [key, entry] of cache) {
2358
- if (now - entry.ts > this.CACHE_TTL) cache.delete(key);
2363
+ const ttl = entry.ttl ?? this.CACHE_TTL;
2364
+ if (now - entry.ts > ttl) cache.delete(key);
2359
2365
  }
2360
2366
  }
2361
2367
  getVariant(key) {
2362
2368
  return this.getCached(this.variantCache, key);
2363
2369
  }
2364
- setVariant(key, value) {
2365
- this.setCached(this.variantCache, key, value);
2370
+ setVariant(key, value, ttlOverride) {
2371
+ this.setCached(this.variantCache, key, value, ttlOverride);
2366
2372
  this.evictExpiredEntries(this.variantCache);
2367
2373
  }
2374
+ get nullTTL() {
2375
+ return this.CACHE_NULL_TTL;
2376
+ }
2368
2377
  getStaleVariant(key) {
2369
2378
  const entry = this.variantCache.get(key);
2370
2379
  if (entry && Date.now() - entry.ts < 864e5) return entry.value;
@@ -2373,8 +2382,8 @@ var ApiCache = class {
2373
2382
  getPaywall(key) {
2374
2383
  return this.getCached(this.paywallCache, key);
2375
2384
  }
2376
- setPaywall(key, value) {
2377
- this.setCached(this.paywallCache, key, value);
2385
+ setPaywall(key, value, ttlOverride) {
2386
+ this.setCached(this.paywallCache, key, value, ttlOverride);
2378
2387
  this.evictExpiredEntries(this.paywallCache);
2379
2388
  }
2380
2389
  getStalePaywall(key) {
@@ -2400,10 +2409,10 @@ var ApiCache = class {
2400
2409
  var SDK_VERSION = "1.0.0";
2401
2410
  var DEFAULT_BASE_URL = "https://panel.lucasqueiroga.shop";
2402
2411
  var ApiClient = class {
2403
- constructor(appKey, baseUrl, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2412
+ constructor(appKey, debug = false, environment = "Production", offlineQueueEnabled = true, timeout = 3e4) {
2404
2413
  this.cache = new ApiCache();
2405
2414
  this.appKey = appKey;
2406
- this.baseUrl = baseUrl ?? DEFAULT_BASE_URL;
2415
+ this.baseUrl = DEFAULT_BASE_URL;
2407
2416
  this.webUrl = deriveWebUrl(this.baseUrl);
2408
2417
  this.debug = debug;
2409
2418
  this.environment = environment;
@@ -2490,14 +2499,18 @@ var ApiClient = class {
2490
2499
  const hit = this.cache.getVariant(key);
2491
2500
  if (hit !== void 0) return hit;
2492
2501
  try {
2493
- const url = new URL(`${this.baseUrl}/api/v1/flags/${flagKey}`);
2494
- url.searchParams.set("distinctId", distinctId);
2495
- const res = await this.get(url.toString());
2502
+ const res = await this.get(`/api/v1/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
2496
2503
  if (res.status === 404) {
2497
2504
  const v = { variant: null };
2498
- this.cache.setVariant(key, v);
2505
+ this.cache.setVariant(key, v, this.cache.nullTTL);
2499
2506
  return v;
2500
2507
  }
2508
+ if (!res.ok) {
2509
+ this.log("Non-OK response for variant:", flagKey, res.status);
2510
+ const stale = this.cache.getStaleVariant(key);
2511
+ if (stale !== void 0) return stale;
2512
+ return { variant: null };
2513
+ }
2501
2514
  this.cache.setVariant(key, res.data);
2502
2515
  return res.data;
2503
2516
  } catch (error) {
@@ -2513,7 +2526,13 @@ var ApiClient = class {
2513
2526
  try {
2514
2527
  const res = await this.get(`/api/v1/paywalls/${placement}`);
2515
2528
  if (res.status === 404) {
2516
- this.cache.setPaywall(placement, null);
2529
+ this.cache.setPaywall(placement, null, this.cache.nullTTL);
2530
+ return null;
2531
+ }
2532
+ if (!res.ok) {
2533
+ this.log("Non-OK response for paywall:", placement, res.status);
2534
+ const stale = this.cache.getStalePaywall(placement);
2535
+ if (stale !== void 0) return stale;
2517
2536
  return null;
2518
2537
  }
2519
2538
  this.cache.setPaywall(placement, res.data);
@@ -2579,6 +2598,10 @@ var ApiClient = class {
2579
2598
  const url = buildConditionalFlagUrl(this.baseUrl, flagKey, context);
2580
2599
  const response = await this.get(url);
2581
2600
  if (response.status === 404) return { value: false, flagKey };
2601
+ if (!response.ok) {
2602
+ this.log("Non-OK response for conditional flag:", flagKey, response.status);
2603
+ return { value: false, flagKey };
2604
+ }
2582
2605
  this.log("Got conditional flag:", flagKey, response.data.value);
2583
2606
  return { value: response.data.value, flagKey };
2584
2607
  } catch {
@@ -3099,6 +3122,13 @@ var InstallTracker = class {
3099
3122
  const storage = new SecureStorage(this.debug);
3100
3123
  const alreadyTracked = await storage.get("install_tracked");
3101
3124
  if (alreadyTracked) return;
3125
+ let fallbackTracked = null;
3126
+ try {
3127
+ const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3128
+ fallbackTracked = await AsyncStorage3.getItem("@paywallo:install_tracked");
3129
+ } catch {
3130
+ }
3131
+ if (fallbackTracked) return;
3102
3132
  const distinctId = identityManager.getDistinctId();
3103
3133
  const sessionId = sessionManager.getSessionId();
3104
3134
  const installedAt = Date.now();
@@ -3160,6 +3190,11 @@ var InstallTracker = class {
3160
3190
  attStatus: adIds.attStatus
3161
3191
  });
3162
3192
  const stored = await storage.set("install_tracked", String(installedAt));
3193
+ try {
3194
+ const AsyncStorage3 = (await import("@react-native-async-storage/async-storage")).default;
3195
+ await AsyncStorage3.setItem("@paywallo:install_tracked", String(installedAt));
3196
+ } catch {
3197
+ }
3163
3198
  if (this.debug && !stored) {
3164
3199
  console.warn("[Paywallo] Install event tracked but flag storage failed \u2014 may re-track on next launch");
3165
3200
  }
@@ -3176,7 +3211,7 @@ function isValidEventName(name) {
3176
3211
  if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3177
3212
  return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
3178
3213
  }
3179
- var PaywalloClientClass = class {
3214
+ var _PaywalloClientClass = class _PaywalloClientClass {
3180
3215
  constructor() {
3181
3216
  this.config = null;
3182
3217
  this.apiClient = null;
@@ -3187,45 +3222,121 @@ var PaywalloClientClass = class {
3187
3222
  this.activeChecker = null;
3188
3223
  this.restoreHandler = null;
3189
3224
  this.lastOnboardingStepTimestamp = null;
3225
+ this.pendingConfig = null;
3226
+ this.networkCleanup = null;
3227
+ this.initAttempts = 0;
3190
3228
  }
3191
3229
  async init(config) {
3192
- if (this.config && this.apiClient) return;
3230
+ console.warn("[Paywallo INIT]", "init() called \u2014 appKey:", config.appKey ? config.appKey.substring(0, 8) + "..." : "MISSING");
3231
+ if (this.config && this.apiClient) {
3232
+ console.warn("[Paywallo INIT]", "Already initialized \u2014 skipping");
3233
+ return;
3234
+ }
3193
3235
  if (this.initPromise) {
3236
+ console.warn("[Paywallo INIT]", "Init already in progress \u2014 awaiting...");
3194
3237
  await this.initPromise;
3238
+ console.warn("[Paywallo INIT]", "Awaited existing init \u2014 done");
3195
3239
  return;
3196
3240
  }
3197
3241
  if (!config.appKey) throw new ClientError(CLIENT_ERROR_CODES.MISSING_APP_KEY, "appKey is required");
3198
- this.initPromise = this.doInit(config);
3242
+ console.warn("[Paywallo INIT]", "Starting fresh init...");
3243
+ this.pendingConfig = config;
3244
+ this.initAttempts = 0;
3245
+ this.initPromise = this.initWithRetry(config);
3199
3246
  try {
3200
3247
  await this.initPromise;
3248
+ console.warn("[Paywallo INIT]", "init() SUCCESS");
3201
3249
  } catch (error) {
3250
+ console.warn("[Paywallo INIT]", "init() FAILED:", error instanceof Error ? error.message : String(error));
3202
3251
  this.initPromise = null;
3203
3252
  this.config = null;
3204
3253
  this.apiClient = null;
3254
+ this.reportInitFailure(config, error, "ALL_RETRIES_EXHAUSTED");
3255
+ this.setupNetworkRecovery(config);
3256
+ throw error;
3257
+ }
3258
+ }
3259
+ async initWithRetry(config) {
3260
+ while (this.initAttempts <= _PaywalloClientClass.MAX_INIT_RETRIES) {
3205
3261
  try {
3206
- const tempClient = new ApiClient(config.appKey, config.baseUrl, config.debug);
3207
- void tempClient.reportError("INIT_FAILED", error instanceof Error ? error.message : String(error));
3208
- } catch {
3262
+ await this.doInit(config);
3263
+ if (this.initAttempts > 0) {
3264
+ this.reportInitSuccess(config, this.initAttempts);
3265
+ }
3266
+ this.cleanupNetworkRecovery();
3267
+ this.pendingConfig = null;
3268
+ return;
3269
+ } catch (error) {
3270
+ this.initAttempts++;
3271
+ if (this.initAttempts > _PaywalloClientClass.MAX_INIT_RETRIES) throw error;
3272
+ const delay = _PaywalloClientClass.RETRY_DELAYS[this.initAttempts - 1] ?? 1e4;
3273
+ if (config.debug) console.warn("[Paywallo]", `Init attempt ${this.initAttempts} failed, retrying in ${delay}ms`, error);
3274
+ await new Promise((resolve) => setTimeout(resolve, delay));
3209
3275
  }
3210
- throw error;
3276
+ }
3277
+ }
3278
+ setupNetworkRecovery(config) {
3279
+ if (this.networkCleanup) return;
3280
+ if (!networkMonitor.isInitialized()) return;
3281
+ this.networkCleanup = networkMonitor.addListener((state) => {
3282
+ if (state !== "online") return;
3283
+ if (this.config && this.apiClient) return;
3284
+ if (config.debug) console.warn("[Paywallo]", "Network recovered \u2014 retrying init");
3285
+ this.initPromise = this.initWithRetry(config);
3286
+ this.initPromise.then(() => {
3287
+ if (config.debug) console.warn("[Paywallo]", "Init succeeded after network recovery");
3288
+ }).catch(() => {
3289
+ this.reportInitFailure(config, null, "NETWORK_RECOVERY_FAILED");
3290
+ });
3291
+ });
3292
+ }
3293
+ cleanupNetworkRecovery() {
3294
+ if (this.networkCleanup) {
3295
+ this.networkCleanup();
3296
+ this.networkCleanup = null;
3297
+ }
3298
+ }
3299
+ reportInitFailure(config, error, reason) {
3300
+ try {
3301
+ const tempClient = new ApiClient(config.appKey, config.debug);
3302
+ void tempClient.reportError("INIT_FAILED", reason, {
3303
+ attempts: this.initAttempts,
3304
+ error: error instanceof Error ? error.message : String(error)
3305
+ });
3306
+ } catch {
3307
+ }
3308
+ }
3309
+ reportInitSuccess(config, attempts) {
3310
+ try {
3311
+ if (this.apiClient) {
3312
+ void this.apiClient.reportError("INIT_RECOVERED", `Succeeded after ${attempts} retries`, { attempts });
3313
+ }
3314
+ } catch {
3211
3315
  }
3212
3316
  }
3213
3317
  async doInit(config) {
3214
3318
  const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
3215
3319
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
3320
+ console.warn("[Paywallo INIT]", "Step 1: networkMonitor + offlineQueue...");
3216
3321
  await Promise.all([
3217
3322
  networkMonitor.initialize({ debug: config.debug }),
3218
3323
  offlineQueue.initialize({ debug: config.debug }).then(
3219
3324
  () => offlineQueue.clearItemsWithInvalidAppKey(config.appKey)
3220
3325
  )
3221
3326
  ]);
3222
- this.apiClient = new ApiClient(config.appKey, config.baseUrl, config.debug, environment, offlineQueueEnabled, config.timeout);
3327
+ console.warn("[Paywallo INIT]", "Step 1 DONE");
3328
+ console.warn("[Paywallo INIT]", "Step 2: ApiClient + queueProcessor + affiliate...");
3329
+ this.apiClient = new ApiClient(config.appKey, config.debug, environment, offlineQueueEnabled, config.timeout);
3223
3330
  queueProcessor.initialize(this.apiClient.getHttpClient(), { debug: config.debug });
3224
3331
  affiliateManager.initialize(this.apiClient, config.debug);
3332
+ console.warn("[Paywallo INIT]", "Step 2 DONE");
3333
+ console.warn("[Paywallo INIT]", "Step 3: identityManager + sessionManager...");
3225
3334
  await Promise.all([
3226
3335
  identityManager.initialize(this.apiClient, config.debug),
3227
3336
  sessionManager.initialize(this.apiClient, config.sessionConfig, config.debug)
3228
3337
  ]);
3338
+ const distinctId = identityManager.getDistinctId();
3339
+ console.warn("[Paywallo INIT]", "Step 3 DONE \u2014 distinctId:", distinctId ? distinctId.substring(0, 8) + "..." : "EMPTY");
3229
3340
  if (config.autoStartSession !== false) {
3230
3341
  sessionManager.startSession().catch(() => {
3231
3342
  if (config.debug) console.warn("[Paywallo]", "Session start failed during init \u2014 continuing");
@@ -3234,6 +3345,7 @@ var PaywalloClientClass = class {
3234
3345
  new InstallTracker(config.debug).trackIfNeeded(this.apiClient, config.requestATT === true).catch(() => {
3235
3346
  });
3236
3347
  this.config = config;
3348
+ console.warn("[Paywallo INIT]", "COMPLETE \u2014 SDK ready, environment:", environment);
3237
3349
  }
3238
3350
  async identify(options) {
3239
3351
  this.ensureInitialized();
@@ -3243,7 +3355,7 @@ var PaywalloClientClass = class {
3243
3355
  const apiClient = this.getApiClientOrThrow();
3244
3356
  const distinctId = identityManager.getDistinctId();
3245
3357
  if (!distinctId) {
3246
- if (this.config?.debug) console.warn("[Paywallo] Skipping event \u2014 no distinctId available");
3358
+ console.warn("[Paywallo TRACK]", "SKIPPED \u2014 no distinctId", eventName);
3247
3359
  return;
3248
3360
  }
3249
3361
  if (!isValidEventName(eventName)) {
@@ -3251,6 +3363,25 @@ var PaywalloClientClass = class {
3251
3363
  throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
3252
3364
  }
3253
3365
  const sessionId = sessionManager.getSessionId();
3366
+ if (eventName === "$purchase_completed") {
3367
+ console.warn("[Paywallo PURCHASE]", "tracking purchase:", JSON.stringify({
3368
+ distinctId: distinctId.substring(0, 8) + "...",
3369
+ productId: options?.properties?.productId,
3370
+ priceUsd: options?.properties?.priceUsd,
3371
+ price: options?.properties?.price,
3372
+ currency: options?.properties?.currency,
3373
+ placement: options?.properties?.placement
3374
+ }));
3375
+ } else if (eventName === "$paywall_viewed" || eventName === "$paywall_closed") {
3376
+ console.warn("[Paywallo PAYWALL]", eventName, JSON.stringify({
3377
+ distinctId: distinctId.substring(0, 8) + "...",
3378
+ placement: options?.properties?.placement,
3379
+ variantKey: options?.properties?.variantKey,
3380
+ timeOnPaywall: options?.properties?.timeOnPaywall
3381
+ }));
3382
+ } else {
3383
+ console.warn("[Paywallo TRACK]", eventName, "distinctId:", distinctId.substring(0, 8) + "...");
3384
+ }
3254
3385
  await apiClient.trackEvent(eventName, distinctId, {
3255
3386
  ...identityManager.getProperties(),
3256
3387
  ...options?.properties,
@@ -3301,15 +3432,32 @@ var PaywalloClientClass = class {
3301
3432
  });
3302
3433
  }
3303
3434
  async getVariant(flagKey) {
3304
- const distinctId = identityManager.getDistinctId();
3305
- if (!distinctId) return { variant: null };
3306
- return this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3435
+ try {
3436
+ const distinctId = identityManager.getDistinctId();
3437
+ if (!distinctId) {
3438
+ console.warn("[Paywallo AB]", "getVariant SKIPPED \u2014 no distinctId", flagKey);
3439
+ return { variant: null };
3440
+ }
3441
+ const result = await this.getApiClientOrThrow().getVariant(flagKey, distinctId);
3442
+ console.warn("[Paywallo AB]", `getVariant("${flagKey}") =`, result.variant);
3443
+ return result;
3444
+ } catch (error) {
3445
+ if (this.config?.debug) console.warn("[Paywallo]", "getVariant failed \u2014 returning null", error);
3446
+ if (this.apiClient) void this.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
3447
+ return { variant: null };
3448
+ }
3307
3449
  }
3308
3450
  async getConditionalFlag(flagKey, context) {
3309
- const distinctId = identityManager.getDistinctId();
3310
- if (!distinctId) return false;
3311
- const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3312
- return result.value;
3451
+ try {
3452
+ const distinctId = identityManager.getDistinctId();
3453
+ if (!distinctId) return false;
3454
+ const result = await this.getApiClientOrThrow().getConditionalFlag(flagKey, { distinctId, ...context });
3455
+ return result.value;
3456
+ } catch (error) {
3457
+ if (this.config?.debug) console.warn("[Paywallo]", "getConditionalFlag failed \u2014 returning false", error);
3458
+ if (this.apiClient) void this.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
3459
+ return false;
3460
+ }
3313
3461
  }
3314
3462
  async getPaywall(placement) {
3315
3463
  return this.getApiClientOrThrow().getPaywall(placement);
@@ -3369,9 +3517,12 @@ var PaywalloClientClass = class {
3369
3517
  offlineQueue.dispose();
3370
3518
  networkMonitor.dispose();
3371
3519
  campaignGateService.clearPreloaded();
3520
+ this.cleanupNetworkRecovery();
3372
3521
  this.apiClient = null;
3373
3522
  this.config = null;
3374
3523
  this.initPromise = null;
3524
+ this.pendingConfig = null;
3525
+ this.initAttempts = 0;
3375
3526
  this.paywallPresenter = null;
3376
3527
  this.campaignPresenter = null;
3377
3528
  this.subscriptionGetter = null;
@@ -3487,6 +3638,9 @@ var PaywalloClientClass = class {
3487
3638
  if (!this.config || !this.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized. Call Paywallo.init() first");
3488
3639
  }
3489
3640
  };
3641
+ _PaywalloClientClass.MAX_INIT_RETRIES = 3;
3642
+ _PaywalloClientClass.RETRY_DELAYS = [2e3, 5e3, 1e4];
3643
+ var PaywalloClientClass = _PaywalloClientClass;
3490
3644
  var PaywalloClient = new PaywalloClientClass();
3491
3645
 
3492
3646
  // src/context/PaywalloContext.ts