expo-iap 3.0.8 → 3.1.1-rc.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.
Files changed (99) hide show
  1. package/CLAUDE.md +2 -2
  2. package/CONTRIBUTING.md +19 -0
  3. package/README.md +18 -6
  4. package/android/build.gradle +24 -1
  5. package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
  6. package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
  7. package/build/ExpoIapModule.d.ts +1 -0
  8. package/build/ExpoIapModule.d.ts.map +1 -1
  9. package/build/ExpoIapModule.js +29 -4
  10. package/build/ExpoIapModule.js.map +1 -1
  11. package/build/index.d.ts +20 -47
  12. package/build/index.d.ts.map +1 -1
  13. package/build/index.js +94 -137
  14. package/build/index.js.map +1 -1
  15. package/build/modules/android.d.ts.map +1 -1
  16. package/build/modules/android.js +2 -1
  17. package/build/modules/android.js.map +1 -1
  18. package/build/modules/ios.d.ts +16 -1
  19. package/build/modules/ios.d.ts.map +1 -1
  20. package/build/modules/ios.js +29 -16
  21. package/build/modules/ios.js.map +1 -1
  22. package/build/types.d.ts +8 -6
  23. package/build/types.d.ts.map +1 -1
  24. package/build/types.js.map +1 -1
  25. package/build/useIAP.d.ts +1 -1
  26. package/build/useIAP.d.ts.map +1 -1
  27. package/build/useIAP.js +12 -15
  28. package/build/useIAP.js.map +1 -1
  29. package/build/utils/constants.d.ts +2 -0
  30. package/build/utils/constants.d.ts.map +1 -1
  31. package/build/utils/constants.js +9 -2
  32. package/build/utils/constants.js.map +1 -1
  33. package/build/utils/errorMapping.d.ts +32 -23
  34. package/build/utils/errorMapping.d.ts.map +1 -1
  35. package/build/utils/errorMapping.js +117 -22
  36. package/build/utils/errorMapping.js.map +1 -1
  37. package/coverage/clover.xml +497 -0
  38. package/coverage/coverage-final.json +6 -0
  39. package/coverage/lcov-report/base.css +224 -0
  40. package/coverage/lcov-report/block-navigation.js +87 -0
  41. package/coverage/lcov-report/favicon.png +0 -0
  42. package/coverage/lcov-report/index.html +161 -0
  43. package/coverage/lcov-report/prettify.css +1 -0
  44. package/coverage/lcov-report/prettify.js +2 -0
  45. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  46. package/coverage/lcov-report/sorter.js +196 -0
  47. package/coverage/lcov-report/src/ExpoIap.types.ts.html +1243 -0
  48. package/coverage/lcov-report/src/PurchaseError.ts.html +787 -0
  49. package/coverage/lcov-report/src/helpers/index.html +116 -0
  50. package/coverage/lcov-report/src/helpers/subscription.ts.html +496 -0
  51. package/coverage/lcov-report/src/index.html +116 -0
  52. package/coverage/lcov-report/src/index.ts.html +1993 -0
  53. package/coverage/lcov-report/src/modules/android.ts.html +550 -0
  54. package/coverage/lcov-report/src/modules/index.html +131 -0
  55. package/coverage/lcov-report/src/modules/ios.ts.html +1222 -0
  56. package/coverage/lcov-report/src/purchase-error.ts.html +880 -0
  57. package/coverage/lcov-report/src/types/ExpoIapAndroid.types.ts.html +493 -0
  58. package/coverage/lcov-report/src/types/index.html +116 -0
  59. package/coverage/lcov-report/src/useIap.ts.html +1483 -0
  60. package/coverage/lcov-report/src/utils/errorMapping.ts.html +1069 -0
  61. package/coverage/lcov-report/src/utils/index.html +116 -0
  62. package/coverage/lcov-report/src/utils/purchase.ts.html +241 -0
  63. package/coverage/lcov.info +929 -0
  64. package/expo-module.config.json +10 -3
  65. package/ios/ExpoIap.podspec +3 -2
  66. package/ios/ExpoIapHelper.swift +96 -0
  67. package/ios/ExpoIapLog.swift +127 -0
  68. package/ios/ExpoIapModule.swift +218 -340
  69. package/ios/OneSideModule.swift +489 -0
  70. package/ios/expoiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  71. package/ios/expoiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  72. package/openiap-versions.json +5 -0
  73. package/package.json +4 -3
  74. package/plugin/build/withIAP.d.ts +22 -9
  75. package/plugin/build/withIAP.js +163 -13
  76. package/plugin/jest.config.js +13 -3
  77. package/plugin/src/expoConfig.augmentation.d.ts +38 -0
  78. package/plugin/src/withIAP.ts +272 -22
  79. package/plugin/tsconfig.json +2 -1
  80. package/plugin/tsconfig.tsbuildinfo +1 -1
  81. package/scripts/update-types.mjs +20 -1
  82. package/src/ExpoIapModule.ts +45 -4
  83. package/src/index.ts +122 -165
  84. package/src/modules/android.ts +2 -1
  85. package/src/modules/ios.ts +31 -19
  86. package/src/types.ts +8 -6
  87. package/src/useIAP.ts +17 -25
  88. package/src/utils/constants.ts +11 -2
  89. package/src/utils/errorMapping.ts +203 -23
  90. package/build/purchase-error.d.ts +0 -67
  91. package/build/purchase-error.d.ts.map +0 -1
  92. package/build/purchase-error.js +0 -166
  93. package/build/purchase-error.js.map +0 -1
  94. package/build/utils/purchase.d.ts +0 -9
  95. package/build/utils/purchase.d.ts.map +0 -1
  96. package/build/utils/purchase.js +0 -34
  97. package/build/utils/purchase.js.map +0 -1
  98. package/src/purchase-error.ts +0 -265
  99. package/src/utils/purchase.ts +0 -52
