expo-iap 2.9.6 → 3.0.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/.eslintrc.js +24 -0
- package/CHANGELOG.md +43 -0
- package/README.md +1 -1
- package/android/build.gradle +7 -2
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +195 -668
- package/android/src/main/java/expo/modules/iap/PromiseUtils.kt +85 -0
- package/build/ExpoIap.types.d.ts +0 -6
- package/build/ExpoIap.types.d.ts.map +1 -1
- package/build/ExpoIap.types.js.map +1 -1
- package/build/helpers/subscription.d.ts.map +1 -1
- package/build/helpers/subscription.js +14 -3
- package/build/helpers/subscription.js.map +1 -1
- package/build/index.d.ts +6 -73
- package/build/index.d.ts.map +1 -1
- package/build/index.js +21 -154
- package/build/index.js.map +1 -1
- package/build/modules/android.d.ts +2 -2
- package/build/modules/android.d.ts.map +1 -1
- package/build/modules/android.js +11 -1
- package/build/modules/android.js.map +1 -1
- package/build/modules/ios.d.ts +0 -60
- package/build/modules/ios.d.ts.map +1 -1
- package/build/modules/ios.js +2 -121
- package/build/modules/ios.js.map +1 -1
- package/build/types/ExpoIapAndroid.types.d.ts +0 -8
- package/build/types/ExpoIapAndroid.types.d.ts.map +1 -1
- package/build/types/ExpoIapAndroid.types.js +0 -1
- package/build/types/ExpoIapAndroid.types.js.map +1 -1
- package/build/types/ExpoIapIOS.types.d.ts +0 -5
- package/build/types/ExpoIapIOS.types.d.ts.map +1 -1
- package/build/types/ExpoIapIOS.types.js.map +1 -1
- package/build/useIAP.d.ts +0 -18
- package/build/useIAP.d.ts.map +1 -1
- package/build/useIAP.js +1 -18
- package/build/useIAP.js.map +1 -1
- package/bun.lock +340 -137
- package/codecov.yml +17 -21
- package/ios/ExpoIapModule.swift +50 -23
- package/jest.config.js +5 -9
- package/package.json +5 -3
- package/plugin/build/withIAP.d.ts +4 -1
- package/plugin/build/withIAP.js +38 -24
- package/plugin/build/withLocalOpenIAP.d.ts +6 -2
- package/plugin/build/withLocalOpenIAP.js +175 -20
- package/plugin/src/withIAP.ts +66 -30
- package/plugin/src/withLocalOpenIAP.ts +228 -24
- package/src/ExpoIap.types.ts +0 -8
- package/src/helpers/subscription.ts +14 -3
- package/src/index.ts +22 -230
- package/src/modules/android.ts +16 -6
- package/src/modules/ios.ts +2 -168
- package/src/types/ExpoIapAndroid.types.ts +0 -11
- package/src/types/ExpoIapIOS.types.ts +0 -5
- package/src/useIAP.ts +3 -55
- package/android/src/main/java/expo/modules/iap/PlayUtils.kt +0 -178
- package/android/src/main/java/expo/modules/iap/Types.kt +0 -98
package/codecov.yml
CHANGED
|
@@ -6,8 +6,8 @@ codecov:
|
|
|
6
6
|
coverage:
|
|
7
7
|
precision: 2
|
|
8
8
|
round: down
|
|
9
|
-
range:
|
|
10
|
-
|
|
9
|
+
range: '50...100'
|
|
10
|
+
|
|
11
11
|
status:
|
|
12
12
|
project:
|
|
13
13
|
default:
|
|
@@ -18,7 +18,7 @@ coverage:
|
|
|
18
18
|
- unittests
|
|
19
19
|
paths:
|
|
20
20
|
- src
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
example:
|
|
23
23
|
target: 40%
|
|
24
24
|
threshold: 10%
|
|
@@ -27,7 +27,7 @@ coverage:
|
|
|
27
27
|
- example
|
|
28
28
|
paths:
|
|
29
29
|
- example
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
patch:
|
|
32
32
|
default:
|
|
33
33
|
target: 60%
|
|
@@ -43,7 +43,7 @@ parsers:
|
|
|
43
43
|
macro: no
|
|
44
44
|
|
|
45
45
|
comment:
|
|
46
|
-
layout:
|
|
46
|
+
layout: 'reach,diff,flags,files,footer'
|
|
47
47
|
behavior: default
|
|
48
48
|
require_changes: false
|
|
49
49
|
require_base: false
|
|
@@ -54,21 +54,17 @@ flags:
|
|
|
54
54
|
paths:
|
|
55
55
|
- src/
|
|
56
56
|
carryforward: false
|
|
57
|
-
|
|
58
|
-
example:
|
|
59
|
-
paths:
|
|
60
|
-
- example/
|
|
61
|
-
carryforward: false
|
|
62
57
|
|
|
63
58
|
ignore:
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
59
|
+
- '**/__tests__/**'
|
|
60
|
+
- '**/*.test.ts'
|
|
61
|
+
- '**/*.test.tsx'
|
|
62
|
+
- '**/node_modules/**'
|
|
63
|
+
- 'build/**'
|
|
64
|
+
- 'android/**'
|
|
65
|
+
- 'ios/**'
|
|
66
|
+
- 'docs/**'
|
|
67
|
+
- 'plugin/**'
|
|
68
|
+
- '*.config.js'
|
|
69
|
+
- '*.setup.js'
|
|
70
|
+
- 'example/**'
|
package/ios/ExpoIapModule.swift
CHANGED
|
@@ -22,6 +22,8 @@ struct OpenIapEvent {
|
|
|
22
22
|
@available(iOS 15.0, tvOS 15.0, *)
|
|
23
23
|
@MainActor
|
|
24
24
|
public class ExpoIapModule: Module {
|
|
25
|
+
// Connection state for local validation parity with RN module
|
|
26
|
+
private var isInitialized: Bool = false
|
|
25
27
|
// Subscriptions for OpenIapModule event listeners
|
|
26
28
|
private var purchaseUpdatedSub: Subscription?
|
|
27
29
|
private var purchaseErrorSub: Subscription?
|
|
@@ -65,6 +67,8 @@ public class ExpoIapModule: Module {
|
|
|
65
67
|
AsyncFunction("initConnection") { () async throws -> Bool in
|
|
66
68
|
logDebug("initConnection called")
|
|
67
69
|
let isConnected = try await OpenIapModule.shared.initConnection()
|
|
70
|
+
// Track initialization locally for ensureConnection()
|
|
71
|
+
await MainActor.run { self.isInitialized = isConnected }
|
|
68
72
|
logDebug("Connection initialized: \(isConnected)")
|
|
69
73
|
return isConnected
|
|
70
74
|
}
|
|
@@ -74,12 +78,14 @@ public class ExpoIapModule: Module {
|
|
|
74
78
|
let _ = try await OpenIapModule.shared.endConnection()
|
|
75
79
|
|
|
76
80
|
logDebug("Connection ended")
|
|
81
|
+
await MainActor.run { self.isInitialized = false }
|
|
77
82
|
return true
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
// MARK: - Product Management
|
|
81
86
|
|
|
82
87
|
AsyncFunction("fetchProducts") { (params: [String: Any]) async throws -> [[String: Any?]] in
|
|
88
|
+
try await ensureConnection()
|
|
83
89
|
logDebug("fetchProducts raw params: \(params)")
|
|
84
90
|
|
|
85
91
|
// Handle both object format {skus: [...], type: "..."} and array format
|
|
@@ -148,6 +154,7 @@ public class ExpoIapModule: Module {
|
|
|
148
154
|
guard let sku = params["sku"] as? String, !sku.isEmpty else {
|
|
149
155
|
throw OpenIapError.make(code: OpenIapError.E_PURCHASE_ERROR, message: "Missing required 'sku'")
|
|
150
156
|
}
|
|
157
|
+
try await ensureConnection()
|
|
151
158
|
|
|
152
159
|
// Optional fields
|
|
153
160
|
let andFinish = (params["andDangerouslyFinishTransactionAutomatically"] as? Bool) ?? false
|
|
@@ -205,6 +212,7 @@ public class ExpoIapModule: Module {
|
|
|
205
212
|
}
|
|
206
213
|
|
|
207
214
|
AsyncFunction("finishTransaction") { (transactionId: String) async throws -> Bool in
|
|
215
|
+
try await ensureConnection()
|
|
208
216
|
logDebug("finishTransaction called with id: \(transactionId)")
|
|
209
217
|
let result = try await OpenIapModule.shared.finishTransaction(transactionIdentifier: transactionId)
|
|
210
218
|
return result
|
|
@@ -213,6 +221,7 @@ public class ExpoIapModule: Module {
|
|
|
213
221
|
// MARK: - Purchase History
|
|
214
222
|
|
|
215
223
|
AsyncFunction("getAvailablePurchases") { (options: [String: Any?]?) async throws -> [[String: Any?]] in
|
|
224
|
+
try await ensureConnection()
|
|
216
225
|
logDebug("getAvailablePurchases called")
|
|
217
226
|
|
|
218
227
|
// Build options and get purchases directly from OpenIapModule
|
|
@@ -228,6 +237,7 @@ public class ExpoIapModule: Module {
|
|
|
228
237
|
|
|
229
238
|
// Legacy function for backward compatibility
|
|
230
239
|
AsyncFunction("getAvailableItems") { (alsoPublishToEventListener: Bool, onlyIncludeActiveItems: Bool) async throws -> [[String: Any?]] in
|
|
240
|
+
try await ensureConnection()
|
|
231
241
|
logDebug("getAvailableItems called (legacy)")
|
|
232
242
|
|
|
233
243
|
let purchaseOptions = OpenIapGetAvailablePurchasesProps(
|
|
@@ -239,6 +249,7 @@ public class ExpoIapModule: Module {
|
|
|
239
249
|
}
|
|
240
250
|
|
|
241
251
|
AsyncFunction("getPendingTransactionsIOS") { () async throws -> [[String: Any?]] in
|
|
252
|
+
try await ensureConnection()
|
|
242
253
|
logDebug("getPendingTransactionsIOS called")
|
|
243
254
|
|
|
244
255
|
let pendingTransactions = try await OpenIapModule.shared.getPendingTransactionsIOS()
|
|
@@ -246,6 +257,7 @@ public class ExpoIapModule: Module {
|
|
|
246
257
|
}
|
|
247
258
|
|
|
248
259
|
AsyncFunction("clearTransactionIOS") { () async throws -> Bool in
|
|
260
|
+
try await ensureConnection()
|
|
249
261
|
logDebug("clearTransactionIOS called")
|
|
250
262
|
try await OpenIapModule.shared.clearTransactionIOS()
|
|
251
263
|
return true
|
|
@@ -254,17 +266,27 @@ public class ExpoIapModule: Module {
|
|
|
254
266
|
// MARK: - Receipt & Validation
|
|
255
267
|
|
|
256
268
|
AsyncFunction("getReceiptIOS") { () async throws -> String in
|
|
269
|
+
try await ensureConnection()
|
|
257
270
|
logDebug("getReceiptIOS called")
|
|
258
271
|
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
|
|
259
272
|
}
|
|
260
273
|
|
|
274
|
+
// Backward-compatible alias expected by JS layer/tests
|
|
275
|
+
AsyncFunction("getReceiptDataIOS") { () async throws -> String in
|
|
276
|
+
try await ensureConnection()
|
|
277
|
+
logDebug("getReceiptDataIOS called (alias of getReceiptIOS)")
|
|
278
|
+
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
|
|
279
|
+
}
|
|
280
|
+
|
|
261
281
|
AsyncFunction("requestReceiptRefreshIOS") { () async throws -> String in
|
|
282
|
+
try await ensureConnection()
|
|
262
283
|
logDebug("requestReceiptRefreshIOS called")
|
|
263
284
|
// Receipt refresh is handled automatically by StoreKit 2
|
|
264
285
|
return try await OpenIapModule.shared.getReceiptDataIOS() ?? ""
|
|
265
286
|
}
|
|
266
287
|
|
|
267
288
|
AsyncFunction("validateReceiptIOS") { (sku: String) async throws -> [String: Any?] in
|
|
289
|
+
try await ensureConnection()
|
|
268
290
|
logDebug("validateReceiptIOS called for sku: \(sku)")
|
|
269
291
|
do {
|
|
270
292
|
// Use OpenIapReceiptValidationProps to keep naming parity with OpenIAP
|
|
@@ -286,15 +308,17 @@ public class ExpoIapModule: Module {
|
|
|
286
308
|
// MARK: - iOS Specific Features
|
|
287
309
|
|
|
288
310
|
AsyncFunction("presentCodeRedemptionSheetIOS") { () async throws -> Bool in
|
|
311
|
+
try await ensureConnection()
|
|
289
312
|
logDebug("presentCodeRedemptionSheetIOS called")
|
|
290
313
|
let _ = try await OpenIapModule.shared.presentCodeRedemptionSheetIOS()
|
|
291
314
|
return true
|
|
292
315
|
}
|
|
293
316
|
|
|
294
|
-
AsyncFunction("showManageSubscriptionsIOS") { () async throws ->
|
|
317
|
+
AsyncFunction("showManageSubscriptionsIOS") { () async throws -> [[String: Any?]] in
|
|
318
|
+
try await ensureConnection()
|
|
295
319
|
logDebug("showManageSubscriptionsIOS called")
|
|
296
|
-
let
|
|
297
|
-
return
|
|
320
|
+
let purchases = try await OpenIapModule.shared.showManageSubscriptionsIOS()
|
|
321
|
+
return OpenIapSerialization.purchases(purchases)
|
|
298
322
|
}
|
|
299
323
|
|
|
300
324
|
AsyncFunction("deepLinkToSubscriptionsIOS") { () async throws in
|
|
@@ -310,11 +334,13 @@ public class ExpoIapModule: Module {
|
|
|
310
334
|
}
|
|
311
335
|
|
|
312
336
|
AsyncFunction("beginRefundRequestIOS") { (sku: String) async throws -> String? in
|
|
337
|
+
try await ensureConnection()
|
|
313
338
|
logDebug("beginRefundRequestIOS called for sku: \(sku)")
|
|
314
339
|
return try await OpenIapModule.shared.beginRefundRequestIOS(sku: sku)
|
|
315
340
|
}
|
|
316
341
|
|
|
317
342
|
AsyncFunction("getPromotedProductIOS") { () async throws -> [String: Any?]? in
|
|
343
|
+
try await ensureConnection()
|
|
318
344
|
logDebug("getPromotedProductIOS called")
|
|
319
345
|
|
|
320
346
|
if let promoted = try await OpenIapModule.shared.getPromotedProductIOS() {
|
|
@@ -327,11 +353,13 @@ public class ExpoIapModule: Module {
|
|
|
327
353
|
return nil
|
|
328
354
|
}
|
|
329
355
|
AsyncFunction("getStorefrontIOS") { () async throws -> String in
|
|
356
|
+
try await ensureConnection()
|
|
330
357
|
logDebug("getStorefrontIOS called")
|
|
331
358
|
return try await OpenIapModule.shared.getStorefrontIOS()
|
|
332
359
|
}
|
|
333
360
|
|
|
334
361
|
AsyncFunction("syncIOS") { () async throws -> Bool in
|
|
362
|
+
try await ensureConnection()
|
|
335
363
|
logDebug("syncIOS called")
|
|
336
364
|
return try await OpenIapModule.shared.syncIOS()
|
|
337
365
|
}
|
|
@@ -339,21 +367,25 @@ public class ExpoIapModule: Module {
|
|
|
339
367
|
// MARK: - Additional iOS Methods
|
|
340
368
|
|
|
341
369
|
AsyncFunction("isTransactionVerifiedIOS") { (sku: String) async throws -> Bool in
|
|
370
|
+
try await ensureConnection()
|
|
342
371
|
logDebug("isTransactionVerifiedIOS called for sku: \(sku)")
|
|
343
372
|
return await OpenIapModule.shared.isTransactionVerifiedIOS(sku: sku)
|
|
344
373
|
}
|
|
345
374
|
|
|
346
375
|
AsyncFunction("getTransactionJwsIOS") { (sku: String) async throws -> String? in
|
|
376
|
+
try await ensureConnection()
|
|
347
377
|
logDebug("getTransactionJwsIOS called for sku: \(sku)")
|
|
348
378
|
return try await OpenIapModule.shared.getTransactionJwsIOS(sku: sku)
|
|
349
379
|
}
|
|
350
380
|
|
|
351
381
|
AsyncFunction("isEligibleForIntroOfferIOS") { (groupID: String) async throws -> Bool in
|
|
382
|
+
try await ensureConnection()
|
|
352
383
|
logDebug("isEligibleForIntroOfferIOS called for groupID: \(groupID)")
|
|
353
384
|
return await OpenIapModule.shared.isEligibleForIntroOfferIOS(groupID: groupID)
|
|
354
385
|
}
|
|
355
386
|
|
|
356
387
|
AsyncFunction("subscriptionStatusIOS") { (sku: String) async throws -> [[String: Any?]]? in
|
|
388
|
+
try await ensureConnection()
|
|
357
389
|
logDebug("subscriptionStatusIOS called for sku: \(sku)")
|
|
358
390
|
|
|
359
391
|
if let statuses = try await OpenIapModule.shared.subscriptionStatusIOS(sku: sku) {
|
|
@@ -365,27 +397,9 @@ public class ExpoIapModule: Module {
|
|
|
365
397
|
]
|
|
366
398
|
|
|
367
399
|
if let info = status.renewalInfo {
|
|
368
|
-
//
|
|
369
|
-
let willAutoRenew: Bool = {
|
|
370
|
-
// Try boolean first
|
|
371
|
-
if let b = info.autoRenewStatus as? Bool { return b }
|
|
372
|
-
// Fallback to string normalization
|
|
373
|
-
let normalized = String(describing: info.autoRenewStatus).lowercased()
|
|
374
|
-
let truthy = Set([
|
|
375
|
-
"willrenew",
|
|
376
|
-
"will_autorenew",
|
|
377
|
-
"will-auto-renew",
|
|
378
|
-
"auto_renew_on",
|
|
379
|
-
"true",
|
|
380
|
-
"1",
|
|
381
|
-
"on",
|
|
382
|
-
"yes",
|
|
383
|
-
])
|
|
384
|
-
return truthy.contains(normalized)
|
|
385
|
-
}()
|
|
386
|
-
|
|
400
|
+
// autoRenewStatus is a Bool from OpenIAP types
|
|
387
401
|
let renewalInfo: [String: Any?] = [
|
|
388
|
-
"willAutoRenew":
|
|
402
|
+
"willAutoRenew": info.autoRenewStatus,
|
|
389
403
|
"autoRenewPreference": info.autoRenewPreference
|
|
390
404
|
]
|
|
391
405
|
dict["renewalInfo"] = renewalInfo
|
|
@@ -398,6 +412,7 @@ public class ExpoIapModule: Module {
|
|
|
398
412
|
}
|
|
399
413
|
|
|
400
414
|
AsyncFunction("currentEntitlementIOS") { (sku: String) async throws -> [String: Any?]? in
|
|
415
|
+
try await ensureConnection()
|
|
401
416
|
logDebug("currentEntitlementIOS called for sku: \(sku)")
|
|
402
417
|
do {
|
|
403
418
|
if let entitlement = try await OpenIapModule.shared.currentEntitlementIOS(sku: sku) {
|
|
@@ -410,6 +425,7 @@ public class ExpoIapModule: Module {
|
|
|
410
425
|
}
|
|
411
426
|
|
|
412
427
|
AsyncFunction("latestTransactionIOS") { (sku: String) async throws -> [String: Any?]? in
|
|
428
|
+
try await ensureConnection()
|
|
413
429
|
logDebug("latestTransactionIOS called for sku: \(sku)")
|
|
414
430
|
do {
|
|
415
431
|
if let transaction = try await OpenIapModule.shared.latestTransactionIOS(sku: sku) {
|
|
@@ -468,4 +484,15 @@ public class ExpoIapModule: Module {
|
|
|
468
484
|
_ = try? await OpenIapModule.shared.endConnection()
|
|
469
485
|
}
|
|
470
486
|
|
|
487
|
+
// MARK: - Private Helper Methods
|
|
488
|
+
|
|
489
|
+
private func ensureConnection() throws {
|
|
490
|
+
guard isInitialized else {
|
|
491
|
+
throw OpenIapError.make(
|
|
492
|
+
code: OpenIapError.E_INIT_CONNECTION,
|
|
493
|
+
message: "Connection not initialized. Call initConnection() first."
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
471
498
|
}
|
package/jest.config.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
preset: 'ts-jest',
|
|
3
3
|
testEnvironment: 'node',
|
|
4
|
-
// Disable watchman to avoid sandbox/permission issues in CI and sandboxes
|
|
5
4
|
watchman: false,
|
|
6
5
|
roots: ['<rootDir>/src'],
|
|
7
6
|
testMatch: [
|
|
@@ -34,12 +33,9 @@ module.exports = {
|
|
|
34
33
|
'!src/ExpoIapModule.ts',
|
|
35
34
|
'!src/ExpoIapModule.web.ts',
|
|
36
35
|
],
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
statements: 15,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
36
|
+
coveragePathIgnorePatterns: [
|
|
37
|
+
'<rootDir>/src/useIAP.ts',
|
|
38
|
+
'<rootDir>/src/ExpoIap.types.ts',
|
|
39
|
+
'<rootDir>/src/utils/constants.ts',
|
|
40
|
+
],
|
|
45
41
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-iap",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "In App Purchase module in Expo",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
"lint:eslint": "eslint --fix 'src/**/*.{ts,tsx}' 'plugin/src/**/*.{ts,tsx}'",
|
|
12
12
|
"lint:prettier": "prettier --write \"**/*.{md,js,jsx,ts,tsx}\"",
|
|
13
13
|
"lint:tsc": "tsc -p tsconfig.json --noEmit --skipLibCheck",
|
|
14
|
-
"lint:
|
|
14
|
+
"lint:kt": "sh -c 'command -v ktlint >/dev/null 2>&1 && ktlint --format ./android || { echo \"ktlint not installed; skipping\"; exit 0; }'",
|
|
15
|
+
"lint:ci": "bun run lint:tsc && bun run lint:eslint && bun run lint:prettier && bun run lint:kt",
|
|
15
16
|
"test": "jest",
|
|
16
17
|
"test:coverage": "jest --coverage",
|
|
17
|
-
"prepare": "expo-module prepare",
|
|
18
|
+
"prepare": "expo-module prepare && husky install",
|
|
18
19
|
"expo-module": "expo-module",
|
|
19
20
|
"open:ios": "xed example/ios",
|
|
20
21
|
"open:android": "open -a \"Android Studio\" example/android",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"license": "MIT",
|
|
43
44
|
"homepage": "https://github.com/hyochan/expo-iap#readme",
|
|
44
45
|
"devDependencies": {
|
|
46
|
+
"husky": "^9.0.11",
|
|
45
47
|
"@jest/globals": "^30.0.5",
|
|
46
48
|
"@types/jest": "^30.0.0",
|
|
47
49
|
"@types/react": "~19.1.7",
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
2
|
export interface ExpoIapPluginOptions {
|
|
3
3
|
/** Local development path for OpenIAP library */
|
|
4
|
-
localPath?: string
|
|
4
|
+
localPath?: string | {
|
|
5
|
+
ios?: string;
|
|
6
|
+
android?: string;
|
|
7
|
+
};
|
|
5
8
|
/** Enable local development mode */
|
|
6
9
|
enableLocalDev?: boolean;
|
|
7
10
|
}
|
package/plugin/build/withIAP.js
CHANGED
|
@@ -55,39 +55,43 @@ const addLineToGradle = (content, anchor, lineToAdd, offset = 1) => {
|
|
|
55
55
|
const lines = content.split('\n');
|
|
56
56
|
const index = lines.findIndex((line) => line.match(anchor));
|
|
57
57
|
if (index === -1) {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
config_plugins_1.WarningAggregator.addWarningAndroid('expo-iap', `dependencies { ... } block not found; skipping injection: ${lineToAdd.trim()}`);
|
|
59
|
+
return content;
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
62
62
|
lines.splice(index + offset, 0, lineToAdd);
|
|
63
63
|
}
|
|
64
64
|
return lines.join('\n');
|
|
65
65
|
};
|
|
66
|
-
const modifyAppBuildGradle = (gradle) => {
|
|
66
|
+
const modifyAppBuildGradle = (gradle, language) => {
|
|
67
67
|
let modified = gradle;
|
|
68
|
-
// Add
|
|
69
|
-
const
|
|
70
|
-
|
|
68
|
+
// Add OpenIAP dependency to app-level build.gradle(.kts)
|
|
69
|
+
const impl = (ga, v) => language === 'kotlin'
|
|
70
|
+
? ` implementation("${ga}:${v}")`
|
|
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');
|
|
74
|
+
const hasGA = (ga) => new RegExp(String.raw `\b(?:implementation|api)\s*\(?["']${ga}:`, 'm').test(modified);
|
|
71
75
|
let hasAddedDependency = false;
|
|
72
|
-
if (!
|
|
73
|
-
modified = addLineToGradle(modified, /dependencies\s*{/,
|
|
74
|
-
hasAddedDependency = true;
|
|
75
|
-
}
|
|
76
|
-
if (!modified.includes(gmsDep)) {
|
|
77
|
-
modified = addLineToGradle(modified, /dependencies\s*{/, gmsDep, 1);
|
|
76
|
+
if (!hasGA('io.github.hyochan.openiap:openiap-google')) {
|
|
77
|
+
modified = addLineToGradle(modified, /dependencies\s*{/, openiapDep, 0);
|
|
78
78
|
hasAddedDependency = true;
|
|
79
79
|
}
|
|
80
80
|
// Log only once and only if we actually added dependencies
|
|
81
81
|
if (hasAddedDependency)
|
|
82
|
-
logOnce('🛠️ expo-iap: Added
|
|
82
|
+
logOnce('🛠️ expo-iap: Added OpenIAP dependency to build.gradle');
|
|
83
83
|
return modified;
|
|
84
84
|
};
|
|
85
|
-
const withIapAndroid = (config) => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
config
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
const withIapAndroid = (config, props) => {
|
|
86
|
+
const addDeps = props?.addDeps ?? true;
|
|
87
|
+
if (addDeps) {
|
|
88
|
+
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
89
|
+
// language provided by config-plugins: 'groovy' | 'kotlin'
|
|
90
|
+
const language = config.modResults.language || 'groovy';
|
|
91
|
+
config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language);
|
|
92
|
+
return config;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
91
95
|
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
92
96
|
const manifest = config.modResults;
|
|
93
97
|
if (!manifest.manifest['uses-permission']) {
|
|
@@ -137,17 +141,27 @@ const withIapIOS = (config) => {
|
|
|
137
141
|
};
|
|
138
142
|
const withIap = (config, options) => {
|
|
139
143
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
const isLocalDev = !!(options?.enableLocalDev || options?.localPath);
|
|
145
|
+
// Apply Android modifications (skip adding deps when linking local module)
|
|
146
|
+
let result = withIapAndroid(config, { addDeps: !isLocalDev });
|
|
142
147
|
// iOS: choose one path to avoid overlap
|
|
143
148
|
if (options?.enableLocalDev || options?.localPath) {
|
|
144
149
|
if (!options?.localPath) {
|
|
145
150
|
config_plugins_1.WarningAggregator.addWarningIOS('expo-iap', 'enableLocalDev is true but no localPath provided. Skipping local OpenIAP integration.');
|
|
146
151
|
}
|
|
147
152
|
else {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
const raw = options.localPath;
|
|
154
|
+
const resolved = typeof raw === 'string'
|
|
155
|
+
? path.resolve(raw)
|
|
156
|
+
: {
|
|
157
|
+
ios: raw.ios ? path.resolve(raw.ios) : undefined,
|
|
158
|
+
android: raw.android ? path.resolve(raw.android) : undefined,
|
|
159
|
+
};
|
|
160
|
+
const preview = typeof resolved === 'string'
|
|
161
|
+
? resolved
|
|
162
|
+
: `ios=${resolved.ios ?? 'auto'}, android=${resolved.android ?? 'auto'}`;
|
|
163
|
+
logOnce(`🔧 [expo-iap] Enabling local OpenIAP: ${preview}`);
|
|
164
|
+
result = (0, withLocalOpenIAP_1.default)(result, { localPath: resolved });
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
167
|
else {
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { ConfigPlugin } from '
|
|
1
|
+
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
2
|
/**
|
|
3
3
|
* Plugin to add local OpenIAP pod dependency for development
|
|
4
4
|
* This is only for local development with openiap-apple library
|
|
5
5
|
*/
|
|
6
|
+
type LocalPathOption = string | {
|
|
7
|
+
ios?: string;
|
|
8
|
+
android?: string;
|
|
9
|
+
};
|
|
6
10
|
declare const withLocalOpenIAP: ConfigPlugin<{
|
|
7
|
-
localPath?:
|
|
11
|
+
localPath?: LocalPathOption;
|
|
8
12
|
} | void>;
|
|
9
13
|
export default withLocalOpenIAP;
|