rampkit-expo-dev 0.0.67 → 0.0.68

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.
@@ -9,22 +9,26 @@ public class RampKitModule: Module {
9
9
  private let installDateKey = "rk_install_date"
10
10
  private let launchCountKey = "rk_launch_count"
11
11
  private let lastLaunchKey = "rk_last_launch"
12
+ private let trackedTransactionsKey = "rk_tracked_transactions"
12
13
 
13
14
  // Transaction observer task
14
15
  private var transactionObserverTask: Task<Void, Never>?
15
16
  private var appId: String?
16
17
  private var userId: String?
17
-
18
- // Queue for transactions received before SDK is configured
19
- private var pendingTransactions: [Any] = []
20
18
  private var isConfigured = false
21
19
 
20
+ // Set of already-tracked originalTransactionIds to prevent duplicates
21
+ private var trackedTransactionIds: Set<String> = []
22
+
22
23
  public func definition() -> ModuleDefinition {
23
24
  Name("RampKit")
24
25
 
25
- // Start transaction observer immediately when module loads
26
+ // OnCreate runs when JS first requires the module - may be too late for some transactions
26
27
  OnCreate {
27
- self.startTransactionObserverEarly()
28
+ print("[RampKit] ⚡ OnCreate called - module being initialized")
29
+ self.loadTrackedTransactions()
30
+ self.userId = self.getOrCreateUserId()
31
+ print("[RampKit] ⚡ OnCreate complete, userId: \(self.userId ?? "nil")")
28
32
  }
29
33
 
30
34
  // ============================================================================
@@ -249,8 +253,23 @@ public class RampKitModule: Module {
249
253
  SecItemAdd(addQuery as CFDictionary, nil)
250
254
  }
251
255
 
256
+ // MARK: - Tracked Transactions (Deduplication)
257
+
258
+ private func loadTrackedTransactions() {
259
+ if let stored = UserDefaults.standard.array(forKey: trackedTransactionsKey) as? [String] {
260
+ trackedTransactionIds = Set(stored)
261
+ print("[RampKit] 📚 Loaded \(trackedTransactionIds.count) tracked transaction IDs")
262
+ }
263
+ }
264
+
265
+ private func saveTrackedTransactions() {
266
+ let array = Array(trackedTransactionIds)
267
+ UserDefaults.standard.set(array, forKey: trackedTransactionsKey)
268
+ print("[RampKit] 💾 Saved \(trackedTransactionIds.count) tracked transaction IDs")
269
+ }
270
+
252
271
  // MARK: - Launch Tracking
253
-
272
+
254
273
  private func getLaunchTrackingData() -> [String: Any?] {
255
274
  let defaults = UserDefaults.standard
256
275
  let now = ISO8601DateFormatter().string(from: Date())
@@ -412,149 +431,125 @@ public class RampKitModule: Module {
412
431
 
413
432
  // MARK: - StoreKit 2 Transaction Observer
414
433
 
415
- /// Called on module init - starts listening BEFORE JavaScript configures us
416
- private func startTransactionObserverEarly() {
417
- // Get userId early so we have it ready
418
- self.userId = getOrCreateUserId()
419
-
420
- guard #available(iOS 15.0, *) else {
421
- print("[RampKit] ⚠️ Transaction observer requires iOS 15+")
422
- return
423
- }
424
-
425
- // Don't restart if already running
426
- guard transactionObserverTask == nil else {
427
- print("[RampKit] Transaction observer already running")
428
- return
429
- }
430
-
431
- print("[RampKit] 🚀 Starting transaction observer early (module init)...")
432
-
433
- transactionObserverTask = Task {
434
- print("[RampKit] 👂 Transaction.updates loop starting...")
435
- for await result in Transaction.updates {
436
- print("[RampKit] 🎉 RECEIVED TRANSACTION UPDATE!")
437
- await self.handleTransactionUpdate(result)
438
- }
439
- print("[RampKit] ⚠️ Transaction.updates loop exited (unexpected)")
440
- }
441
-
442
- // Also check unfinished transactions
443
- Task {
444
- await self.handleUnfinishedTransactions()
445
- }
446
- }
447
-
448
- /// Called from JavaScript - sets appId and processes any pending transactions
434
+ /// Called from JavaScript - sets appId and IMMEDIATELY checks for purchases we may have missed
449
435
  private func startTransactionObserver(appId: String) {
450
436
  self.appId = appId
451
437
  self.userId = getOrCreateUserId()
452
438
  self.isConfigured = true
453
439
 
454
440
  print("[RampKit] ✅ Transaction observer configured with appId: \(appId)")
441
+ print("[RampKit] 📊 Already tracked \(trackedTransactionIds.count) transactions")
455
442
 
456
- // Process any transactions that arrived before we were configured
457
443
  if #available(iOS 15.0, *) {
444
+ // CRITICAL: Check current entitlements for any purchases we missed
445
+ // This is the KEY mechanism for catching Superwall/RevenueCat purchases
458
446
  Task {
459
- await self.processPendingTransactions()
447
+ await self.checkAndTrackCurrentEntitlements()
460
448
  }
461
449
 
462
- // Debug: check current entitlements
450
+ // Also start listening for future transactions
463
451
  Task {
464
- await self.debugCurrentEntitlements()
452
+ await self.startTransactionUpdatesListener()
465
453
  }
466
454
  }
467
-
468
- // Ensure observer is running (in case OnCreate didn't fire)
469
- if transactionObserverTask == nil {
470
- startTransactionObserverEarly()
471
- }
472
455
  }
473
456
 
457
+ /// Check all current entitlements and track any we haven't seen before
458
+ /// This catches purchases made by Superwall/RevenueCat before our observer started
474
459
  @available(iOS 15.0, *)
475
- private func handleTransactionUpdate(_ result: VerificationResult<Transaction>) async {
476
- do {
477
- let transaction = try self.checkVerified(result)
478
- print("[RampKit] ✅ Transaction verified: \(transaction.productID)")
460
+ private func checkAndTrackCurrentEntitlements() async {
461
+ print("[RampKit] 🔍 Checking current entitlements for missed purchases...")
479
462
 
480
- if self.isConfigured, let _ = self.appId {
481
- // We're configured, process immediately
482
- await self.handleTransaction(transaction)
483
- } else {
484
- // Not configured yet, queue it
485
- print("[RampKit] 📦 Queueing transaction (SDK not configured yet): \(transaction.productID)")
486
- self.pendingTransactions.append(transaction)
463
+ var foundCount = 0
464
+ var trackedCount = 0
465
+ var newCount = 0
466
+
467
+ for await result in Transaction.currentEntitlements {
468
+ foundCount += 1
469
+
470
+ guard case .verified(let transaction) = result else {
471
+ print("[RampKit] ⚠️ Unverified entitlement skipped")
472
+ continue
487
473
  }
488
474
 
489
- // Always finish the transaction
490
- await transaction.finish()
491
- print("[RampKit] 🏁 Transaction finished: \(transaction.productID)")
492
- } catch {
493
- print("[RampKit] ❌ Transaction verification failed: \(error)")
475
+ let originalId = String(transaction.originalID)
476
+ print("[RampKit] 📦 Found entitlement: \(transaction.productID), originalID: \(originalId)")
477
+
478
+ // Check if we've already tracked this transaction
479
+ if trackedTransactionIds.contains(originalId) {
480
+ trackedCount += 1
481
+ print("[RampKit] ✓ Already tracked: \(transaction.productID)")
482
+ continue
483
+ }
484
+
485
+ // NEW transaction we haven't seen!
486
+ newCount += 1
487
+ print("[RampKit] 🆕 NEW purchase detected: \(transaction.productID)")
488
+
489
+ // Track it
490
+ await self.handleTransaction(transaction)
491
+
492
+ // Mark as tracked
493
+ trackedTransactionIds.insert(originalId)
494
+ saveTrackedTransactions()
494
495
  }
496
+
497
+ print("[RampKit] 🔍 Entitlement check complete:")
498
+ print("[RampKit] - Total found: \(foundCount)")
499
+ print("[RampKit] - Already tracked: \(trackedCount)")
500
+ print("[RampKit] - NEW (sent events): \(newCount)")
495
501
  }
496
502
 
503
+ /// Start listening for Transaction.updates (for future purchases)
497
504
  @available(iOS 15.0, *)
498
- private func processPendingTransactions() async {
499
- guard !pendingTransactions.isEmpty else { return }
505
+ private func startTransactionUpdatesListener() async {
506
+ guard transactionObserverTask == nil else {
507
+ print("[RampKit] Transaction updates listener already running")
508
+ return
509
+ }
510
+
511
+ print("[RampKit] 👂 Starting Transaction.updates listener...")
512
+
513
+ transactionObserverTask = Task {
514
+ for await result in Transaction.updates {
515
+ print("[RampKit] 🎉 Transaction.updates received!")
516
+
517
+ guard case .verified(let transaction) = result else {
518
+ print("[RampKit] ⚠️ Unverified transaction update")
519
+ continue
520
+ }
500
521
 
501
- print("[RampKit] 📤 Processing \(pendingTransactions.count) pending transaction(s)...")
522
+ let originalId = String(transaction.originalID)
502
523
 
503
- for item in pendingTransactions {
504
- if let transaction = item as? Transaction {
524
+ // Skip if already tracked
525
+ if self.trackedTransactionIds.contains(originalId) {
526
+ print("[RampKit] ✓ Transaction.updates: Already tracked \(transaction.productID)")
527
+ await transaction.finish()
528
+ continue
529
+ }
530
+
531
+ print("[RampKit] 🆕 Transaction.updates: NEW purchase \(transaction.productID)")
532
+
533
+ // Track it
505
534
  await self.handleTransaction(transaction)
506
- }
507
- }
508
535
 
509
- pendingTransactions.removeAll()
510
- print("[RampKit] ✅ All pending transactions processed")
511
- }
536
+ // Mark as tracked
537
+ self.trackedTransactionIds.insert(originalId)
538
+ self.saveTrackedTransactions()
512
539
 
513
- @available(iOS 15.0, *)
514
- private func debugCurrentEntitlements() async {
515
- print("[RampKit] 🔍 Checking current entitlements...")
516
- var count = 0
517
- for await result in Transaction.currentEntitlements {
518
- if case .verified(let transaction) = result {
519
- count += 1
520
- print("[RampKit] 📦 Current entitlement: \(transaction.productID), originalID: \(transaction.originalID), id: \(transaction.id)")
540
+ // Finish the transaction
541
+ await transaction.finish()
521
542
  }
522
543
  }
523
- print("[RampKit] 🔍 Found \(count) current entitlements")
524
544
  }
525
545
 
526
546
  private func stopTransactionObserver() {
527
547
  transactionObserverTask?.cancel()
528
548
  transactionObserverTask = nil
529
549
  isConfigured = false
530
- pendingTransactions.removeAll()
531
550
  print("[RampKit] Transaction observer stopped")
532
551
  }
533
552
 
534
- @available(iOS 15.0, *)
535
- private func handleUnfinishedTransactions() async {
536
- print("[RampKit] 🔍 Checking for unfinished transactions...")
537
- var count = 0
538
- for await result in Transaction.unfinished {
539
- count += 1
540
- print("[RampKit] 📦 Found unfinished transaction #\(count)")
541
- await self.handleTransactionUpdate(result)
542
- }
543
- print("[RampKit] 🔍 Finished checking unfinished transactions. Found: \(count)")
544
- }
545
-
546
- @available(iOS 15.0, *)
547
- private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
548
- switch result {
549
- case .unverified(_, let error):
550
- throw error
551
- case .verified(let safe):
552
- return safe
553
- @unknown default:
554
- fatalError("Unknown VerificationResult case")
555
- }
556
- }
557
-
558
553
  @available(iOS 15.0, *)
559
554
  private func handleTransaction(_ transaction: Transaction) async {
560
555
  print("[RampKit] 🔄 handleTransaction called for: \(transaction.productID)")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rampkit-expo-dev",
3
- "version": "0.0.67",
3
+ "version": "0.0.68",
4
4
  "description": "The Expo SDK for RampKit. Build, test, and personalize app onboardings with instant updates.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",