expo-iap 3.0.4 → 3.0.5

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.
Files changed (44) hide show
  1. package/.eslintignore +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/.prettierignore +1 -0
  4. package/CHANGELOG.md +6 -0
  5. package/CLAUDE.md +1 -1
  6. package/CONTRIBUTING.md +10 -0
  7. package/android/build.gradle +1 -1
  8. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +12 -12
  9. package/android/src/main/java/expo/modules/iap/PromiseUtils.kt +2 -2
  10. package/build/index.d.ts +28 -14
  11. package/build/index.d.ts.map +1 -1
  12. package/build/index.js +36 -15
  13. package/build/index.js.map +1 -1
  14. package/build/modules/android.d.ts +3 -4
  15. package/build/modules/android.d.ts.map +1 -1
  16. package/build/modules/android.js +2 -4
  17. package/build/modules/android.js.map +1 -1
  18. package/build/modules/ios.d.ts +2 -3
  19. package/build/modules/ios.d.ts.map +1 -1
  20. package/build/modules/ios.js +1 -3
  21. package/build/modules/ios.js.map +1 -1
  22. package/build/purchase-error.d.ts +8 -10
  23. package/build/purchase-error.d.ts.map +1 -1
  24. package/build/purchase-error.js +4 -2
  25. package/build/purchase-error.js.map +1 -1
  26. package/build/types.d.ts +159 -204
  27. package/build/types.d.ts.map +1 -1
  28. package/build/types.js +1 -59
  29. package/build/types.js.map +1 -1
  30. package/build/useIAP.d.ts +5 -8
  31. package/build/useIAP.d.ts.map +1 -1
  32. package/build/useIAP.js.map +1 -1
  33. package/ios/ExpoIap.podspec +1 -1
  34. package/ios/ExpoIapModule.swift +103 -89
  35. package/package.json +2 -1
  36. package/plugin/build/withIAP.js +4 -5
  37. package/plugin/src/withIAP.ts +4 -5
  38. package/scripts/update-types.mjs +61 -0
  39. package/src/index.ts +77 -29
  40. package/src/modules/android.ts +5 -7
  41. package/src/modules/ios.ts +3 -5
  42. package/src/purchase-error.ts +13 -16
  43. package/src/types.ts +183 -216
  44. package/src/useIAP.ts +10 -10
@@ -1,26 +1,26 @@
1
1
  import ExpoModulesCore
2
- import StoreKit
3
- import OpenIAP
4
2
  import OSLog
3
+ import OpenIAP
4
+ import StoreKit
5
5
 
6
6
  private let iapLogger = Logger(subsystem: "dev.hyo.expo-iap", category: "ExpoIapModule")
7
7
  private func logDebug(_ message: String) {
8
8
  // Use OSLog/Logger so logs are structured and filterable
9
9
  // Suppress debug logs in Release builds
10
10
  #if DEBUG
11
- iapLogger.debug("\(message, privacy: .public)")
11
+ iapLogger.debug("\(message, privacy: .public)")
12
12
  #endif
13
13
  }
14
14
 
15
15
  // MARK: - Swift helpers for optional dictionary compaction
