expo-iap 3.1.18 โ 3.1.20
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/README.md +1 -1
- package/android/build.gradle +12 -0
- package/android/src/main/java/expo/modules/iap/ExpoIapModule.kt +1 -1
- package/build/types.d.ts +1 -1
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/bun.lockb +0 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/src/index.html +1 -1
- package/coverage/lcov-report/src/index.ts.html +1 -1
- package/coverage/lcov-report/src/modules/android.ts.html +1 -1
- package/coverage/lcov-report/src/modules/index.html +1 -1
- package/coverage/lcov-report/src/modules/ios.ts.html +1 -1
- package/coverage/lcov-report/src/utils/debug.ts.html +1 -1
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1 -1
- package/coverage/lcov-report/src/utils/index.html +1 -1
- package/ios/ExpoIap.podspec +2 -1
- package/openiap-versions.json +3 -3
- package/package.json +1 -1
- package/plugin/build/withIAP.d.ts +39 -21
- package/plugin/build/withIAP.js +72 -62
- package/plugin/build/withIosAlternativeBilling.d.ts +19 -0
- package/plugin/build/withIosAlternativeBilling.js +70 -0
- package/plugin/build/withLocalOpenIAP.d.ts +4 -1
- package/plugin/build/withLocalOpenIAP.js +58 -57
- package/plugin/src/withIAP.ts +145 -123
- package/plugin/src/withIosAlternativeBilling.ts +133 -0
- package/plugin/src/withLocalOpenIAP.ts +80 -67
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/types.ts +1 -1
package/plugin/build/withIAP.js
CHANGED
|
@@ -41,6 +41,8 @@ const config_plugins_1 = require("expo/config-plugins");
|
|
|
41
41
|
const fs = __importStar(require("fs"));
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const withLocalOpenIAP_1 = __importDefault(require("./withLocalOpenIAP"));
|
|
44
|
+
const withIosAlternativeBilling_1 = require("./withIosAlternativeBilling");
|
|
45
|
+
Object.defineProperty(exports, "withIosAlternativeBilling", { enumerable: true, get: function () { return withIosAlternativeBilling_1.withIosAlternativeBilling; } });
|
|
44
46
|
const pkg = require('../../package.json');
|
|
45
47
|
const openiapVersions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../openiap-versions.json'), 'utf8'));
|
|
46
48
|
const OPENIAP_ANDROID_VERSION = openiapVersions.google;
|
|
@@ -66,8 +68,10 @@ const addLineToGradle = (content, anchor, lineToAdd, offset = 1) => {
|
|
|
66
68
|
}
|
|
67
69
|
return lines.join('\n');
|
|
68
70
|
};
|
|
69
|
-
const modifyAppBuildGradle = (gradle, language) => {
|
|
71
|
+
const modifyAppBuildGradle = (gradle, language, isHorizonEnabled) => {
|
|
70
72
|
let modified = gradle;
|
|
73
|
+
// Determine which flavor to use based on isHorizonEnabled
|
|
74
|
+
const flavor = isHorizonEnabled ? 'horizon' : 'play';
|
|
71
75
|
// Ensure OpenIAP dependency exists at desired version in app-level build.gradle(.kts)
|
|
72
76
|
const impl = (ga, v) => language === 'kotlin'
|
|
73
77
|
? ` implementation("${ga}:${v}")`
|
|
@@ -87,18 +91,40 @@ const modifyAppBuildGradle = (gradle, language) => {
|
|
|
87
91
|
? `๐ ๏ธ expo-iap: Replaced OpenIAP dependency with ${OPENIAP_ANDROID_VERSION}`
|
|
88
92
|
: `๐ ๏ธ expo-iap: Added OpenIAP dependency (${OPENIAP_ANDROID_VERSION}) to build.gradle`);
|
|
89
93
|
}
|
|
94
|
+
// Add flavor dimension and default config for OpenIAP if horizon is enabled
|
|
95
|
+
if (isHorizonEnabled) {
|
|
96
|
+
// Add missingDimensionStrategy to select horizon flavor
|
|
97
|
+
const defaultConfigRegex = /defaultConfig\s*{/;
|
|
98
|
+
if (defaultConfigRegex.test(modified)) {
|
|
99
|
+
const strategyLine = language === 'kotlin'
|
|
100
|
+
? ` missingDimensionStrategy("platform", "${flavor}")`
|
|
101
|
+
: ` missingDimensionStrategy "platform", "${flavor}"`;
|
|
102
|
+
// Remove any existing platform strategies first to avoid duplicates
|
|
103
|
+
const strategyPattern = /^\s*missingDimensionStrategy\s*\(?\s*["']platform["']\s*,\s*["'](play|horizon)["']\s*\)?\s*$/gm;
|
|
104
|
+
if (strategyPattern.test(modified)) {
|
|
105
|
+
modified = modified.replace(strategyPattern, '');
|
|
106
|
+
logOnce('๐งน Removed existing missingDimensionStrategy for platform');
|
|
107
|
+
}
|
|
108
|
+
// Add the new strategy
|
|
109
|
+
if (!/missingDimensionStrategy.*platform/.test(modified)) {
|
|
110
|
+
modified = addLineToGradle(modified, defaultConfigRegex, strategyLine, 1);
|
|
111
|
+
logOnce(`๐ ๏ธ expo-iap: Added missingDimensionStrategy for ${flavor} flavor`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
90
115
|
return modified;
|
|
91
116
|
};
|
|
92
117
|
const withIapAndroid = (config, props) => {
|
|
93
118
|
const addDeps = props?.addDeps ?? true;
|
|
119
|
+
// Add dependencies if needed (only when not using local module)
|
|
94
120
|
if (addDeps) {
|
|
95
121
|
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
96
|
-
// language provided by config-plugins: 'groovy' | 'kotlin'
|
|
97
122
|
const language = config.modResults.language || 'groovy';
|
|
98
|
-
config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language);
|
|
123
|
+
config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language, props?.isHorizonEnabled);
|
|
99
124
|
return config;
|
|
100
125
|
});
|
|
101
126
|
}
|
|
127
|
+
// Note: missingDimensionStrategy for local dev is handled in withLocalOpenIAP
|
|
102
128
|
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
103
129
|
const manifest = config.modResults;
|
|
104
130
|
if (!manifest.manifest['uses-permission']) {
|
|
@@ -114,71 +140,44 @@ const withIapAndroid = (config, props) => {
|
|
|
114
140
|
else {
|
|
115
141
|
logOnce('โน๏ธ com.android.vending.BILLING already exists in AndroidManifest.xml');
|
|
116
142
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
146
|
-
const plist = config.modResults;
|
|
147
|
-
// 1. SKExternalPurchase (Required)
|
|
148
|
-
plist.SKExternalPurchase = options.countries;
|
|
149
|
-
logOnce(`โ
Added SKExternalPurchase with countries: ${options.countries?.join(', ')}`);
|
|
150
|
-
// 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
|
|
151
|
-
if (options.links && Object.keys(options.links).length > 0) {
|
|
152
|
-
plist.SKExternalPurchaseLink = options.links;
|
|
153
|
-
logOnce(`โ
Added SKExternalPurchaseLink for ${Object.keys(options.links).length} countries`);
|
|
154
|
-
}
|
|
155
|
-
// 3. SKExternalPurchaseMultiLink (iOS 17.5+)
|
|
156
|
-
if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
|
|
157
|
-
plist.SKExternalPurchaseMultiLink = options.multiLinks;
|
|
158
|
-
logOnce(`โ
Added SKExternalPurchaseMultiLink for ${Object.keys(options.multiLinks).length} countries`);
|
|
159
|
-
}
|
|
160
|
-
// 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
|
|
161
|
-
if (options.customLinkRegions && options.customLinkRegions.length > 0) {
|
|
162
|
-
plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
|
|
163
|
-
logOnce(`โ
Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(', ')}`);
|
|
164
|
-
}
|
|
165
|
-
// 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
|
|
166
|
-
if (options.streamingLinkRegions &&
|
|
167
|
-
options.streamingLinkRegions.length > 0) {
|
|
168
|
-
plist.SKExternalPurchaseLinkStreamingRegions =
|
|
169
|
-
options.streamingLinkRegions;
|
|
170
|
-
logOnce(`โ
Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions.join(', ')}`);
|
|
143
|
+
// Add Meta Horizon App ID if provided
|
|
144
|
+
if (props?.horizonAppId) {
|
|
145
|
+
if (!manifest.manifest.application ||
|
|
146
|
+
manifest.manifest.application.length === 0) {
|
|
147
|
+
manifest.manifest.application = [
|
|
148
|
+
{ $: { 'android:name': '.MainApplication' } },
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
const application = manifest.manifest.application[0];
|
|
152
|
+
if (!application['meta-data']) {
|
|
153
|
+
application['meta-data'] = [];
|
|
154
|
+
}
|
|
155
|
+
const metaData = application['meta-data'];
|
|
156
|
+
const horizonAppIdMeta = {
|
|
157
|
+
$: {
|
|
158
|
+
'android:name': 'com.oculus.vr.APP_ID',
|
|
159
|
+
'android:value': props.horizonAppId,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
const existingIndex = metaData.findIndex((m) => m.$['android:name'] === 'com.oculus.vr.APP_ID');
|
|
163
|
+
if (existingIndex !== -1) {
|
|
164
|
+
metaData[existingIndex] = horizonAppIdMeta;
|
|
165
|
+
logOnce(`โ
Updated com.oculus.vr.APP_ID to ${props.horizonAppId} in AndroidManifest.xml`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
metaData.push(horizonAppIdMeta);
|
|
169
|
+
logOnce(`โ
Added com.oculus.vr.APP_ID: ${props.horizonAppId} to AndroidManifest.xml`);
|
|
170
|
+
}
|
|
171
171
|
}
|
|
172
172
|
return config;
|
|
173
173
|
});
|
|
174
174
|
return config;
|
|
175
175
|
};
|
|
176
|
-
exports.withIosAlternativeBilling = withIosAlternativeBilling;
|
|
177
176
|
/** Ensure Podfile uses CocoaPods CDN and no stale local OpenIAP entry remains. */
|
|
178
177
|
const withIapIOS = (config, options) => {
|
|
179
178
|
// Add iOS alternative billing configuration if provided
|
|
180
179
|
if (options) {
|
|
181
|
-
config = withIosAlternativeBilling(config, options);
|
|
180
|
+
config = (0, withIosAlternativeBilling_1.withIosAlternativeBilling)(config, options);
|
|
182
181
|
}
|
|
183
182
|
return (0, config_plugins_1.withDangerousMod)(config, [
|
|
184
183
|
'ios',
|
|
@@ -208,10 +207,19 @@ const withIapIOS = (config, options) => {
|
|
|
208
207
|
};
|
|
209
208
|
const withIap = (config, options) => {
|
|
210
209
|
try {
|
|
210
|
+
// Read Horizon configuration from modules
|
|
211
|
+
const isHorizonEnabled = options?.modules?.horizon ?? false;
|
|
212
|
+
const horizonAppId = options?.android?.horizonAppId ?? options?.horizonAppId;
|
|
213
|
+
const iosAlternativeBilling = options?.ios?.alternativeBilling ?? options?.iosAlternativeBilling;
|
|
214
|
+
logOnce(`๐ [expo-iap] Config values: horizonAppId=${horizonAppId}, isHorizonEnabled=${isHorizonEnabled}`);
|
|
211
215
|
// Respect explicit flag; fall back to presence of localPath only when flag is unset
|
|
212
216
|
const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
|
|
213
217
|
// Apply Android modifications (skip adding deps when linking local module)
|
|
214
|
-
let result = withIapAndroid(config, {
|
|
218
|
+
let result = withIapAndroid(config, {
|
|
219
|
+
addDeps: !isLocalDev,
|
|
220
|
+
horizonAppId,
|
|
221
|
+
isHorizonEnabled,
|
|
222
|
+
});
|
|
215
223
|
// iOS: choose one path to avoid overlap
|
|
216
224
|
if (isLocalDev) {
|
|
217
225
|
if (!options?.localPath) {
|
|
@@ -231,13 +239,15 @@ const withIap = (config, options) => {
|
|
|
231
239
|
logOnce(`๐ง [expo-iap] Enabling local OpenIAP: ${preview}`);
|
|
232
240
|
result = (0, withLocalOpenIAP_1.default)(result, {
|
|
233
241
|
localPath: resolved,
|
|
234
|
-
iosAlternativeBilling
|
|
242
|
+
iosAlternativeBilling,
|
|
243
|
+
horizonAppId,
|
|
244
|
+
isHorizonEnabled, // Resolved from modules.horizon (line 467)
|
|
235
245
|
});
|
|
236
246
|
}
|
|
237
247
|
}
|
|
238
248
|
else {
|
|
239
249
|
// Ensure iOS Podfile is set up to resolve public CocoaPods specs
|
|
240
|
-
result = withIapIOS(result,
|
|
250
|
+
result = withIapIOS(result, iosAlternativeBilling);
|
|
241
251
|
logOnce('๐ฆ [expo-iap] Using OpenIAP from CocoaPods');
|
|
242
252
|
}
|
|
243
253
|
return result;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
+
export interface IOSAlternativeBillingConfig {
|
|
3
|
+
/** Country codes where external purchases are supported (ISO 3166-1 alpha-2) */
|
|
4
|
+
countries?: string[];
|
|
5
|
+
/** External purchase URLs per country (iOS 15.4+) */
|
|
6
|
+
links?: Record<string, string>;
|
|
7
|
+
/** Multiple external purchase URLs per country (iOS 17.5+, up to 5 per country) */
|
|
8
|
+
multiLinks?: Record<string, string[]>;
|
|
9
|
+
/** Custom link regions (iOS 18.1+) */
|
|
10
|
+
customLinkRegions?: string[];
|
|
11
|
+
/** Streaming link regions for music apps (iOS 18.2+) */
|
|
12
|
+
streamingLinkRegions?: string[];
|
|
13
|
+
/** Enable external purchase link entitlement */
|
|
14
|
+
enableExternalPurchaseLink?: boolean;
|
|
15
|
+
/** Enable external purchase link streaming entitlement (music apps only) */
|
|
16
|
+
enableExternalPurchaseLinkStreaming?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** Add external purchase entitlements and Info.plist configuration */
|
|
19
|
+
export declare const withIosAlternativeBilling: ConfigPlugin<IOSAlternativeBillingConfig | undefined>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.withIosAlternativeBilling = void 0;
|
|
4
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
5
|
+
// Log a message only once per Node process
|
|
6
|
+
const logOnce = (() => {
|
|
7
|
+
const printed = new Set();
|
|
8
|
+
return (msg) => {
|
|
9
|
+
if (!printed.has(msg)) {
|
|
10
|
+
console.log(msg);
|
|
11
|
+
printed.add(msg);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
})();
|
|
15
|
+
/** Add external purchase entitlements and Info.plist configuration */
|
|
16
|
+
const withIosAlternativeBilling = (config, options) => {
|
|
17
|
+
if (!options || !options.countries || options.countries.length === 0) {
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
// Add entitlements
|
|
21
|
+
config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
|
|
22
|
+
// Always add basic external purchase entitlement when countries are specified
|
|
23
|
+
config.modResults['com.apple.developer.storekit.external-purchase'] = true;
|
|
24
|
+
logOnce('โ
Added com.apple.developer.storekit.external-purchase to entitlements');
|
|
25
|
+
// Add external purchase link entitlement if enabled
|
|
26
|
+
if (options.enableExternalPurchaseLink) {
|
|
27
|
+
config.modResults['com.apple.developer.storekit.external-purchase-link'] =
|
|
28
|
+
true;
|
|
29
|
+
logOnce('โ
Added com.apple.developer.storekit.external-purchase-link to entitlements');
|
|
30
|
+
}
|
|
31
|
+
// Add streaming entitlement if enabled
|
|
32
|
+
if (options.enableExternalPurchaseLinkStreaming) {
|
|
33
|
+
config.modResults['com.apple.developer.storekit.external-purchase-link-streaming'] = true;
|
|
34
|
+
logOnce('โ
Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements');
|
|
35
|
+
}
|
|
36
|
+
return config;
|
|
37
|
+
});
|
|
38
|
+
// Add Info.plist configuration
|
|
39
|
+
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
40
|
+
const plist = config.modResults;
|
|
41
|
+
// 1. SKExternalPurchase (Required)
|
|
42
|
+
plist.SKExternalPurchase = options.countries;
|
|
43
|
+
logOnce(`โ
Added SKExternalPurchase with countries: ${options.countries?.join(', ')}`);
|
|
44
|
+
// 2. SKExternalPurchaseLink (Optional - iOS 15.4+)
|
|
45
|
+
if (options.links && Object.keys(options.links).length > 0) {
|
|
46
|
+
plist.SKExternalPurchaseLink = options.links;
|
|
47
|
+
logOnce(`โ
Added SKExternalPurchaseLink for ${Object.keys(options.links).length} countries`);
|
|
48
|
+
}
|
|
49
|
+
// 3. SKExternalPurchaseMultiLink (iOS 17.5+)
|
|
50
|
+
if (options.multiLinks && Object.keys(options.multiLinks).length > 0) {
|
|
51
|
+
plist.SKExternalPurchaseMultiLink = options.multiLinks;
|
|
52
|
+
logOnce(`โ
Added SKExternalPurchaseMultiLink for ${Object.keys(options.multiLinks).length} countries`);
|
|
53
|
+
}
|
|
54
|
+
// 4. SKExternalPurchaseCustomLinkRegions (iOS 18.1+)
|
|
55
|
+
if (options.customLinkRegions && options.customLinkRegions.length > 0) {
|
|
56
|
+
plist.SKExternalPurchaseCustomLinkRegions = options.customLinkRegions;
|
|
57
|
+
logOnce(`โ
Added SKExternalPurchaseCustomLinkRegions: ${options.customLinkRegions.join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
// 5. SKExternalPurchaseLinkStreamingRegions (iOS 18.2+)
|
|
60
|
+
if (options.streamingLinkRegions &&
|
|
61
|
+
options.streamingLinkRegions.length > 0) {
|
|
62
|
+
plist.SKExternalPurchaseLinkStreamingRegions =
|
|
63
|
+
options.streamingLinkRegions;
|
|
64
|
+
logOnce(`โ
Added SKExternalPurchaseLinkStreamingRegions: ${options.streamingLinkRegions.join(', ')}`);
|
|
65
|
+
}
|
|
66
|
+
return config;
|
|
67
|
+
});
|
|
68
|
+
return config;
|
|
69
|
+
};
|
|
70
|
+
exports.withIosAlternativeBilling = withIosAlternativeBilling;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
import type
|
|
2
|
+
import { type IOSAlternativeBillingConfig } from './withIosAlternativeBilling';
|
|
3
3
|
/**
|
|
4
4
|
* Plugin to add local OpenIAP pod dependency for development
|
|
5
5
|
* This is only for local development with openiap-apple library
|
|
@@ -11,5 +11,8 @@ type LocalPathOption = string | {
|
|
|
11
11
|
declare const withLocalOpenIAP: ConfigPlugin<{
|
|
12
12
|
localPath?: LocalPathOption;
|
|
13
13
|
iosAlternativeBilling?: IOSAlternativeBillingConfig;
|
|
14
|
+
horizonAppId?: string;
|
|
15
|
+
/** Resolved from modules.horizon by withIAP */
|
|
16
|
+
isHorizonEnabled?: boolean;
|
|
14
17
|
} | void>;
|
|
15
18
|
export default withLocalOpenIAP;
|
|
@@ -36,13 +36,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
const config_plugins_1 = require("expo/config-plugins");
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
+
const withIosAlternativeBilling_1 = require("./withIosAlternativeBilling");
|
|
40
|
+
// Log a message only once per Node process
|
|
41
|
+
const logOnce = (() => {
|
|
42
|
+
const printed = new Set();
|
|
43
|
+
return (msg) => {
|
|
44
|
+
if (!printed.has(msg)) {
|
|
45
|
+
console.log(msg);
|
|
46
|
+
printed.add(msg);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
39
50
|
const withLocalOpenIAP = (config, props) => {
|
|
40
51
|
// Import and apply iOS alternative billing configuration if provided
|
|
41
52
|
if (props?.iosAlternativeBilling) {
|
|
42
|
-
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
-
const { withIosAlternativeBilling } = require('./withIAP');
|
|
45
|
-
config = withIosAlternativeBilling(config, props.iosAlternativeBilling);
|
|
53
|
+
config = (0, withIosAlternativeBilling_1.withIosAlternativeBilling)(config, props.iosAlternativeBilling);
|
|
46
54
|
}
|
|
47
55
|
// Helper to resolve Android module path
|
|
48
56
|
const resolveAndroidModulePath = (p) => {
|
|
@@ -82,7 +90,7 @@ const withLocalOpenIAP = (config, props) => {
|
|
|
82
90
|
}
|
|
83
91
|
let podfileContent = fs.readFileSync(podfilePath, 'utf8');
|
|
84
92
|
if (podfileContent.includes("pod 'openiap',")) {
|
|
85
|
-
|
|
93
|
+
logOnce('โ
Local OpenIAP pod already configured');
|
|
86
94
|
return config;
|
|
87
95
|
}
|
|
88
96
|
const targetRegex = /target\s+['"][\w]+['"]\s+do\s*\n\s*use_expo_modules!/;
|
|
@@ -94,7 +102,7 @@ const withLocalOpenIAP = (config, props) => {
|
|
|
94
102
|
pod 'openiap', :path => '${iosPath}'`;
|
|
95
103
|
});
|
|
96
104
|
fs.writeFileSync(podfilePath, podfileContent);
|
|
97
|
-
|
|
105
|
+
logOnce(`โ
Added local OpenIAP pod at: ${iosPath}`);
|
|
98
106
|
}
|
|
99
107
|
else {
|
|
100
108
|
console.warn('โ ๏ธ Could not find target block in Podfile');
|
|
@@ -168,13 +176,13 @@ const withLocalOpenIAP = (config, props) => {
|
|
|
168
176
|
if (!contents.includes(projectDirLine))
|
|
169
177
|
contents += `${projectDirLine}\n`;
|
|
170
178
|
settings.contents = contents;
|
|
171
|
-
|
|
179
|
+
logOnce(`โ
Linked local Android module at: ${androidModulePath}`);
|
|
172
180
|
return config;
|
|
173
181
|
});
|
|
174
182
|
// 2) app/build.gradle: add implementation project(':openiap-google')
|
|
175
183
|
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
176
|
-
const raw = props?.localPath;
|
|
177
184
|
const projectRoot = config.modRequest.projectRoot;
|
|
185
|
+
const raw = props?.localPath;
|
|
178
186
|
const androidInput = typeof raw === 'string' ? undefined : raw?.android;
|
|
179
187
|
const androidModulePath = resolveAndroidModulePath(androidInput) ||
|
|
180
188
|
resolveAndroidModulePath(path.resolve(projectRoot, 'openiap-google')) ||
|
|
@@ -184,72 +192,65 @@ const withLocalOpenIAP = (config, props) => {
|
|
|
184
192
|
}
|
|
185
193
|
const gradle = config.modResults;
|
|
186
194
|
const dependencyLine = ` implementation project(':openiap-google')`;
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
// Groovy DSL: implementation "io.github.hyochan.openiap:openiap-google:x.y.z" or api "..."
|
|
190
|
-
/^\s*(?:implementation|api)\s+["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*$/gm,
|
|
191
|
-
// Kotlin DSL: implementation("io.github.hyochan.openiap:openiap-google:x.y.z") or api("...")
|
|
192
|
-
/^\s*(?:implementation|api)\s*\(\s*["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*\)\s*$/gm,
|
|
193
|
-
];
|
|
195
|
+
const flavor = props?.isHorizonEnabled ? 'horizon' : 'play';
|
|
196
|
+
const strategyLine = ` missingDimensionStrategy "platform", "${flavor}"`;
|
|
194
197
|
let contents = gradle.contents;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
198
|
+
// Remove Maven deps (avoid duplicate classes with local module)
|
|
199
|
+
const mavenPattern = /^\s*(?:implementation|api)\s*\(?\s*["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*\)?\s*$/gm;
|
|
200
|
+
if (mavenPattern.test(contents)) {
|
|
201
|
+
contents = contents.replace(mavenPattern, '\n');
|
|
202
|
+
logOnce('๐งน Removed Maven openiap-google (using local module)');
|
|
201
203
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
// Add missingDimensionStrategy (required for flavored module)
|
|
205
|
+
// Remove any existing platform strategies first to avoid duplicates
|
|
206
|
+
const strategyPattern = /^\s*missingDimensionStrategy\s*\(?\s*["']platform["']\s*,\s*["'](play|horizon)["']\s*\)?\s*$/gm;
|
|
207
|
+
if (strategyPattern.test(contents)) {
|
|
208
|
+
contents = contents.replace(strategyPattern, '');
|
|
209
|
+
logOnce('๐งน Removed existing missingDimensionStrategy for platform');
|
|
205
210
|
}
|
|
206
|
-
if (!
|
|
211
|
+
if (!contents.includes(strategyLine)) {
|
|
212
|
+
const lines = contents.split('\n');
|
|
213
|
+
const idx = lines.findIndex((line) => line.match(/defaultConfig\s*\{/));
|
|
214
|
+
if (idx !== -1) {
|
|
215
|
+
lines.splice(idx + 1, 0, strategyLine);
|
|
216
|
+
contents = lines.join('\n');
|
|
217
|
+
logOnce(`๐ ๏ธ expo-iap: Added missingDimensionStrategy for ${flavor} flavor`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Add project dependency
|
|
221
|
+
if (!contents.includes(dependencyLine)) {
|
|
207
222
|
const anchor = /dependencies\s*\{/m;
|
|
208
|
-
if (anchor.test(
|
|
209
|
-
|
|
223
|
+
if (anchor.test(contents)) {
|
|
224
|
+
contents = contents.replace(anchor, (m) => `${m}\n${dependencyLine}`);
|
|
210
225
|
}
|
|
211
226
|
else {
|
|
212
|
-
|
|
227
|
+
contents += `\n\ndependencies {\n${dependencyLine}\n}\n`;
|
|
213
228
|
}
|
|
214
|
-
|
|
229
|
+
logOnce('๐ ๏ธ Added dependency on local :openiap-google project');
|
|
215
230
|
}
|
|
231
|
+
gradle.contents = contents;
|
|
216
232
|
return config;
|
|
217
233
|
});
|
|
218
|
-
// 3)
|
|
234
|
+
// 3) Set horizonEnabled in gradle.properties
|
|
219
235
|
config = (0, config_plugins_1.withDangerousMod)(config, [
|
|
220
236
|
'android',
|
|
221
237
|
async (config) => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
for (const p of patterns) {
|
|
235
|
-
if (p.test(contents)) {
|
|
236
|
-
contents = contents.replace(p, '\n');
|
|
237
|
-
changed = true;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
if (changed) {
|
|
241
|
-
fs.writeFileSync(appBuildGradle, contents);
|
|
242
|
-
console.log('๐งน expo-iap: Cleaned Maven openiap-google for local :openiap-google');
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
catch (e) {
|
|
247
|
-
console.warn('expo-iap: cleanup step failed:', e);
|
|
238
|
+
const { platformProjectRoot } = config.modRequest;
|
|
239
|
+
const gradlePropertiesPath = path.join(platformProjectRoot, 'gradle.properties');
|
|
240
|
+
if (fs.existsSync(gradlePropertiesPath)) {
|
|
241
|
+
let contents = fs.readFileSync(gradlePropertiesPath, 'utf8');
|
|
242
|
+
const isHorizon = props?.isHorizonEnabled ?? false;
|
|
243
|
+
// Update horizonEnabled property
|
|
244
|
+
contents = contents.replace(/^horizonEnabled=.*$/gm, '');
|
|
245
|
+
if (!contents.endsWith('\n'))
|
|
246
|
+
contents += '\n';
|
|
247
|
+
contents += `horizonEnabled=${isHorizon}\n`;
|
|
248
|
+
fs.writeFileSync(gradlePropertiesPath, contents);
|
|
249
|
+
logOnce(`๐ ๏ธ expo-iap: Set horizonEnabled=${isHorizon} in gradle.properties`);
|
|
248
250
|
}
|
|
249
251
|
return config;
|
|
250
252
|
},
|
|
251
253
|
]);
|
|
252
|
-
// (removed) Avoid global root build.gradle mutations; included module should manage its plugins
|
|
253
254
|
return config;
|
|
254
255
|
};
|
|
255
256
|
exports.default = withLocalOpenIAP;
|