react-native-iap 15.3.6 → 15.4.0-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 (81) hide show
  1. package/README.md +1 -1
  2. package/android/build.gradle +13 -5
  3. package/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt +16 -3
  4. package/ios/HybridRnIap.swift +12 -0
  5. package/lib/module/index.js +56 -16
  6. package/lib/module/index.js.map +1 -1
  7. package/lib/module/index.kepler.js +170 -0
  8. package/lib/module/index.kepler.js.map +1 -0
  9. package/lib/module/types/amazon-devices-kepler/index.d.js +2 -0
  10. package/lib/module/types/amazon-devices-kepler/index.d.js.map +1 -0
  11. package/lib/module/types.js +5 -2
  12. package/lib/module/types.js.map +1 -1
  13. package/lib/module/utils/error.js +2 -2
  14. package/lib/module/utils/error.js.map +1 -1
  15. package/lib/module/utils/type-bridge.js +4 -1
  16. package/lib/module/utils/type-bridge.js.map +1 -1
  17. package/lib/module/vega-adapter.js +981 -0
  18. package/lib/module/vega-adapter.js.map +1 -0
  19. package/lib/module/vega.js +10 -0
  20. package/lib/module/vega.js.map +1 -0
  21. package/lib/module/vega.kepler.js +18 -0
  22. package/lib/module/vega.kepler.js.map +1 -0
  23. package/lib/typescript/plugin/src/withIAP.d.ts +33 -0
  24. package/lib/typescript/plugin/src/withIAP.d.ts.map +1 -1
  25. package/lib/typescript/src/index.d.ts +9 -2
  26. package/lib/typescript/src/index.d.ts.map +1 -1
  27. package/lib/typescript/src/index.kepler.d.ts +55 -0
  28. package/lib/typescript/src/index.kepler.d.ts.map +1 -0
  29. package/lib/typescript/src/specs/RnIap.nitro.d.ts +10 -1
  30. package/lib/typescript/src/specs/RnIap.nitro.d.ts.map +1 -1
  31. package/lib/typescript/src/types.d.ts +16 -3
  32. package/lib/typescript/src/types.d.ts.map +1 -1
  33. package/lib/typescript/src/utils/type-bridge.d.ts.map +1 -1
  34. package/lib/typescript/src/vega-adapter.d.ts +76 -0
  35. package/lib/typescript/src/vega-adapter.d.ts.map +1 -0
  36. package/lib/typescript/src/vega.d.ts +4 -0
  37. package/lib/typescript/src/vega.d.ts.map +1 -0
  38. package/lib/typescript/src/vega.kepler.d.ts +4 -0
  39. package/lib/typescript/src/vega.kepler.d.ts.map +1 -0
  40. package/nitrogen/generated/android/NitroIap+autolinking.cmake +1 -0
  41. package/nitrogen/generated/android/c++/JHybridRnIapSpec.cpp +5 -0
  42. package/nitrogen/generated/android/c++/JIapStore.hpp +3 -0
  43. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitAmazonProps.hpp +71 -0
  44. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithIapkitProps.hpp +9 -1
  45. package/nitrogen/generated/android/c++/JNitroVerifyPurchaseWithProviderProps.hpp +4 -0
  46. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.cpp +26 -0
  47. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.hpp +75 -0
  48. package/nitrogen/generated/android/c++/JVariant_NullType_NitroVerifyPurchaseWithIapkitProps.hpp +4 -0
  49. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/IapStore.kt +2 -1
  50. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitAmazonProps.kt +44 -0
  51. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/NitroVerifyPurchaseWithIapkitProps.kt +5 -2
  52. package/nitrogen/generated/android/kotlin/com/margelo/nitro/iap/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.kt +53 -0
  53. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Bridge.hpp +47 -0
  54. package/nitrogen/generated/ios/NitroIap-Swift-Cxx-Umbrella.hpp +3 -0
  55. package/nitrogen/generated/ios/c++/HybridRnIapSpecSwift.hpp +3 -0
  56. package/nitrogen/generated/ios/swift/IapStore.swift +4 -0
  57. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitAmazonProps.swift +103 -0
  58. package/nitrogen/generated/ios/swift/NitroVerifyPurchaseWithIapkitProps.swift +38 -1
  59. package/nitrogen/generated/ios/swift/Variant_NullType_NitroVerifyPurchaseWithIapkitAmazonProps.swift +18 -0
  60. package/nitrogen/generated/shared/c++/IapStore.hpp +4 -0
  61. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitAmazonProps.hpp +94 -0
  62. package/nitrogen/generated/shared/c++/NitroVerifyPurchaseWithIapkitProps.hpp +8 -1
  63. package/openiap-versions.json +1 -1
  64. package/package.json +17 -4
  65. package/plugin/build/withIAP.d.ts +33 -0
  66. package/plugin/build/withIAP.js +109 -42
  67. package/plugin/src/withIAP.ts +185 -55
  68. package/src/index.kepler.ts +311 -0
  69. package/src/index.ts +65 -19
  70. package/src/specs/RnIap.nitro.ts +13 -2
  71. package/src/types/amazon-devices-kepler/index.d.ts +14 -0
  72. package/src/types.ts +17 -3
  73. package/src/utils/error.ts +2 -2
  74. package/src/utils/type-bridge.ts +4 -1
  75. package/src/vega-adapter.ts +1503 -0
  76. package/src/vega.kepler.ts +21 -0
  77. package/src/vega.ts +10 -0
  78. package/android/src/test/java/com/margelo/nitro/iap/ListenerThreadSafetyTest.kt +0 -154
  79. package/android/src/test/java/com/margelo/nitro/iap/OpenIapExceptionTest.kt +0 -93
  80. package/android/src/test/java/com/margelo/nitro/iap/ProductQueryHelpersTest.kt +0 -140
  81. package/plugin/tsconfig.tsbuildinfo +0 -1
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "react-native-iap",
3
- "version": "15.3.6",
3
+ "version": "15.4.0-rc.1",
4
4
  "description": "React Native In-App Purchases module for iOS and Android using Nitro",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "source": "./src/index.ts",
