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.
@@ -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
- return config;
118
- });
119
- return config;
120
- };
121
- /** Add external purchase entitlements and Info.plist configuration */
122
- const withIosAlternativeBilling = (config, options) => {
123
- if (!options || !options.countries || options.countries.length === 0) {
124
- return config;
125
- }
126
- // Add entitlements
127
- config = (0, config_plugins_1.withEntitlementsPlist)(config, (config) => {
128
- // Always add basic external purchase entitlement when countries are specified
129
- config.modResults['com.apple.developer.storekit.external-purchase'] = true;
130
- logOnce('โœ… Added com.apple.developer.storekit.external-purchase to entitlements');
131
- // Add external purchase link entitlement if enabled
132
- if (options.enableExternalPurchaseLink) {
133
- config.modResults['com.apple.developer.storekit.external-purchase-link'] =
134
- true;
135
- logOnce('โœ… Added com.apple.developer.storekit.external-purchase-link to entitlements');
136
- }
137
- // Add streaming entitlement if enabled
138
- if (options.enableExternalPurchaseLinkStreaming) {
139
- config.modResults['com.apple.developer.storekit.external-purchase-link-streaming'] = true;
140
- logOnce('โœ… Added com.apple.developer.storekit.external-purchase-link-streaming to entitlements');
141
- }
142
- return config;
143
- });
144
- // Add Info.plist configuration
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, { addDeps: !isLocalDev });
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: options?.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, options?.iosAlternativeBilling);
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 { IOSAlternativeBillingConfig } from './withIAP';
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
- // Import withIosAlternativeBilling from withIAP module
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
- console.log('โœ… Local OpenIAP pod already configured');
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
- console.log(`โœ… Added local OpenIAP pod at: ${iosPath}`);
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
- console.log(`โœ… Linked local Android module at: ${androidModulePath}`);
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
- // Remove any previously added Maven deps for openiap-google to avoid duplicate classes
188
- const removalPatterns = [
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
- let removedAny = false;
196
- for (const pattern of removalPatterns) {
197
- if (pattern.test(contents)) {
198
- contents = contents.replace(pattern, '\n');
199
- removedAny = true;
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
- if (removedAny) {
203
- gradle.contents = contents;
204
- console.log('๐Ÿงน Removed Maven openiap-google to use local :openiap-google');
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 (!gradle.contents.includes(dependencyLine)) {
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(gradle.contents)) {
209
- gradle.contents = gradle.contents.replace(anchor, (m) => `${m}\n${dependencyLine}`);
223
+ if (anchor.test(contents)) {
224
+ contents = contents.replace(anchor, (m) => `${m}\n${dependencyLine}`);
210
225
  }
211
226
  else {
212
- gradle.contents += `\n\ndependencies {\n${dependencyLine}\n}\n`;
227
+ contents += `\n\ndependencies {\n${dependencyLine}\n}\n`;
213
228
  }
214
- console.log('๐Ÿ› ๏ธ Added dependency on local :openiap-google project');
229
+ logOnce('๐Ÿ› ๏ธ Added dependency on local :openiap-google project');
215
230
  }
231
+ gradle.contents = contents;
216
232
  return config;
217
233
  });
218
- // 3) Ensure final cleanup in app/build.gradle after all mods are applied
234
+ // 3) Set horizonEnabled in gradle.properties
219
235
  config = (0, config_plugins_1.withDangerousMod)(config, [
220
236
  'android',
221
237
  async (config) => {
222
- try {
223
- const { platformProjectRoot } = config.modRequest;
224
- const appBuildGradle = path.join(platformProjectRoot, 'app', 'build.gradle');
225
- if (fs.existsSync(appBuildGradle)) {
226
- let contents = fs.readFileSync(appBuildGradle, 'utf8');
227
- const patterns = [
228
- // Groovy DSL
229
- /^\s*(?:implementation|api)\s+["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*$/gm,
230
- // Kotlin DSL
231
- /^\s*(?:implementation|api)\s*\(\s*["']io\.github\.hyochan\.openiap:openiap-google:[^"']+["']\s*\)\s*$/gm,
232
- ];
233
- let changed = false;
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;