16
- private extension Sequence where Element == [String: Any?] {
17
- func compactingValues() -> [[String: Any]] {
16
+ extension Sequence where Element == [String: Any?] {
17
+ fileprivate func compactingValues() -> [[String: Any]] {
18
18
  return self.map { $0.compactMapValues { $0 } }
19
19
  }
20
20
  }
21
21
 
22
- private extension Dictionary where Key == String, Value == Any? {
23
- func compactingValues() -> [String: Any] {
22
+ extension Dictionary where Key == String, Value == Any? {
23
+ fileprivate func compactingValues() -> [String: Any] {
24
24
  return self.compactMapValues { $0 }
25
25
  }
26
26
  }
@@ -47,36 +47,36 @@ public class ExpoIapModule: Module {
47
47
  if let s = sub { OpenIapModule.shared.removeListener(s) }
48
48
  sub = nil
49
49
  }
50
-
50
+
51
51
  nonisolated public func definition() -> ModuleDefinition {
52
52
  Name("ExpoIap")
53
-
53
+
54
54
  Constants {
55
55
  OpenIapSerialization.errorCodes()
56
56
  }
57
-
57
+
58
58
  Events(
59
59
  OpenIapEvent.PurchaseUpdated,
60
60
  OpenIapEvent.PurchaseError,
61
61
  OpenIapEvent.PromotedProductIOS
62
62
  )
63
-
63
+
64
64
  OnCreate {
65
65
  logDebug("Module created")
66
66
  Task { @MainActor in
67
67
  self.setupStore()
68
68
  }
69
69
  }
70
-
70
+
71
71
  OnDestroy {
72
72
  logDebug("Module destroyed")
73
73
  Task { @MainActor in
74
74
  await self.cleanupStore()
75
75
  }
76
76
  }
77
-
77
+
78
78
  // MARK: - Connection Management
79
-
79
+
80
80
  AsyncFunction("initConnection") { () async throws -> Bool in
81
81
  logDebug("initConnection called")
82
82
  let isConnected = try await OpenIapModule.shared.initConnection()
@@ -85,26 +85,26 @@ public class ExpoIapModule: Module {
85
85
  logDebug("Connection initialized: \(isConnected)")
86
86
  return isConnected
87
87
  }
88
-
88
+
89
89
  AsyncFunction("endConnection") { () async throws -> Bool in
90
90
  logDebug("endConnection called")
91
91
  let _ = try await OpenIapModule.shared.endConnection()
92
-
92
+
93
93
  logDebug("Connection ended")
94
94
  await MainActor.run { self.isInitialized = false }
95
95
  return true
96
96
  }
97
-
97
+
98
98
  // MARK: - Product Management
99
-
99
+
100
100
  AsyncFunction("fetchProducts") { (params: [String: Any]) async throws -> [[String: Any]] in
101
101
  try await ensureConnection()
102
102
  logDebug("fetchProducts raw params: \(params)")
103
-
103
+
104
104
  // Handle both object format {skus: [...], type: "..."} and array format
105
105
  var skus: [String] = []
106
106
  var typeString = "all"
107
-
107
+
108
108
  if let skusArray = params["skus"] as? [String] {
109
109
  // Object format: {skus: [...], type: "..."}
110
110
  skus = skusArray
@@ -119,16 +119,16 @@ public class ExpoIapModule: Module {
119
119
  }
120
120
  skus = tempSkus
121
121
  }
122
-
122
+
123
123
  logDebug("fetchProducts parsed - skus: \(skus), type: \(typeString)")
124
124
  logDebug("SKUs count: \(skus.count)")
125
-
125
+
126
126
  // Validate SKUs
127
127
  guard !skus.isEmpty else {
128
128
  logDebug("ERROR: Empty SKUs array!")
129
129
  throw OpenIapError.emptySkuList()
130
130
  }
131
-
131
+
132
132
  // Convert string to OpenIapRequestProductType enum
133
133
  let productType: OpenIapRequestProductType = {
134
134
  switch typeString {
@@ -140,9 +140,9 @@ public class ExpoIapModule: Module {
140
140
  return .all
141
141
  }
142
142
  }()
143
-
143
+
144
144
  logDebug("Converted type to OpenIapRequestProductType: \(productType)")
145
-
145
+
146
146
  // Build OpenIapProductRequest and fetch via OpenIapModule
147
147
  let request = OpenIapProductRequest(skus: skus, type: productType)
148
148
  let products = try await OpenIapModule.shared.fetchProducts(request)
@@ -160,18 +160,20 @@ public class ExpoIapModule: Module {
160
160
  // Ensure non-optional values for Expo bridge
161
161
  return OpenIapSerialization.products(products).compactingValues()
162
162
  }
163
-
163
+
164
164
  // MARK: - Purchase Operations
165
-
165
+
166
166
  AsyncFunction("requestPurchase") { (params: [String: Any]) async throws in
167
167
  // Extract and validate required fields
168
168
  guard let sku = params["sku"] as? String, !sku.isEmpty else {
169
- throw OpenIapError.make(code: OpenIapError.E_PURCHASE_ERROR, message: "Missing required 'sku'")
169
+ throw OpenIapError.make(
170
+ code: OpenIapError.PurchaseError, message: "Missing required 'sku'")
170
171
  }
171
172
  try await ensureConnection()
172
173
 
173
174
  // Optional fields
174
- let andFinish = (params["andDangerouslyFinishTransactionAutomatically"] as? Bool) ?? false
175
+ let andFinish =
176
+ (params["andDangerouslyFinishTransactionAutomatically"] as? Bool) ?? false
175
177
  let appAccountToken = params["appAccountToken"] as? String
176
178
  let quantity: Int? = {
177
179
  if let q = params["quantity"] as? Int { return q }
@@ -188,7 +190,9 @@ public class ExpoIapModule: Module {
188
190
  let nonce = (offer["nonce"] as? String) ?? ""
189
191
  let signature = (offer["signature"] as? String) ?? ""
190
192
  let timestamp = (offer["timestamp"] as? String) ?? ""
191
- if !identifier.isEmpty && !keyIdentifier.isEmpty && !nonce.isEmpty && !signature.isEmpty && !timestamp.isEmpty {
193
+ if !identifier.isEmpty && !keyIdentifier.isEmpty && !nonce.isEmpty
194
+ && !signature.isEmpty && !timestamp.isEmpty
195
+ {
192
196
  discountOffer = OpenIapDiscountOffer(
193
197
  identifier: identifier,
194
198
  keyIdentifier: keyIdentifier,
@@ -201,9 +205,10 @@ public class ExpoIapModule: Module {
201
205
 
202
206
  let tokenForLog = appAccountToken ?? "nil"
203
207
  let qtyForLog = quantity ?? -1
204
- logDebug("requestPurchase parsed - sku: \(sku), andFinish: \(andFinish), appAccountToken: \(tokenForLog), quantity: \(qtyForLog), hasOffer: \(discountOffer != nil)")
205
-
206
-
208
+ logDebug(
209
+ "requestPurchase parsed - sku: \(sku), andFinish: \(andFinish), appAccountToken: \(tokenForLog), quantity: \(qtyForLog), hasOffer: \(discountOffer != nil)"
210
+ )
211
+
207
212
  // Build purchase request props using OpenIapRequestPurchaseProps
208
213
  let requestProps = OpenIapRequestPurchaseProps(
209
214
  sku: sku,
@@ -212,7 +217,7 @@ public class ExpoIapModule: Module {
212
217
  quantity: quantity,
213
218
  withOffer: discountOffer
214
219
  )
215
-
220
+
216
221
  do {
217
222
  _ = try await OpenIapModule.shared.requestPurchase(requestProps)
218
223
  logDebug("Purchase request completed successfully")
@@ -221,23 +226,26 @@ public class ExpoIapModule: Module {
221
226
  if let openIapError = error as? OpenIapError {
222
227
  throw openIapError
223
228
  }
224
- throw OpenIapError.make(code: OpenIapError.E_PURCHASE_ERROR, message: error.localizedDescription)
229
+ throw OpenIapError.make(
230
+ code: OpenIapError.PurchaseError, message: error.localizedDescription)
225
231
  }
226
232
  }
227
-
233
+
228
234
  AsyncFunction("finishTransaction") { (transactionId: String) async throws -> Bool in
229
235
  try await ensureConnection()
230
236
  logDebug("finishTransaction called with id: \(transactionId)")
231
- let result = try await OpenIapModule.shared.finishTransaction(transactionIdentifier: transactionId)
237
+ let result = try await OpenIapModule.shared.finishTransaction(
238
+ transactionIdentifier: transactionId)
232
239
  return result
233
240
  }
234
-
241
+
235
242
  // MARK: - Purchase History
236
-
237
- AsyncFunction("getAvailablePurchases") { (options: [String: Any?]?) async throws -> [[String: Any]] in
243
+
244
+ AsyncFunction("getAvailablePurchases") {
245
+ (options: [String: Any?]?) async throws -> [[String: Any]] in
238
246
  try await ensureConnection()
239
247
  logDebug("getAvailablePurchases called")
240
-
248
+
241
249
  // Build options and get purchases directly from OpenIapModule
242
250
  let purchaseOptions: OpenIapGetAvailablePurchasesProps? = options.map {
243
251
  OpenIapGetAvailablePurchasesProps(
@@ -248,12 +256,14 @@ public class ExpoIapModule: Module {
248
256
  let purchases = try await OpenIapModule.shared.getAvailablePurchases(purchaseOptions)
249
257
  return OpenIapSerialization.purchases(purchases).compactingValues()
250
258
  }
251
-
259
+
252
260
  // Legacy function for backward compatibility
253
- AsyncFunction("getAvailableItems") { (alsoPublishToEventListener: Bool, onlyIncludeActiveItems: Bool) async throws -> [[String: Any]] in
261
+ AsyncFunction("getAvailableItems") {
262
+ (alsoPublishToEventListener: Bool, onlyIncludeActiveItems: Bool) async throws
263
+ -> [[String: Any]] in
254
264
  try await ensureConnection()
255
265
  logDebug("getAvailableItems called (legacy)")
256
-
266
+
257
267
  let purchaseOptions = OpenIapGetAvailablePurchasesProps(
258
268
  alsoPublishToEventListenerIOS: alsoPublishToEventListener,
259
269
  onlyIncludeActiveItemsIOS: onlyIncludeActiveItems
@@ -261,44 +271,44 @@ public class ExpoIapModule: Module {
261
271
  let purchases = try await OpenIapModule.shared.getAvailablePurchases(purchaseOptions)
262
272
  return OpenIapSerialization.purchases(purchases).compactingValues()
263
273
  }
264
-
274
+
265
275
  AsyncFunction("getPendingTransactionsIOS") { () async throws -> [[String: Any]] in
266
276
  try await ensureConnection()
267
277
  logDebug("getPendingTransactionsIOS called")
268
-
278
+
269
279
  let pendingTransactions = try await OpenIapModule.shared.getPendingTransactionsIOS()
270
280
  return OpenIapSerialization.purchases(pendingTransactions).compactingValues()
271
281
  }
272
-
282
+
273
283
  AsyncFunction("clearTransactionIOS") { () async throws -> Bool in
274
284
  try await ensureConnection()
275
285
  logDebug("clearTransactionIOS called")
276
286
  try await OpenIapModule.shared.clearTransactionIOS()
277
287
  return true
278
288
  }
279
-
289
+
280
290
  // MARK: - Receipt & Validation
281
-
291
+
282
292
  AsyncFunction("getReceiptIOS") { () async throws -> String in
283
293
  try await ensureConnection()
284
294
  logDebug("getReceiptIOS called")
285
295
  return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
286
296
  }
287
-
297
+
288
298
  // Backward-compatible alias expected by JS layer/tests
289
299
  AsyncFunction("getReceiptDataIOS") { () async throws -> String in
290
300
  try await ensureConnection()
291
301
  logDebug("getReceiptDataIOS called (alias of getReceiptIOS)")
292
302
  return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
293
303
  }
294
-
304
+
295
305
  AsyncFunction("requestReceiptRefreshIOS") { () async throws -> String in
296
306
  try await ensureConnection()
297
307
  logDebug("requestReceiptRefreshIOS called")
298
308
  // Receipt refresh is handled automatically by StoreKit 2
299
309
  return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
300
310
  }
301
-
311
+
302
312
  AsyncFunction("validateReceiptIOS") { (sku: String) async throws -> [String: Any] in
303
313
  try await ensureConnection()
304
314
  logDebug("validateReceiptIOS called for sku: \(sku)")
@@ -312,53 +322,55 @@ public class ExpoIapModule: Module {
312
322
  "jwsRepresentation": result.jwsRepresentation,
313
323
  // Populate unified purchaseToken for iOS as alias of JWS
314
324
  "purchaseToken": result.jwsRepresentation,
315
- "latestTransaction": result.latestTransaction.map { OpenIapSerialization.purchase($0).compactingValues() },
325
+ "latestTransaction": result.latestTransaction.map {
326
+ OpenIapSerialization.purchase($0).compactingValues()
327
+ },
316
328
  ]
317
329
  return dict.compactingValues()
318
330
  } catch {
319
- throw OpenIapError.make(code: OpenIapError.E_RECEIPT_FAILED)
331
+ throw OpenIapError.make(code: OpenIapError.ReceiptFailed)
320
332
  }
321
333
  }
322
-
334
+
323
335
  // MARK: - iOS Specific Features
324
-
336
+
325
337
  AsyncFunction("presentCodeRedemptionSheetIOS") { () async throws -> Bool in
326
338
  try await ensureConnection()
327
339
  logDebug("presentCodeRedemptionSheetIOS called")
328
340
  let _ = try await OpenIapModule.shared.presentCodeRedemptionSheetIOS()
329
341
  return true
330
342
  }
331
-
343
+
332
344
  AsyncFunction("showManageSubscriptionsIOS") { () async throws -> [[String: Any]] in
333
345
  try await ensureConnection()
334
346
  logDebug("showManageSubscriptionsIOS called")
335
- // OpenIAP 1.1.9 returns already-serialized dictionaries here.
347
+ // OpenIAP returns already-serialized dictionaries here.
336
348
  let purchases = try await OpenIapModule.shared.showManageSubscriptionsIOS()
337
349
  return purchases.compactingValues()
338
350
  }
339
-
351
+
340
352
  AsyncFunction("deepLinkToSubscriptionsIOS") { () async throws in
341
353
  logDebug("deepLinkToSubscriptionsIOS called")
342
354
  // Open App Store subscriptions page directly
343
355
  if let url = URL(string: "https://apps.apple.com/account/subscriptions") {
344
356
  #if canImport(UIKit)
345
- await MainActor.run {
346
- UIApplication.shared.open(url, options: [:], completionHandler: nil)
347
- }
357
+ await MainActor.run {
358
+ UIApplication.shared.open(url, options: [:], completionHandler: nil)
359
+ }
348
360
  #endif
349
361
  }
350
362
  }
351
-
363
+
352
364
  AsyncFunction("beginRefundRequestIOS") { (sku: String) async throws -> String? in
353
365
  try await ensureConnection()
354
366
  logDebug("beginRefundRequestIOS called for sku: \(sku)")
355
367
  return try await OpenIapModule.shared.beginRefundRequestIOS(sku: sku)
356
368
  }
357
-
369
+
358
370
  AsyncFunction("getPromotedProductIOS") { () async throws -> [String: Any]? in
359
371
  try await ensureConnection()
360
372
  logDebug("getPromotedProductIOS called")
361
-
373
+
362
374
  if let promoted = try await OpenIapModule.shared.getPromotedProductIOS() {
363
375
  // Fetch full product info by SKU to conform to OpenIapProduct
364
376
  let request = OpenIapProductRequest(skus: [promoted.productIdentifier], type: .all)
@@ -373,37 +385,37 @@ public class ExpoIapModule: Module {
373
385
  logDebug("getStorefrontIOS called")
374
386
  return try await OpenIapModule.shared.getStorefrontIOS()
375
387
  }
376
-
388
+
377
389
  AsyncFunction("syncIOS") { () async throws -> Bool in
378
390
  try await ensureConnection()
379
391
  logDebug("syncIOS called")
380
392
  return try await OpenIapModule.shared.syncIOS()
381
393
  }
382
-
394
+
383
395
  // MARK: - Additional iOS Methods
384
-
396
+
385
397
  AsyncFunction("isTransactionVerifiedIOS") { (sku: String) async throws -> Bool in
386
398
  try await ensureConnection()
387
399
  logDebug("isTransactionVerifiedIOS called for sku: \(sku)")
388
400
  return await OpenIapModule.shared.isTransactionVerifiedIOS(sku: sku)
389
401
  }
390
-
402
+
391
403
  AsyncFunction("getTransactionJwsIOS") { (sku: String) async throws -> String? in
392
404
  try await ensureConnection()
393
405
  logDebug("getTransactionJwsIOS called for sku: \(sku)")
394
406
  return try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku)
395
407
  }
396
-
408
+
397
409
  AsyncFunction("isEligibleForIntroOfferIOS") { (groupID: String) async throws -> Bool in
398
410
  try await ensureConnection()
399
411
  logDebug("isEligibleForIntroOfferIOS called for groupID: \(groupID)")
400
412
  return await OpenIapModule.shared.isEligibleForIntroOfferIOS(groupID: groupID)
401
413
  }
402
-
414
+
403
415
  AsyncFunction("subscriptionStatusIOS") { (sku: String) async throws -> [[String: Any]]? in
404
416
  try await ensureConnection()
405
417
  logDebug("subscriptionStatusIOS called for sku: \(sku)")
406
-
418
+
407
419
  if let statuses = try await OpenIapModule.shared.subscriptionStatusIOS(sku: sku) {
408
420
  // Align output with SubscriptionStatusIOS in TS:
409
421
  // { state: SubscriptionState; renewalInfo?: { jsonRepresentation?: string; willAutoRenew: boolean; autoRenewPreference?: string } }
@@ -416,7 +428,7 @@ public class ExpoIapModule: Module {
416
428
  // autoRenewStatus is a Bool from OpenIAP types
417
429
  let renewalInfo: [String: Any?] = [
418
430
  "willAutoRenew": info.autoRenewStatus,
419
- "autoRenewPreference": info.autoRenewPreference
431
+ "autoRenewPreference": info.autoRenewPreference,
420
432
  ]
421
433
  dict["renewalInfo"] = renewalInfo
422
434
  }
@@ -426,20 +438,21 @@ public class ExpoIapModule: Module {
426
438
  }
427
439
  return nil
428
440
  }
429
-
441
+
430
442
  AsyncFunction("currentEntitlementIOS") { (sku: String) async throws -> [String: Any]? in
431
443
  try await ensureConnection()
432
444
  logDebug("currentEntitlementIOS called for sku: \(sku)")
433
445
  do {
434
- if let entitlement = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku) {
446
+ if let entitlement = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku)
447
+ {
435
448
  return OpenIapSerialization.purchase(entitlement).compactingValues()
436
449
  }
437
450
  return nil
438
451
  } catch {
439
- throw OpenIapError.make(code: OpenIapError.E_SKU_NOT_FOUND, productId: sku)
452
+ throw OpenIapError.make(code: OpenIapError.SkuNotFound, productId: sku)
440
453
  }
441
454
  }
442
-
455
+
443
456
  AsyncFunction("latestTransactionIOS") { (sku: String) async throws -> [String: Any]? in
444
457
  try await ensureConnection()
445
458
  logDebug("latestTransactionIOS called for sku: \(sku)")
@@ -449,17 +462,17 @@ public class ExpoIapModule: Module {
449
462
  }
450
463
  return nil
451
464
  } catch {
452
- throw OpenIapError.make(code: OpenIapError.E_SKU_NOT_FOUND, productId: sku)
465
+ throw OpenIapError.make(code: OpenIapError.SkuNotFound, productId: sku)
453
466
  }
454
467
  }
455
468
  }
456
-
469
+
457
470
  // MARK: - Listeners Setup
458
-
471
+
459
472
  @MainActor
460
473
  private func setupStore() {
461
474
  logDebug("Setting up OpenIapModule event listeners")
462
-
475
+
463
476
  purchaseUpdatedSub = OpenIapModule.shared.purchaseUpdatedListener { [weak self] purchase in
464
477
  Task { @MainActor in
465
478
  guard let self else { return }
@@ -468,7 +481,7 @@ public class ExpoIapModule: Module {
468
481
  self.sendEvent(OpenIapEvent.PurchaseUpdated, purchaseData)
469
482
  }
470
483
  }
471
-
484
+
472
485
  purchaseErrorSub = OpenIapModule.shared.purchaseErrorListener { [weak self] event in
473
486
  Task { @MainActor in
474
487
  guard let self else { return }
@@ -476,13 +489,14 @@ public class ExpoIapModule: Module {
476
489
  let errorData: [String: Any?] = [
477
490
  "code": event.code,
478
491
  "message": event.message,
479
- "productId": event.productId
492
+ "productId": event.productId,
480
493
  ]
481
494
  self.sendEvent(OpenIapEvent.PurchaseError, errorData)
482
495
  }
483
496
  }
484
-
485
- promotedProductSub = OpenIapModule.shared.promotedProductListenerIOS { [weak self] productId in
497
+
498
+ promotedProductSub = OpenIapModule.shared.promotedProductListenerIOS {
499
+ [weak self] productId in
486
500
  Task { @MainActor in
487
501
  guard let self else { return }
488
502
  logDebug("📱 Promoted product callback - sending event for: \(productId)")
@@ -490,7 +504,7 @@ public class ExpoIapModule: Module {
490
504
  }
491
505
  }
492
506
  }
493
-
507
+
494
508
  @MainActor
495
509
  private func cleanupStore() async {
496
510
  logDebug("Cleaning up listeners and ending connection")
@@ -499,13 +513,13 @@ public class ExpoIapModule: Module {
499
513
  removeListener(&promotedProductSub)
500
514
  _ = try? await OpenIapModule.shared.endConnection()
501
515
  }
502
-
516
+
503
517
  // MARK: - Private Helper Methods
504
-
518
+
505
519
  private func ensureConnection() throws {
506
520
  guard isInitialized else {
507
521
  throw OpenIapError.make(
508
- code: OpenIapError.E_INIT_CONNECTION,
522
+ code: OpenIapError.InitConnection,
509
523
  message: "Connection not initialized. Call initConnection() first."
510
524
  )
511
525
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-iap",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "In App Purchase module in Expo",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -27,6 +27,7 @@
27
27
  "docs:build": "cd docs && bun run build",
28
28
  "docs:serve": "cd docs && bun run serve",
29
29
  "docs:install": "cd docs && bun install",
30
+ "generate:types": "node scripts/update-types.mjs --tag 1.0.2",
30
31
  "generate:icon": "npx sharp-cli resize 32 32 -i docs/static/img/icon.png -o docs/static/img/favicon-32x32.png && npx sharp-cli resize 16 16 -i docs/static/img/icon.png -o docs/static/img/favicon-16x16.png && npx sharp-cli resize 180 180 -i docs/static/img/icon.png -o docs/static/img/apple-touch-icon.png && npx sharp-cli resize 192 192 -i docs/static/img/icon.png -o docs/static/img/android-chrome-192x192.png && npx sharp-cli resize 512 512 -i docs/static/img/icon.png -o docs/static/img/android-chrome-512x512.png && npx sharp-cli resize 150 150 -i docs/static/img/icon.png -o docs/static/img/mstile-150x150.png && npx sharp-cli resize 1200 630 -i docs/static/img/icon.png -o docs/static/img/og-image.png && npx sharp-cli resize 1200 600 -i docs/static/img/icon.png -o docs/static/img/twitter-card.png && npx sharp-cli resize 16 16 -i docs/static/img/icon.png -o docs/static/img/favicon.png && cp docs/static/img/favicon-16x16.png docs/static/img/favicon.ico"
31
32
  },
32
33
  "keywords": [
@@ -69,8 +69,7 @@ const modifyAppBuildGradle = (gradle, language) => {
69
69
  const impl = (ga, v) => language === 'kotlin'
70
70
  ? ` implementation("${ga}:${v}")`
71
71
  : ` implementation "${ga}:${v}"`;
72
- // Pin OpenIAP Google library to 1.1.0
73
- const openiapDep = impl('io.github.hyochan.openiap:openiap-google', '1.1.0');
72
+ const openiapDep = impl('io.github.hyochan.openiap:openiap-google', '1.1.10');
74
73
  // Remove any existing openiap-google lines (any version, groovy/kotlin, implementation/api)
75
74
  const openiapAnyLine = /^\s*(?:implementation|api)\s*\(?\s*["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*\)?\s*$/gm;
76
75
  const hadExisting = openiapAnyLine.test(modified);
@@ -78,12 +77,12 @@ const modifyAppBuildGradle = (gradle, language) => {
78
77
  modified = modified.replace(openiapAnyLine, '').replace(/\n{3,}/g, '\n\n');
79
78
  }
80
79
  // Ensure the desired dependency line is present
81
- if (!new RegExp(String.raw `io\.github\.hyochan\.openiap:openiap-google:1\.1\.0`).test(modified)) {
80
+ if (!new RegExp(String.raw `io\.github\.hyochan\.openiap:openiap-google:1\.1\.10`).test(modified)) {
82
81
  // Insert just after the opening `dependencies {` line
83
82
  modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep, 1);
84
83
  logOnce(hadExisting
85
- ? '🛠️ expo-iap: Replaced OpenIAP dependency with 1.1.0'
86
- : '🛠️ expo-iap: Added OpenIAP dependency (1.1.0) to build.gradle');
84
+ ? '🛠️ expo-iap: Replaced OpenIAP dependency with 1.1.10'
85
+ : '🛠️ expo-iap: Added OpenIAP dependency (1.1.10) to build.gradle');
87
86
  }
88
87
  return modified;
89
88
  };
@@ -54,8 +54,7 @@ const modifyAppBuildGradle = (
54
54
  language === 'kotlin'
55
55
  ? ` implementation("${ga}:${v}")`
56
56
  : ` implementation "${ga}:${v}"`;
57
- // Pin OpenIAP Google library to 1.1.0
58
- const openiapDep = impl('io.github.hyochan.openiap:openiap-google', '1.1.0');
57
+ const openiapDep = impl('io.github.hyochan.openiap:openiap-google', '1.1.10');
59
58
 
60
59
  // Remove any existing openiap-google lines (any version, groovy/kotlin, implementation/api)
61
60
  const openiapAnyLine =
@@ -68,15 +67,15 @@ const modifyAppBuildGradle = (
68
67
  // Ensure the desired dependency line is present
69
68
  if (
70
69
  !new RegExp(
71
- String.raw`io\.github\.hyochan\.openiap:openiap-google:1\.1\.0`,
70
+ String.raw`io\.github\.hyochan\.openiap:openiap-google:1\.1\.10`,
72
71
  ).test(modified)
73
72
  ) {
74
73
  // Insert just after the opening `dependencies {` line
75
74
  modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep, 1);
76
75
  logOnce(
77
76
  hadExisting
78
- ? '🛠️ expo-iap: Replaced OpenIAP dependency with 1.1.0'
79
- : '🛠️ expo-iap: Added OpenIAP dependency (1.1.0) to build.gradle',
77
+ ? '🛠️ expo-iap: Replaced OpenIAP dependency with 1.1.10'
78
+ : '🛠️ expo-iap: Added OpenIAP dependency (1.1.10) to build.gradle',
80
79
  );
81
80
  }
82
81
 
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ import {mkdtempSync, readFileSync, writeFileSync, rmSync} from 'node:fs';
3
+ import {join} from 'node:path';
4
+ import {tmpdir} from 'node:os';
5
+ import {execFileSync} from 'node:child_process';
6
+
7
+ const DEFAULT_TAG = '1.0.1';
8
+ const PROJECT_ROOT = process.cwd();
9
+
10
+ function parseArgs() {
11
+ const args = process.argv.slice(2);
12
+ let tag = DEFAULT_TAG;
13
+
14
+ for (let i = 0; i < args.length; i++) {
15
+ const arg = args[i];
16
+ if (arg === '--tag' && typeof args[i + 1] === 'string') {
17
+ tag = args[i + 1];
18
+ i++;
19
+ }
20
+ }
21
+
22
+ return {tag};
23
+ }
24
+
25
+ function getReleaseUrl(tag) {
26
+ return `https://github.com/hyodotdev/openiap-gql/releases/download/${tag}/openiap-typescript.zip`;
27
+ }
28
+
29
+ function main() {
30
+ const {tag} = parseArgs();
31
+ const releaseUrl = getReleaseUrl(tag);
32
+ const tempDir = mkdtempSync(join(tmpdir(), 'openiap-types-'));
33
+ const zipPath = join(tempDir, 'openiap-typescript.zip');
34
+
35
+ try {
36
+ console.log(`Downloading OpenIAP types (tag: ${tag}) from ${releaseUrl}`);
37
+ execFileSync('curl', ['-L', '-o', zipPath, releaseUrl], {
38
+ stdio: 'inherit',
39
+ });
40
+
41
+ console.log('Extracting types.ts from archive');
42
+ execFileSync('unzip', ['-o', zipPath, 'types.ts', '-d', tempDir], {
43
+ stdio: 'inherit',
44
+ });
45
+
46
+ const extractedPath = join(tempDir, 'types.ts');
47
+ let contents = readFileSync(extractedPath, 'utf8');
48
+ contents = contents.replace(
49
+ /Run `[^`]+` after updating any \*\.graphql schema file\./,
50
+ 'Run `bun run generate:types` after updating any *.graphql schema file.',
51
+ );
52
+
53
+ const destination = join(PROJECT_ROOT, 'src', 'types.ts');
54
+ writeFileSync(destination, contents);
55
+ console.log('Updated src/types.ts');
56
+ } finally {
57
+ rmSync(tempDir, {recursive: true, force: true});
58
+ }
59
+ }
60
+
61
+ main();