10
+ "react-native": "./src/index.ts",
10
11
  "types": "./lib/typescript/src/index.d.ts",
11
- "default": "./lib/module/index.js",
12
- "react-native": "./src/index.ts"
12
+ "default": "./lib/module/index.js"
13
13
  },
14
14
  "./plugin": "./app.plugin.js",
15
15
  "./app.plugin.js": "./app.plugin.js",
@@ -34,9 +34,12 @@
34
34
  "!android/gradlew",
35
35
  "!android/gradlew.bat",
36
36
  "!android/local.properties",
37
+ "!android/src/androidTest",
38
+ "!android/src/test",
37
39
  "!**/__tests__",
38
40
  "!**/__fixtures__",
39
41
  "!**/__mocks__",
42
+ "!**/*.tsbuildinfo",
40
43
  "!**/.*"
41
44
  ],
42
45
  "scripts": {
@@ -59,7 +62,7 @@
59
62
  "test": "jest --coverage",
60
63
  "test:library": "jest --coverage",
61
64
  "test:example": "yarn workspace rn-iap-example test --coverage",
62
- "test:plugin": "jest plugin --coverage",
65
+ "test:plugin": "jest plugin/__tests__ --runInBand --coverage",
63
66
  "test:all": "yarn test:library && yarn test:example",
64
67
  "test:ci": "jest --maxWorkers=2 --coverage",
65
68
  "test:ci:example": "yarn workspace rn-iap-example test --coverage",
@@ -118,10 +121,20 @@
118
121
  "typescript": "^5.9.2"
119
122
  },
120
123
  "peerDependencies": {
124
+ "@amazon-devices/keplerscript-appstore-iap-lib": "~2.12.13",
125
+ "@amazon-devices/package-manager-lib": "~1.0.1767254401",
121
126
  "react": "*",
122
127
  "react-native": "*",
123
128
  "react-native-nitro-modules": "^0.35.0"
124
129
  },
130
+ "peerDependenciesMeta": {
131
+ "@amazon-devices/keplerscript-appstore-iap-lib": {
132
+ "optional": true
133
+ },
134
+ "@amazon-devices/package-manager-lib": {
135
+ "optional": true
136
+ }
137
+ },
125
138
  "workspaces": [
126
139
  "example"
127
140
  ],