@@ -1,9 +1,16 @@
1
1
  {
2
- "platforms": ["ios", "android"],
2
+ "platforms": [
3
+ "ios",
4
+ "android"
5
+ ],
3
6
  "ios": {
4
- "modules": ["ExpoIapModule"]
7
+ "modules": [
8
+ "OneSideModule"
9
+ ]
5
10
  },
6
11
  "android": {
7
- "modules": ["expo.modules.iap.ExpoIapModule"]
12
+ "modules": [
13
+ "expo.modules.iap.ExpoIapModule"
14
+ ]
8
15
  }
9
16
  }
@@ -1,6 +1,7 @@
1
1
  require 'json'
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+ versions = JSON.parse(File.read(File.join(__dir__, '..', 'openiap-versions.json')))
4
5
 
5
6
  Pod::Spec.new do |s|
6
7
  s.name = 'ExpoIap'
@@ -16,12 +17,12 @@ Pod::Spec.new do |s|
16
17
  # Even though StoreKit 2 requires iOS/tvOS 15.0+, keep both at 13.4 for compatibility with affected Expo SDKs
17
18
  # The iOS/tvOS 15.0+ requirement is enforced at build time in source code via @available annotations
18
19
  s.platforms = { :ios => '13.4', :tvos => '13.4' }
19
- s.swift_version = '5.4'
20
+ s.swift_version = '5.9'
20
21
  s.source = { git: 'https://github.com/hyochan/expo-iap' }
21
22
  s.static_framework = true
22
23
 
23
24
  s.dependency 'ExpoModulesCore'
24
- s.dependency 'openiap', '1.1.12'
25
+ s.dependency 'openiap', versions['apple']
25
26
 
26
27
  # Swift/Objective-C compatibility
