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.
- package/CLAUDE.md +2 -2
- package/CONTRIBUTING.md +19 -0
- package/README.md +18 -6
- package/android/build.gradle +24 -1
- package/android/src/main/java/expo/modules/iap/ExpoIapLog.kt +69 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +190 -59
- package/build/ExpoIapModule.d.ts +1 -0
- package/build/ExpoIapModule.d.ts.map +1 -1
- package/build/ExpoIapModule.js +29 -4
- package/build/ExpoIapModule.js.map +1 -1
- package/build/index.d.ts +20 -47
- package/build/index.d.ts.map +1 -1
- package/build/index.js +94 -137
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +2 -1
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +16 -1
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +29 -16
- package/build/modules/ios.js.map +1 -1
- package/build/types.d.ts +8 -6
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/build/useIAP.d.ts +1 -1
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +12 -15
- package/build/useIAP.js.map +1 -1
- package/build/utils/constants.d.ts +2 -0
- package/build/utils/constants.d.ts.map +1 -1
- package/build/utils/constants.js +9 -2
- package/build/utils/constants.js.map +1 -1
- package/build/utils/errorMapping.d.ts +32 -23
- package/build/utils/errorMapping.d.ts.map +1 -1
- package/build/utils/errorMapping.js +117 -22
- package/build/utils/errorMapping.js.map +1 -1
- package/coverage/clover.xml +497 -0
- package/coverage/coverage-final.json +6 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +161 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/ExpoIap.types.ts.html +1243 -0
- package/coverage/lcov-report/src/PurchaseError.ts.html +787 -0
- package/coverage/lcov-report/src/helpers/index.html +116 -0
- package/coverage/lcov-report/src/helpers/subscription.ts.html +496 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/index.ts.html +1993 -0
- package/coverage/lcov-report/src/modules/android.ts.html +550 -0
- package/coverage/lcov-report/src/modules/index.html +131 -0
- package/coverage/lcov-report/src/modules/ios.ts.html +1222 -0
- package/coverage/lcov-report/src/purchase-error.ts.html +880 -0
- package/coverage/lcov-report/src/types/ExpoIapAndroid.types.ts.html +493 -0
- package/coverage/lcov-report/src/types/index.html +116 -0
- package/coverage/lcov-report/src/useIap.ts.html +1483 -0
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1069 -0
- package/coverage/lcov-report/src/utils/index.html +116 -0
- package/coverage/lcov-report/src/utils/purchase.ts.html +241 -0
- package/coverage/lcov.info +929 -0
- package/expo-module.config.json +10 -3
- package/ios/ExpoIap.podspec +3 -2
- package/ios/ExpoIapHelper.swift +96 -0
- package/ios/ExpoIapLog.swift +127 -0
- package/ios/ExpoIapModule.swift +218 -340
- package/ios/OneSideModule.swift +489 -0
- package/ios/expoiap.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/expoiap.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/openiap-versions.json +5 -0
- package/package.json +4 -3
- package/plugin/build/withIAP.d.ts +22 -9
- package/plugin/build/withIAP.js +163 -13
- package/plugin/jest.config.js +13 -3
- package/plugin/src/expoConfig.augmentation.d.ts +38 -0
- package/plugin/src/withIAP.ts +272 -22
- package/plugin/tsconfig.json +2 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/scripts/update-types.mjs +20 -1
- package/src/ExpoIapModule.ts +45 -4
- package/src/index.ts +122 -165
- package/src/modules/android.ts +2 -1
- package/src/modules/ios.ts +31 -19
- package/src/types.ts +8 -6
- package/src/useIAP.ts +17 -25
- package/src/utils/constants.ts +11 -2
- package/src/utils/errorMapping.ts +203 -23
- package/build/purchase-error.d.ts +0 -67
- package/build/purchase-error.d.ts.map +0 -1
- package/build/purchase-error.js +0 -166
- package/build/purchase-error.js.map +0 -1
- package/build/utils/purchase.d.ts +0 -9
- package/build/utils/purchase.d.ts.map +0 -1
- package/build/utils/purchase.js +0 -34
- package/build/utils/purchase.js.map +0 -1
- package/src/purchase-error.ts +0 -265
- package/src/utils/purchase.ts +0 -52
package/expo-module.config.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
-
"platforms": [
|
|
2
|
+
"platforms": [
|
|
3
|
+
"ios",
|
|
4
|
+
"android"
|
|
5
|
+
],
|
|
3
6
|
"ios": {
|
|
4
|
-
"modules": [
|
|
7
|
+
"modules": [
|
|
8
|
+
"OneSideModule"
|
|
9
|
+
]
|
|
5
10
|
},
|
|
6
11
|
"android": {
|
|
7
|
-
"modules": [
|
|
12
|
+
"modules": [
|
|
13
|
+
"expo.modules.iap.ExpoIapModule"
|
|
14
|
+
]
|
|
8
15
|
}
|
|
9
16
|
}
|
package/ios/ExpoIap.podspec
CHANGED
|
@@ -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.
|
|
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', '
|
|
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
|
+
}
|