@@ -37,6 +37,39 @@ type IapPluginProps = {
37
37
  * Get your project key from https://kit.openiap.dev
38
38
  */
39
39
  iapkitApiKey?: string;
40
+ /**
41
+ * Amazon platform targets.
42
+ *
43
+ * Fire OS selects the Android Amazon Appstore flavor. Vega OS is selected by
44
+ * the Kepler runtime and does not change the Android Gradle flavor.
45
+ */
46
+ amazon?: {
47
+ /**
48
+ * Fire OS module for Amazon-distributed Android builds.
49
+ * @platform android
50
+ */
51
+ fireOS?: boolean;
52
+ /**
53
+ * Vega OS runtime target. This is not an Android flavor.
54
+ */
55
+ vegaOS?: boolean;
56
+ };
57
+ /**
58
+ * Optional non-Amazon Android store modules.
59
+ * Fire OS takes precedence when both Fire OS and Horizon are enabled.
60
+ */
61
+ modules?: {
62
+ /**
63
+ * Horizon module for Meta Quest/VR devices.
64
+ * @platform android
65
+ */
66
+ horizon?: boolean;
67
+ };
68
+ };
69
+ export declare function resolveAmazonPlatformFlags(props?: IapPluginProps): {
70
+ isFireOsEnabled: boolean;
71
+ isVegaEnabled: boolean;
72
+ isHorizonEnabled: boolean;
40
73
  };