27
28
  s.pod_target_xcconfig = {
@@ -0,0 +1,96 @@
1
+ import Foundation
2
+ import OpenIAP
3
+
4
+ enum ExpoIapHelper {
5
+ static func sanitizeDictionary(_ dictionary: [String: Any?]) -> [String: Any] {
6
+ var result: [String: Any] = [:]
7
+ for (key, value) in dictionary {
8
+ if let value {
9
+ result[key] = value
10
+ }
11
+ }
12
+ return result
13
+ }
14
+
15
+ static func sanitizeArray(_ array: [[String: Any?]]) -> [[String: Any]] {
16
+ array.map { sanitizeDictionary($0) }
17
+ }
18
+
19
+ // Overloads to support already-sanitized payloads (e.g., serialized OpenIAP responses)
20
+ static func sanitizeDictionary(_ dictionary: [String: Any]) -> [String: Any] {
21
+ dictionary
22
+ }
23
+
24
+ static func sanitizeArray(_ array: [[String: Any]]) -> [[String: Any]] {
25
+ array
26
+ }
27
+
28
+ static func parseProductQueryType(_ rawValue: String?) -> ProductQueryType {
29
+ guard let raw = rawValue?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else {
30
+ return .all
31
+ }
32
+ switch raw.lowercased() {
33
+ case "inapp", ProductQueryType.inApp.rawValue:
34
+ return .inApp
35
+ case ProductQueryType.subs.rawValue:
36
+ return .subs
37
+ case ProductQueryType.all.rawValue:
38
+ return .all
39
+ default:
40
+ return .all
41
+ }
42
+ }
43
+
44
+ static func decodeProductRequest(from payload: [String: Any]) throws -> ProductRequest {
45
+ if let skus = payload["skus"] as? [String], !skus.isEmpty {
46
+ let type = parseProductQueryType(payload["type"] as? String)
47
+ return try OpenIapSerialization.productRequest(skus: skus, type: type)
48
+ }
49
+
50
+ let indexedSkus = payload.keys
51
+ .compactMap { Int($0) }
52
+ .sorted()
53
+ .compactMap { payload[String($0)] as? String }
54
+
55
+ if !indexedSkus.isEmpty {
56
+ return try OpenIapSerialization.productRequest(skus: indexedSkus, type: .all)
57
+ }
58
+
59
+ if let request = try? OpenIapSerialization.decode(object: payload, as: ProductRequest.self) {
60
+ return request
61
+ }
62
+
63
+ throw PurchaseError.emptySkuList()
64
+ }
65
+
66
+ static func decodeRequestPurchaseProps(from payload: [String: Any]) throws -> RequestPurchaseProps {
67
+ if payload["requestPurchase"] != nil || payload["requestSubscription"] != nil {
68
+ return try OpenIapSerialization.decode(object: payload, as: RequestPurchaseProps.self)
69
+ }
70
+
71
+ if let request = payload["request"] {
72
+ let parsedType = parseProductQueryType(payload["type"] as? String)
73
+ let purchaseType: ProductQueryType = parsedType == .all ? .inApp : parsedType
74
+ var normalized: [String: Any] = ["type": purchaseType.rawValue]
75
+ switch purchaseType {
76
+ case .subs:
77
+ normalized["requestSubscription"] = request
78
+ case .inApp:
79
+ normalized["requestPurchase"] = request
80
+ case .all:
81
+ break
82
+ }
83
+ return try OpenIapSerialization.decode(object: normalized, as: RequestPurchaseProps.self)
84
+ }
85
+
86
+ if payload["sku"] != nil {
87
+ let normalized: [String: Any] = [
88
+ "type": ProductQueryType.inApp.rawValue,
89
+ "requestPurchase": ["ios": payload]
90
+ ]
91
+ return try OpenIapSerialization.decode(object: normalized, as: RequestPurchaseProps.self)
92
+ }
93
+
94
+ throw PurchaseError.make(code: .developerError, message: "Invalid request payload")
95
+ }
96
+ }
@@ -0,0 +1,127 @@
1
+ import Foundation
2
+ #if canImport(os)
3
+ import os
4
+ #endif
5
+
6
+ enum ExpoIapLog {
7
+ enum Level: String {
8
+ case debug
9
+ case info
10
+ case warn
11
+ case error
12
+ }
13
+
14
+ private static var isEnabled: Bool = {
15
+ #if DEBUG
16
+ true
17
+ #else
18
+ false
19
+ #endif
20
+ }()
21
+
22
+ private static var customHandler: ((Level, String) -> Void)?
23
+
24
+ static func setEnabled(_ enabled: Bool) {
25
+ isEnabled = enabled
26
+ }
27
+
28
+ static func setHandler(_ handler: ((Level, String) -> Void)?) {
29
+ customHandler = handler
30
+ }
31
+
32
+ static func debug(_ message: String) { log(.debug, message) }
33
+ static func info(_ message: String) { log(.info, message) }
34
+ static func warn(_ message: String) { log(.warn, message) }
35
+ static func error(_ message: String) { log(.error, message) }
36
+
37
+ static func payload(_ name: String, payload: Any?) {
38
+ debug("\(name) payload: \(stringify(payload))")
39
+ }
40
+
41
+ static func result(_ name: String, value: Any?) {
42
+ debug("\(name) result: \(stringify(value))")
43
+ }
44
+
45
+ static func failure(_ name: String, error: Error) {
46
+ ExpoIapLog.error("\(name) failed: \(error.localizedDescription)")
47
+ }
48
+
49
+ private static func log(_ level: Level, _ message: String) {
50
+ guard isEnabled else { return }
51
+
52
+ if let handler = customHandler {
53
+ handler(level, message)
54
+ return
55
+ }
56
+
57
+ #if canImport(os)
58
+ let logger = Logger(subsystem: "dev.hyo.expo-iap", category: "ExpoIap")
59
+ let formatted = "[ExpoIap] \(message)"
60
+ switch level {
61
+ case .debug:
62
+ logger.debug("\(formatted, privacy: .public)")
63
+ case .info:
64
+ logger.info("\(formatted, privacy: .public)")
65
+ case .warn:
66
+ logger.warning("\(formatted, privacy: .public)")
67
+ case .error:
68
+ logger.error("\(formatted, privacy: .public)")
69
+ }
70
+ #else
71
+ NSLog("[ExpoIap][%@] %@", level.rawValue.uppercased(), message)
72
+ #endif
73
+ }
74
+
75
+ private static func stringify(_ value: Any?) -> String {
76
+ guard let sanitized = sanitize(value) else {
77
+ return "null"
78
+ }
79
+
80
+ if JSONSerialization.isValidJSONObject(sanitized),
81
+ let data = try? JSONSerialization.data(withJSONObject: sanitized, options: []) {
82
+ return String(data: data, encoding: .utf8) ?? String(describing: sanitized)
83
+ }
84
+
85
+ return String(describing: sanitized)
86
+ }
87
+
88
+ private static func sanitize(_ value: Any?) -> Any? {
89
+ guard let value else { return nil }
90
+
91
+ if let dictionary = value as? [String: Any] {
92
+ return sanitizeDictionary(dictionary)
93
+ }
94
+
95
+ if let optionalDictionary = value as? [String: Any?] {
96
+ var compact: [String: Any] = [:]
97
+ for (key, optionalValue) in optionalDictionary {
98
+ if let optionalValue {
99
+ compact[key] = optionalValue
100
+ }
101
+ }
102
+ return sanitizeDictionary(compact)
103
+ }
104
+
105
+ if let array = value as? [Any] {
106
+ return array.compactMap { sanitize($0) }
107
+ }
108
+
109
+ if let optionalArray = value as? [Any?] {
110
+ return optionalArray.compactMap { sanitize($0) }
111
+ }
112
+
113
+ return value
114
+ }
115
+
116
+ private static func sanitizeDictionary(_ dictionary: [String: Any]) -> [String: Any] {
117
+ var sanitized: [String: Any] = [:]
118
+ for (key, value) in dictionary {
119
+ if key.lowercased().contains("token") {
120
+ sanitized[key] = "hidden"
121
+ } else if let sanitizedValue = sanitize(value) {
122
+ sanitized[key] = sanitizedValue
123
+ }
124
+ }
125
+ return sanitized
126
+ }
127
+ }