41
74
  type IapPluginCallable = {
42
75
  (config: ExpoConfig): ExpoConfig;
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = exports.withIosAlternativeBilling = exports.modifyProjectBuildGradle = void 0;
4
+ exports.resolveAmazonPlatformFlags = resolveAmazonPlatformFlags;
4
5
  const config_plugins_1 = require("expo/config-plugins");
5
- const node_fs_1 = require("node:fs");
6
- const node_path_1 = require("node:path");
7
6
  const pkg = require('../../package.json');
8
7
  // Global flag to prevent duplicate logs
9
8
  let hasLoggedPluginExecution = false;
@@ -32,35 +31,28 @@ const modifyProjectBuildGradle = (gradle) => {
32
31
  return gradle;
33
32
  };
34
33
  exports.modifyProjectBuildGradle = modifyProjectBuildGradle;
35
- const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
36
- function loadOpenIapConfig() {
37
- const versionsPath = (0, node_path_1.resolve)(__dirname, '../../openiap-versions.json');
38
- try {
39
- const raw = (0, node_fs_1.readFileSync)(versionsPath, 'utf8');
40
- const parsed = JSON.parse(raw);
41
- const googleVersion = typeof parsed?.google === 'string' ? parsed.google.trim() : '';
42
- if (!googleVersion) {
43
- throw new Error('react-native-iap: "google" version missing or invalid in openiap-versions.json');
34
+ const OPENIAP_GROUP = 'io.github.hyochan.openiap';
35
+ const modifyAppBuildGradle = (gradle, isHorizonEnabled, isFireOsEnabled) => {
36
+ const escapeRegExp = (value) => {
37
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
38
+ };
39
+ function loadOpenIapVersion() {
40
+ try {
41
+ const parsed = require('../../openiap-versions.json');
42
+ const googleVersion = typeof parsed?.google === 'string' ? parsed.google.trim() : '';
43
+ if (!googleVersion) {
44
+ throw new Error('react-native-iap: "google" version missing or invalid in openiap-versions.json');
45
+ }
46
+ return googleVersion;
47
+ }
48
+ catch (error) {
49
+ throw new Error(`react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`);
44
50
  }
45
- return { google: googleVersion };
46
- }
47
- catch (error) {
48
- throw new Error(`react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`);
49
- }
50
- }
51
- let cachedOpenIapVersion = null;
52
- const getOpenIapVersion = () => {
53
- if (cachedOpenIapVersion) {
54
- return cachedOpenIapVersion;
55
51
  }
56
- cachedOpenIapVersion = loadOpenIapConfig().google;
57
- return cachedOpenIapVersion;
58
- };
59
- const modifyAppBuildGradle = (gradle) => {
60
52
  let modified = gradle;
61
53
  let openiapVersion;
62
54
  try {
63
- openiapVersion = getOpenIapVersion();
55
+ openiapVersion = loadOpenIapVersion();
64
56
  }
65
57
  catch (error) {
66
58
  config_plugins_1.WarningAggregator.addWarningAndroid('react-native-iap', `react-native-iap: Failed to resolve OpenIAP version (${error instanceof Error ? error.message : error})`);
@@ -72,8 +64,26 @@ const modifyAppBuildGradle = (gradle) => {
72
64
  .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.android\.billingclient:billing-ktx:[^"']+["'][ \t]*$/gim, '')
73
65
  .replace(/^[ \t]*(implementation|api)[ \t]+["']com\.google\.android\.gms:play-services-base:[^"']+["'][ \t]*$/gim, '')
74
66
  .replace(/\n{3,}/g, '\n\n');
75
- const openiapDep = ` implementation "${OPENIAP_COORD}:${openiapVersion}"`;
76
- if (!modified.includes(OPENIAP_COORD)) {
67
+ const flavor = isFireOsEnabled
68
+ ? 'amazon'
69
+ : isHorizonEnabled
70
+ ? 'horizon'
71
+ : 'play';
72
+ const artifactId = isFireOsEnabled
73
+ ? 'openiap-google-amazon'
74
+ : isHorizonEnabled
75
+ ? 'openiap-google-horizon'
76
+ : 'openiap-google';
77
+ const openiapCoord = `${OPENIAP_GROUP}:${artifactId}`;
78
+ const openiapDep = ` implementation "${openiapCoord}:${openiapVersion}"`;
79
+ const desiredOpeniapLine = new RegExp(`^[ \\t]*(?:implementation|api)[ \\t]+\\(?["']${escapeRegExp(`${openiapCoord}:${openiapVersion}`)}["']\\)?[ \\t]*$`, 'm');
80
+ const openiapAnyLine = /^[ \t]*(implementation|api)[ \t]+\(?["']io\.github\.hyochan\.openiap:openiap-google(?:-(?:horizon|amazon))?:[^"']+["']\)?[ \t]*$/gim;
81
+ const withoutExistingOpeniap = modified.replace(openiapAnyLine, '');
82
+ const hadExistingOpeniap = withoutExistingOpeniap !== modified;
83
+ if (hadExistingOpeniap) {
84
+ modified = withoutExistingOpeniap.replace(/\n{3,}/g, '\n\n');
85
+ }
86
+ if (!desiredOpeniapLine.test(modified)) {
77
87
  if (!/dependencies\s*{/.test(modified)) {
78
88
  modified += `\n\ndependencies {\n${openiapDep}\n}\n`;
79
89
  }
@@ -84,31 +94,71 @@ const modifyAppBuildGradle = (gradle) => {
84
94
  console.log(`🛠️ react-native-iap: Added OpenIAP (${openiapVersion}) to build.gradle`);
85
95
  }
86
96
  }
97
+ // Remove stale OpenIAP platform strategies even when returning to the default
98
+ // Play artifact. Otherwise a previous Fire OS/Horizon prebuild can keep
99
+ // selecting the wrong local flavor.
100
+ const strategyPattern = /^[ \t]*missingDimensionStrategy[ \t]*\(?[ \t]*["']platform["'][ \t]*,[ \t]*["'](play|horizon|amazon)["'][ \t]*\)?[ \t]*$/gm;
101
+ const withoutExistingStrategy = modified.replace(strategyPattern, '');
102
+ if (withoutExistingStrategy !== modified) {
103
+ modified = withoutExistingStrategy;
104
+ }
105
+ const defaultConfigRegex = /defaultConfig\s*{/;
106
+ if (defaultConfigRegex.test(modified)) {
107
+ const strategyLine = ` missingDimensionStrategy "platform", "${flavor}"`;
108
+ if (!/missingDimensionStrategy.*platform/.test(modified)) {
109
+ modified = addLineToGradle(modified, defaultConfigRegex, strategyLine);
110
+ }
111
+ }
87
112
  return modified;
88
113
  };
89
114
  const withIapAndroid = (config, props) => {
90
115
  // Add OpenIAP dependency to app build.gradle
91
116
  config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
92
- config.modResults.contents = modifyAppBuildGradle(config.modResults.contents);
117
+ config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, props?.isHorizonEnabled, props?.isFireOsEnabled);
118
+ return config;
119
+ });
120
+ config = (0, config_plugins_1.withGradleProperties)(config, (config) => {
121
+ const horizonValue = props?.isHorizonEnabled ?? false;
122
+ const fireOsValue = props?.isFireOsEnabled ?? false;
123
+ config.modResults = config.modResults.filter((item) => item.type !== 'property' ||
124
+ !['horizonEnabled', 'fireOsEnabled'].includes(item.key));
125
+ config.modResults.push({
126
+ type: 'property',
127
+ key: 'horizonEnabled',
128
+ value: String(horizonValue),
129
+ });
130
+ config.modResults.push({
131
+ type: 'property',
132
+ key: 'fireOsEnabled',
133
+ value: String(fireOsValue),
134
+ });
93
135
  return config;
94
136
  });
95
137
  config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
96
138
  const manifest = config.modResults;
97
- if (!manifest.manifest['uses-permission']) {
98
- manifest.manifest['uses-permission'] = [];
99
- }
100
- const permissions = manifest.manifest['uses-permission'];
139
+ const existingPermissions = manifest.manifest['uses-permission'];
140
+ const permissions = Array.isArray(existingPermissions)
141
+ ? existingPermissions
142
+ : existingPermissions
143
+ ? [existingPermissions]
144
+ : [];
145
+ manifest.manifest['uses-permission'] = permissions;
101
146
  const billingPerm = { $: { 'android:name': 'com.android.vending.BILLING' } };
102
- const alreadyExists = permissions.some((p) => p.$['android:name'] === 'com.android.vending.BILLING');
103
- if (!alreadyExists) {
104
- permissions.push(billingPerm);
105
- if (!hasLoggedPluginExecution) {
106
- console.log('✅ Added com.android.vending.BILLING to AndroidManifest.xml');
107
- }
147
+ if (props?.isFireOsEnabled) {
148
+ manifest.manifest['uses-permission'] = permissions.filter((p) => p.$['android:name'] !== 'com.android.vending.BILLING');
108
149
  }
109
150
  else {
110
- if (!hasLoggedPluginExecution) {
111
- console.log('ℹ️ com.android.vending.BILLING already exists in AndroidManifest.xml');
151
+ const alreadyExists = permissions.some((p) => p.$['android:name'] === 'com.android.vending.BILLING');
152
+ if (!alreadyExists) {
153
+ permissions.push(billingPerm);
154
+ if (!hasLoggedPluginExecution) {
155
+ console.log('✅ Added com.android.vending.BILLING to AndroidManifest.xml');
156
+ }
157
+ }
158
+ else {
159
+ if (!hasLoggedPluginExecution) {
160
+ console.log('ℹ️ com.android.vending.BILLING already exists in AndroidManifest.xml');
161
+ }
112
162
  }
113
163
  }
114
164
  // Add IAPKit API key as meta-data if provided
@@ -224,6 +274,18 @@ const withIosAlternativeBilling = (config, options) => {
224
274
  return config;
225
275
  };
226
276
  exports.withIosAlternativeBilling = withIosAlternativeBilling;
277
+ function resolveAmazonPlatformFlags(props) {
278
+ const isFireOsEnabled = props?.amazon?.fireOS ?? false;
279
+ const isVegaEnabled = props?.amazon?.vegaOS ?? false;
280
+ const isHorizonEnabled = isFireOsEnabled
281
+ ? false
282
+ : (props?.modules?.horizon ?? false);
283
+ return {
284
+ isFireOsEnabled,
285
+ isVegaEnabled,
286
+ isHorizonEnabled,
287
+ };
288
+ }
227
289
  const withIapIosFollyWorkaround = (config, props) => {
228
290
  const newKey = props?.ios?.['with-folly-no-coroutines'];
229
291
  const oldKey = props?.ios?.['with-folly-no-couroutines'];
@@ -289,7 +351,12 @@ const withIapkitApiKeyIOS = (config, apiKey) => {
289
351
  };
290
352
  const withIAP = (config, props) => {
291
353
  try {
292
- let result = withIapAndroid(config, { iapkitApiKey: props?.iapkitApiKey });
354
+ const { isFireOsEnabled, isHorizonEnabled } = resolveAmazonPlatformFlags(props);
355
+ let result = withIapAndroid(config, {
356
+ iapkitApiKey: props?.iapkitApiKey,
357
+ isHorizonEnabled,
358
+ isFireOsEnabled,
359
+ });
293
360
  result = withIapIosFollyWorkaround(result, props);
294
361
  // Add iOS alternative billing configuration if provided
295
362
  if (props?.iosAlternativeBilling) {
@@ -3,14 +3,13 @@ import {
3
3
  WarningAggregator,
4
4
  withAndroidManifest,
5
5
  withAppBuildGradle,
6
+ withGradleProperties,
6
7
  withPodfile,
7
8
  withEntitlementsPlist,
8
9
  withInfoPlist,
9
10
  } from 'expo/config-plugins';
10
11
  import type {ConfigPlugin} from 'expo/config-plugins';
11
12
  import type {ExpoConfig} from '@expo/config-types';
12
- import {readFileSync} from 'node:fs';
13
- import {resolve as resolvePath} from 'node:path';
14
13
 
15
14
  const pkg = require('../../package.json');
16
15
 
@@ -49,43 +48,40 @@ export const modifyProjectBuildGradle = (gradle: string): string => {
49
48
  return gradle;
50
49
  };
51
50
 
52
- const OPENIAP_COORD = 'io.github.hyochan.openiap:openiap-google';
51
+ const OPENIAP_GROUP = 'io.github.hyochan.openiap';
53
52
 
54
- function loadOpenIapConfig(): {google: string} {
55
- const versionsPath = resolvePath(__dirname, '../../openiap-versions.json');
56
- try {
57
- const raw = readFileSync(versionsPath, 'utf8');
58
- const parsed = JSON.parse(raw);
59
- const googleVersion =
60
- typeof parsed?.google === 'string' ? parsed.google.trim() : '';
61
- if (!googleVersion) {
53
+ const modifyAppBuildGradle = (
54
+ gradle: string,
55
+ isHorizonEnabled?: boolean,
56
+ isFireOsEnabled?: boolean,
57
+ ): string => {
58
+ const escapeRegExp = (value: string): string => {
59
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
+ };
61
+
62
+ function loadOpenIapVersion(): string {
63
+ try {
64
+ const parsed = require('../../openiap-versions.json');
65
+ const googleVersion =
66
+ typeof parsed?.google === 'string' ? parsed.google.trim() : '';
67
+ if (!googleVersion) {
68
+ throw new Error(
69
+ 'react-native-iap: "google" version missing or invalid in openiap-versions.json',
70
+ );
71
+ }
72
+ return googleVersion;
73
+ } catch (error) {
62
74
  throw new Error(
63
- 'react-native-iap: "google" version missing or invalid in openiap-versions.json',
75
+ `react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`,
64
76
  );
65
77
  }
66
- return {google: googleVersion};
67
- } catch (error) {
68
- throw new Error(
69
- `react-native-iap: Unable to load openiap-versions.json (${error instanceof Error ? error.message : error})`,
70
- );
71
- }
72
- }
73
-
74
- let cachedOpenIapVersion: string | null = null;
75
- const getOpenIapVersion = (): string => {
76
- if (cachedOpenIapVersion) {
77
- return cachedOpenIapVersion;
78
78
  }
79
- cachedOpenIapVersion = loadOpenIapConfig().google;
80
- return cachedOpenIapVersion;
81
- };
82
79
 
83
- const modifyAppBuildGradle = (gradle: string): string => {
84
80
  let modified = gradle;
85
81
 
86
82
  let openiapVersion: string;
87
83
  try {
88
- openiapVersion = getOpenIapVersion();
84
+ openiapVersion = loadOpenIapVersion();
89
85
  } catch (error) {
90
86
  WarningAggregator.addWarningAndroid(
91
87
  'react-native-iap',
@@ -107,9 +103,34 @@ const modifyAppBuildGradle = (gradle: string): string => {
107
103
  )
108
104
  .replace(/\n{3,}/g, '\n\n');
109
105
 
110
- const openiapDep = ` implementation "${OPENIAP_COORD}:${openiapVersion}"`;
106
+ const flavor = isFireOsEnabled
107
+ ? 'amazon'
108
+ : isHorizonEnabled
109
+ ? 'horizon'
110
+ : 'play';
111
+ const artifactId = isFireOsEnabled
112
+ ? 'openiap-google-amazon'
113
+ : isHorizonEnabled
114
+ ? 'openiap-google-horizon'
115
+ : 'openiap-google';
116
+ const openiapCoord = `${OPENIAP_GROUP}:${artifactId}`;
117
+ const openiapDep = ` implementation "${openiapCoord}:${openiapVersion}"`;
118
+ const desiredOpeniapLine = new RegExp(
119
+ `^[ \\t]*(?:implementation|api)[ \\t]+\\(?["']${escapeRegExp(
120
+ `${openiapCoord}:${openiapVersion}`,
121
+ )}["']\\)?[ \\t]*$`,
122
+ 'm',
123
+ );
124
+
125
+ const openiapAnyLine =
126
+ /^[ \t]*(implementation|api)[ \t]+\(?["']io\.github\.hyochan\.openiap:openiap-google(?:-(?:horizon|amazon))?:[^"']+["']\)?[ \t]*$/gim;
127
+ const withoutExistingOpeniap = modified.replace(openiapAnyLine, '');
128
+ const hadExistingOpeniap = withoutExistingOpeniap !== modified;
129
+ if (hadExistingOpeniap) {
130
+ modified = withoutExistingOpeniap.replace(/\n{3,}/g, '\n\n');
131
+ }
111
132
 
112
- if (!modified.includes(OPENIAP_COORD)) {
133
+ if (!desiredOpeniapLine.test(modified)) {
113
134
  if (!/dependencies\s*{/.test(modified)) {
114
135
  modified += `\n\ndependencies {\n${openiapDep}\n}\n`;
115
136
  } else {
@@ -122,45 +143,101 @@ const modifyAppBuildGradle = (gradle: string): string => {
122
143
  }
123
144
  }
124
145
 
146
+ // Remove stale OpenIAP platform strategies even when returning to the default
147
+ // Play artifact. Otherwise a previous Fire OS/Horizon prebuild can keep
148
+ // selecting the wrong local flavor.
149
+ const strategyPattern =
150
+ /^[ \t]*missingDimensionStrategy[ \t]*\(?[ \t]*["']platform["'][ \t]*,[ \t]*["'](play|horizon|amazon)["'][ \t]*\)?[ \t]*$/gm;
151
+ const withoutExistingStrategy = modified.replace(strategyPattern, '');
152
+ if (withoutExistingStrategy !== modified) {
153
+ modified = withoutExistingStrategy;
154
+ }
155
+
156
+ const defaultConfigRegex = /defaultConfig\s*{/;
157
+ if (defaultConfigRegex.test(modified)) {
158
+ const strategyLine = ` missingDimensionStrategy "platform", "${flavor}"`;
159
+ if (!/missingDimensionStrategy.*platform/.test(modified)) {
160
+ modified = addLineToGradle(modified, defaultConfigRegex, strategyLine);
161
+ }
162
+ }
163
+
125
164
  return modified;
126
165
  };
127
166
 
128
- const withIapAndroid: ConfigPlugin<{iapkitApiKey?: string} | undefined> = (
129
- config,
130
- props,
131
- ) => {
167
+ const withIapAndroid: ConfigPlugin<
168
+ | {
169
+ iapkitApiKey?: string;
170
+ isHorizonEnabled?: boolean;
171
+ isFireOsEnabled?: boolean;
172
+ }
173
+ | undefined
174
+ > = (config, props) => {
132
175
  // Add OpenIAP dependency to app build.gradle
133
176
  config = withAppBuildGradle(config, (config) => {
134
177
  config.modResults.contents = modifyAppBuildGradle(
135
178
  config.modResults.contents,
179
+ props?.isHorizonEnabled,
180
+ props?.isFireOsEnabled,
181
+ );
182
+ return config;
183
+ });
184
+
185
+ config = withGradleProperties(config, (config) => {
186
+ const horizonValue = props?.isHorizonEnabled ?? false;
187
+ const fireOsValue = props?.isFireOsEnabled ?? false;
188
+
189
+ config.modResults = config.modResults.filter(
190
+ (item) =>
191
+ item.type !== 'property' ||
192
+ !['horizonEnabled', 'fireOsEnabled'].includes(item.key),
136
193
  );
194
+
195
+ config.modResults.push({
196
+ type: 'property',
197
+ key: 'horizonEnabled',
198
+ value: String(horizonValue),
199
+ });
200
+ config.modResults.push({
201
+ type: 'property',
202
+ key: 'fireOsEnabled',
203
+ value: String(fireOsValue),
204
+ });
205
+
137
206
  return config;
138
207
  });
139
208
 
140
209
  config = withAndroidManifest(config, (config) => {
141
210
  const manifest = config.modResults;
142
- if (!manifest.manifest['uses-permission']) {
143
- manifest.manifest['uses-permission'] = [];
144
- }
145
-
146
- const permissions = manifest.manifest['uses-permission'];
211
+ const existingPermissions = manifest.manifest['uses-permission'];
212
+ const permissions = Array.isArray(existingPermissions)
213
+ ? existingPermissions
214
+ : existingPermissions
215
+ ? [existingPermissions]
216
+ : [];
217
+ manifest.manifest['uses-permission'] = permissions;
147
218
  const billingPerm = {$: {'android:name': 'com.android.vending.BILLING'}};
148
219
 
149
- const alreadyExists = permissions.some(
150
- (p) => p.$['android:name'] === 'com.android.vending.BILLING',
151
- );
152
- if (!alreadyExists) {
153
- permissions.push(billingPerm);
154
- if (!hasLoggedPluginExecution) {
155
- console.log(
156
- '✅ Added com.android.vending.BILLING to AndroidManifest.xml',
157
- );
158
- }
220
+ if (props?.isFireOsEnabled) {
221
+ manifest.manifest['uses-permission'] = permissions.filter(
222
+ (p) => p.$['android:name'] !== 'com.android.vending.BILLING',
223
+ );
159
224
  } else {
160
- if (!hasLoggedPluginExecution) {
161
- console.log(
162
- 'ℹ️ com.android.vending.BILLING already exists in AndroidManifest.xml',
163
- );
225
+ const alreadyExists = permissions.some(
226
+ (p) => p.$['android:name'] === 'com.android.vending.BILLING',
227
+ );
228
+ if (!alreadyExists) {
229
+ permissions.push(billingPerm);
230
+ if (!hasLoggedPluginExecution) {
231
+ console.log(
232
+ '✅ Added com.android.vending.BILLING to AndroidManifest.xml',
233
+ );
234
+ }
235
+ } else {
236
+ if (!hasLoggedPluginExecution) {
237
+ console.log(
238
+ 'ℹ️ com.android.vending.BILLING already exists in AndroidManifest.xml',
239
+ );
240
+ }
164
241
  }
165
242
  }
166
243
 
@@ -366,8 +443,54 @@ type IapPluginProps = {
366
443
  * Get your project key from https://kit.openiap.dev
367
444
  */
368
445
  iapkitApiKey?: string;
446
+ /**
447
+ * Amazon platform targets.
448
+ *
449
+ * Fire OS selects the Android Amazon Appstore flavor. Vega OS is selected by
450
+ * the Kepler runtime and does not change the Android Gradle flavor.
451
+ */
452
+ amazon?: {
453
+ /**
454
+ * Fire OS module for Amazon-distributed Android builds.
455
+ * @platform android
456
+ */
457
+ fireOS?: boolean;
458
+ /**
459
+ * Vega OS runtime target. This is not an Android flavor.
460
+ */
461
+ vegaOS?: boolean;
462
+ };
463
+ /**
464
+ * Optional non-Amazon Android store modules.
465
+ * Fire OS takes precedence when both Fire OS and Horizon are enabled.
466
+ */
467
+ modules?: {
468
+ /**
469
+ * Horizon module for Meta Quest/VR devices.
470
+ * @platform android
471
+ */
472
+ horizon?: boolean;
473
+ };
369
474
  };
370
475
 
476
+ export function resolveAmazonPlatformFlags(props?: IapPluginProps): {
477
+ isFireOsEnabled: boolean;
478
+ isVegaEnabled: boolean;
479
+ isHorizonEnabled: boolean;
480
+ } {
481
+ const isFireOsEnabled = props?.amazon?.fireOS ?? false;
482
+ const isVegaEnabled = props?.amazon?.vegaOS ?? false;
483
+ const isHorizonEnabled = isFireOsEnabled
484
+ ? false
485
+ : (props?.modules?.horizon ?? false);
486
+
487
+ return {
488
+ isFireOsEnabled,
489
+ isVegaEnabled,
490
+ isHorizonEnabled,
491
+ };
492
+ }
493
+
371
494
  const withIapIosFollyWorkaround: ConfigPlugin<IapPluginProps | undefined> = (
372
495
  config,
373
496
  props,
@@ -452,7 +575,14 @@ const withIapkitApiKeyIOS: ConfigPlugin<string | undefined> = (
452
575
 
453
576
  const withIAP: ConfigPlugin<IapPluginProps | undefined> = (config, props) => {
454
577
  try {
455
- let result = withIapAndroid(config, {iapkitApiKey: props?.iapkitApiKey});
578
+ const {isFireOsEnabled, isHorizonEnabled} =
579
+ resolveAmazonPlatformFlags(props);
580
+
581
+ let result = withIapAndroid(config, {
582
+ iapkitApiKey: props?.iapkitApiKey,
583
+ isHorizonEnabled,
584
+ isFireOsEnabled,
585
+ });
456
586
  result = withIapIosFollyWorkaround(result, props);
457
587
  // Add iOS alternative billing configuration if provided
458
588
  if (props?.iosAlternativeBilling) {