expo-iap 3.1.0 → 3.1.1-rc.2
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/build/ExpoIapModule.d.ts +1 -0
- package/build/ExpoIapModule.d.ts.map +1 -1
- package/build/ExpoIapModule.js +29 -4
- package/build/ExpoIapModule.js.map +1 -1
- package/build/utils/constants.d.ts +2 -0
- package/build/utils/constants.d.ts.map +1 -1
- package/build/utils/constants.js +9 -2
- package/build/utils/constants.js.map +1 -1
- package/coverage/clover.xml +497 -0
- package/coverage/coverage-final.json +6 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +161 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov-report/src/ExpoIap.types.ts.html +1243 -0
- package/coverage/lcov-report/src/PurchaseError.ts.html +787 -0
- package/coverage/lcov-report/src/helpers/index.html +116 -0
- package/coverage/lcov-report/src/helpers/subscription.ts.html +496 -0
- package/coverage/lcov-report/src/index.html +116 -0
- package/coverage/lcov-report/src/index.ts.html +1993 -0
- package/coverage/lcov-report/src/modules/android.ts.html +550 -0
- package/coverage/lcov-report/src/modules/index.html +131 -0
- package/coverage/lcov-report/src/modules/ios.ts.html +1222 -0
- package/coverage/lcov-report/src/purchase-error.ts.html +880 -0
- package/coverage/lcov-report/src/types/ExpoIapAndroid.types.ts.html +493 -0
- package/coverage/lcov-report/src/types/index.html +116 -0
- package/coverage/lcov-report/src/useIap.ts.html +1483 -0
- package/coverage/lcov-report/src/utils/errorMapping.ts.html +1069 -0
- package/coverage/lcov-report/src/utils/index.html +116 -0
- package/coverage/lcov-report/src/utils/purchase.ts.html +241 -0
- package/coverage/lcov.info +929 -0
- package/expo-module.config.json +10 -3
- package/ios/onside/OnsideIapModule.swift +489 -0
- package/package.json +4 -3
- package/plugin/build/withIAP.d.ts +22 -9
- package/plugin/build/withIAP.js +157 -9
- package/plugin/jest.config.js +13 -3
- package/plugin/src/expoConfig.augmentation.d.ts +38 -0
- package/plugin/src/withIAP.ts +258 -18
- package/plugin/tsconfig.json +2 -1
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/src/ExpoIapModule.ts +45 -4
- package/src/utils/constants.ts +11 -2
package/plugin/build/withIAP.js
CHANGED
|
@@ -36,6 +36,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.modifyAppBuildGradle = void 0;
|
|
40
|
+
exports.computeAutolinkModules = computeAutolinkModules;
|
|
41
|
+
exports.resolveModuleSelection = resolveModuleSelection;
|
|
39
42
|
const config_plugins_1 = require("expo/config-plugins");
|
|
40
43
|
const fs = __importStar(require("fs"));
|
|
41
44
|
const path = __importStar(require("path"));
|
|
@@ -43,6 +46,7 @@ const withLocalOpenIAP_1 = __importDefault(require("./withLocalOpenIAP"));
|
|
|
43
46
|
const pkg = require('../../package.json');
|
|
44
47
|
const openiapVersions = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../openiap-versions.json'), 'utf8'));
|
|
45
48
|
const OPENIAP_ANDROID_VERSION = openiapVersions.google;
|
|
49
|
+
const AUTOLINKING_CONFIG_PATH = path.resolve(__dirname, '../../expo-module.config.json');
|
|
46
50
|
// Log a message only once per Node process
|
|
47
51
|
const logOnce = (() => {
|
|
48
52
|
const printed = new Set();
|
|
@@ -88,13 +92,14 @@ const modifyAppBuildGradle = (gradle, language) => {
|
|
|
88
92
|
}
|
|
89
93
|
return modified;
|
|
90
94
|
};
|
|
95
|
+
exports.modifyAppBuildGradle = modifyAppBuildGradle;
|
|
91
96
|
const withIapAndroid = (config, props) => {
|
|
92
97
|
const addDeps = props?.addDeps ?? true;
|
|
93
98
|
if (addDeps) {
|
|
94
99
|
config = (0, config_plugins_1.withAppBuildGradle)(config, (config) => {
|
|
95
100
|
// language provided by config-plugins: 'groovy' | 'kotlin'
|
|
96
101
|
const language = config.modResults.language || 'groovy';
|
|
97
|
-
config.modResults.contents = modifyAppBuildGradle(config.modResults.contents, language);
|
|
102
|
+
config.modResults.contents = (0, exports.modifyAppBuildGradle)(config.modResults.contents, language);
|
|
98
103
|
return config;
|
|
99
104
|
});
|
|
100
105
|
}
|
|
@@ -117,8 +122,88 @@ const withIapAndroid = (config, props) => {
|
|
|
117
122
|
});
|
|
118
123
|
return config;
|
|
119
124
|
};
|
|
120
|
-
|
|
121
|
-
const
|
|
125
|
+
const ensureOnsidePod = (content) => {
|
|
126
|
+
const podLine = " pod 'OnsideKit', :git => 'https://github.com/onside-io/OnsideKit-iOS.git'";
|
|
127
|
+
const podRegex = /^\s*pod\s+'OnsideKit'\b.*$/m;
|
|
128
|
+
if (podRegex.test(content)) {
|
|
129
|
+
return content;
|
|
130
|
+
}
|
|
131
|
+
const targetMatch = content.match(/target\s+'[^']+'\s+do\s*\n/);
|
|
132
|
+
if (!targetMatch) {
|
|
133
|
+
config_plugins_1.WarningAggregator.addWarningIOS('expo-iap', 'Could not find a target block in Podfile when adding OnsideKit; skipping installation.');
|
|
134
|
+
return content;
|
|
135
|
+
}
|
|
136
|
+
const insertIndex = targetMatch.index + targetMatch[0].length;
|
|
137
|
+
const before = content.slice(0, insertIndex);
|
|
138
|
+
const after = content.slice(insertIndex);
|
|
139
|
+
logOnce('📦 expo-iap: Added OnsideKit pod to Podfile');
|
|
140
|
+
return `${before}${podLine}\n${after}`;
|
|
141
|
+
};
|
|
142
|
+
function computeAutolinkModules(existing, desired) {
|
|
143
|
+
let modules = [...existing];
|
|
144
|
+
const added = [];
|
|
145
|
+
const removed = [];
|
|
146
|
+
for (const entry of desired) {
|
|
147
|
+
const hasModule = modules.includes(entry.name);
|
|
148
|
+
if (entry.enable && !hasModule) {
|
|
149
|
+
modules = [...modules, entry.name];
|
|
150
|
+
added.push(entry.name);
|
|
151
|
+
}
|
|
152
|
+
else if (!entry.enable && hasModule) {
|
|
153
|
+
modules = modules.filter((module) => module !== entry.name);
|
|
154
|
+
removed.push(entry.name);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return { modules, added, removed };
|
|
158
|
+
}
|
|
159
|
+
const syncAutolinking = (state) => {
|
|
160
|
+
if (!fs.existsSync(AUTOLINKING_CONFIG_PATH)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const raw = fs.readFileSync(AUTOLINKING_CONFIG_PATH, 'utf8');
|
|
165
|
+
const config = JSON.parse(raw);
|
|
166
|
+
const iosConfig = config.ios ?? (config.ios = {});
|
|
167
|
+
const existing = Array.isArray(iosConfig.modules)
|
|
168
|
+
? iosConfig.modules.filter((module) => module !== 'OneSideModule')
|
|
169
|
+
: [];
|
|
170
|
+
const desiredEntries = [
|
|
171
|
+
{
|
|
172
|
+
name: 'ExpoIapModule',
|
|
173
|
+
enable: state.expoIap,
|
|
174
|
+
addLog: '🔗 expo-iap: Enabled ExpoIapModule autolinking',
|
|
175
|
+
removeLog: '🧹 expo-iap: Disabled ExpoIapModule autolinking',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'OnsideIapModule',
|
|
179
|
+
enable: state.onside,
|
|
180
|
+
addLog: '🔗 expo-iap: Enabled OnsideIapModule autolinking',
|
|
181
|
+
removeLog: '🧹 expo-iap: Disabled OnsideIapModule autolinking',
|
|
182
|
+
},
|
|
183
|
+
];
|
|
184
|
+
const { modules: nextModules, added, removed, } = computeAutolinkModules(existing, desiredEntries.map(({ name, enable }) => ({ name, enable })));
|
|
185
|
+
for (const name of added) {
|
|
186
|
+
const entry = desiredEntries.find((candidate) => candidate.name === name);
|
|
187
|
+
if (entry) {
|
|
188
|
+
logOnce(entry.addLog);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
for (const name of removed) {
|
|
192
|
+
const entry = desiredEntries.find((candidate) => candidate.name === name);
|
|
193
|
+
if (entry) {
|
|
194
|
+
logOnce(entry.removeLog);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (added.length > 0 || removed.length > 0) {
|
|
198
|
+
iosConfig.modules = nextModules;
|
|
199
|
+
fs.writeFileSync(AUTOLINKING_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
config_plugins_1.WarningAggregator.addWarningIOS('expo-iap', `Failed to sync Expo IAP autolinking modules: ${String(error)}`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const withIapIOS = (config, props) => {
|
|
122
207
|
return (0, config_plugins_1.withDangerousMod)(config, [
|
|
123
208
|
'ios',
|
|
124
209
|
async (config) => {
|
|
@@ -140,17 +225,77 @@ const withIapIOS = (config) => {
|
|
|
140
225
|
content = content.replace(localPodRegex, '').replace(/\n{3,}/g, '\n\n');
|
|
141
226
|
logOnce('🧹 expo-iap: Removed local OpenIAP pod from Podfile');
|
|
142
227
|
}
|
|
228
|
+
// 3) Optionally install OnsideKit when enabled in config
|
|
229
|
+
if (props?.enableOnside) {
|
|
230
|
+
content = ensureOnsidePod(content);
|
|
231
|
+
}
|
|
143
232
|
fs.writeFileSync(podfilePath, content);
|
|
144
233
|
return config;
|
|
145
234
|
},
|
|
146
235
|
]);
|
|
147
236
|
};
|
|
148
|
-
const
|
|
237
|
+
const MODULE_RULES = {
|
|
238
|
+
expoIap: {
|
|
239
|
+
when: {
|
|
240
|
+
'expo-iap': true,
|
|
241
|
+
onside: false,
|
|
242
|
+
},
|
|
243
|
+
default: ({ options }) => options?.modules?.expoIap ?? true,
|
|
244
|
+
},
|
|
245
|
+
onside: {
|
|
246
|
+
when: {
|
|
247
|
+
'expo-iap': false,
|
|
248
|
+
onside: true,
|
|
249
|
+
},
|
|
250
|
+
default: ({ config, options }) => options?.modules?.onside ?? config.ios?.onside?.enabled ?? true,
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
function resolveModuleSelection(config, options) {
|
|
254
|
+
const normalizedOptions = (options ?? undefined);
|
|
255
|
+
const selection = normalizedOptions?.module ?? 'auto';
|
|
256
|
+
const includeExpoIap = pickModuleState('expoIap', selection, config, normalizedOptions);
|
|
257
|
+
const includeOnside = pickModuleState('onside', selection, config, normalizedOptions);
|
|
258
|
+
return { selection, includeExpoIap, includeOnside };
|
|
259
|
+
}
|
|
260
|
+
function pickModuleState(key, selection, config, options) {
|
|
261
|
+
const rules = MODULE_RULES[key];
|
|
262
|
+
const explicit = rules.when[selection];
|
|
263
|
+
if (explicit !== undefined) {
|
|
264
|
+
return explicit;
|
|
265
|
+
}
|
|
266
|
+
const override = options?.modules?.[key];
|
|
267
|
+
if (override !== undefined) {
|
|
268
|
+
return override;
|
|
269
|
+
}
|
|
270
|
+
return rules.default({ config, options });
|
|
271
|
+
}
|
|
272
|
+
const withIAP = (config, options) => {
|
|
149
273
|
try {
|
|
274
|
+
const { includeExpoIap, includeOnside } = resolveModuleSelection(config, options);
|
|
275
|
+
const autolinkState = {
|
|
276
|
+
expoIap: includeExpoIap,
|
|
277
|
+
onside: includeOnside,
|
|
278
|
+
};
|
|
279
|
+
if (includeOnside) {
|
|
280
|
+
config.ios = {
|
|
281
|
+
...config.ios,
|
|
282
|
+
onside: {
|
|
283
|
+
...(config.ios?.onside ?? {}),
|
|
284
|
+
enabled: true,
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
else if (config.ios?.onside?.enabled) {
|
|
289
|
+
config.ios.onside.enabled = false;
|
|
290
|
+
}
|
|
150
291
|
// Respect explicit flag; fall back to presence of localPath only when flag is unset
|
|
151
292
|
const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
|
|
152
|
-
|
|
153
|
-
|
|
293
|
+
const shouldConfigureAndroid = includeExpoIap;
|
|
294
|
+
const shouldAddAndroidDeps = includeExpoIap && !isLocalDev;
|
|
295
|
+
// Apply Android modifications (skip when Expo IAP disabled)
|
|
296
|
+
let result = shouldConfigureAndroid
|
|
297
|
+
? withIapAndroid(config, { addDeps: shouldAddAndroidDeps })
|
|
298
|
+
: config;
|
|
154
299
|
// iOS: choose one path to avoid overlap
|
|
155
300
|
if (isLocalDev) {
|
|
156
301
|
if (!options?.localPath) {
|
|
@@ -173,9 +318,12 @@ const withIap = (config, options) => {
|
|
|
173
318
|
}
|
|
174
319
|
else {
|
|
175
320
|
// Ensure iOS Podfile is set up to resolve public CocoaPods specs
|
|
176
|
-
result = withIapIOS(result);
|
|
177
|
-
|
|
321
|
+
result = withIapIOS(result, { enableOnside: includeOnside });
|
|
322
|
+
if (includeExpoIap) {
|
|
323
|
+
logOnce('📦 [expo-iap] Using OpenIAP from CocoaPods');
|
|
324
|
+
}
|
|
178
325
|
}
|
|
326
|
+
syncAutolinking(autolinkState);
|
|
179
327
|
return result;
|
|
180
328
|
}
|
|
181
329
|
catch (error) {
|
|
@@ -184,4 +332,4 @@ const withIap = (config, options) => {
|
|
|
184
332
|
return config;
|
|
185
333
|
}
|
|
186
334
|
};
|
|
187
|
-
exports.default = (0, config_plugins_1.createRunOncePlugin)(
|
|
335
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withIAP, pkg.name, pkg.version);
|
package/plugin/jest.config.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
// In documentation there is `preset: expo-module-scripts`, but it runs tests for every platform (ios, android, web, node)
|
|
2
|
-
// We need only node tests right now
|
|
3
1
|
module.exports = {
|
|
4
|
-
preset: 'jest
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
transform: {
|
|
5
|
+
'^.+\\.(ts|tsx)$': [
|
|
6
|
+
'ts-jest',
|
|
7
|
+
{
|
|
8
|
+
tsconfig: '<rootDir>/tsconfig.json',
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
},
|
|
12
|
+
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
|
13
|
+
roots: ['<rootDir>/__tests__'],
|
|
14
|
+
testMatch: ['**/?(*.)+(spec|test).ts?(x)'],
|
|
5
15
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {IOS} from '@expo/config-types';
|
|
2
|
+
|
|
3
|
+
export type ExpoIapModuleOverrides = {
|
|
4
|
+
expoIap?: boolean;
|
|
5
|
+
onside?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type BaseExpoIapOptions = {
|
|
9
|
+
enableLocalDev?: boolean;
|
|
10
|
+
localPath?:
|
|
11
|
+
| string
|
|
12
|
+
| {
|
|
13
|
+
ios?: string;
|
|
14
|
+
android?: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type AutoModuleOptions = BaseExpoIapOptions & {
|
|
19
|
+
module?: 'auto';
|
|
20
|
+
modules?: ExpoIapModuleOverrides;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type ExplicitModuleOptions = BaseExpoIapOptions & {
|
|
24
|
+
module: 'expo-iap' | 'onside';
|
|
25
|
+
modules?: never;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ExpoIapPluginCommonOptions =
|
|
29
|
+
| AutoModuleOptions
|
|
30
|
+
| ExplicitModuleOptions;
|
|
31
|
+
|
|
32
|
+
declare module '@expo/config-types' {
|
|
33
|
+
interface IOS {
|
|
34
|
+
onside?: {
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
package/plugin/src/withIAP.ts
CHANGED
|
@@ -6,9 +6,11 @@ import {
|
|
|
6
6
|
withAppBuildGradle,
|
|
7
7
|
withDangerousMod,
|
|
8
8
|
} from 'expo/config-plugins';
|
|
9
|
+
import type {ExpoConfig} from '@expo/config-types';
|
|
9
10
|
import * as fs from 'fs';
|
|
10
11
|
import * as path from 'path';
|
|
11
12
|
import withLocalOpenIAP from './withLocalOpenIAP';
|
|
13
|
+
import type {ExpoIapPluginCommonOptions} from './expoConfig.augmentation';
|
|
12
14
|
|
|
13
15
|
const pkg = require('../../package.json');
|
|
14
16
|
const openiapVersions = JSON.parse(
|
|
@@ -18,6 +20,10 @@ const openiapVersions = JSON.parse(
|
|
|
18
20
|
),
|
|
19
21
|
);
|
|
20
22
|
const OPENIAP_ANDROID_VERSION = openiapVersions.google;
|
|
23
|
+
const AUTOLINKING_CONFIG_PATH = path.resolve(
|
|
24
|
+
__dirname,
|
|
25
|
+
'../../expo-module.config.json',
|
|
26
|
+
);
|
|
21
27
|
|
|
22
28
|
// Log a message only once per Node process
|
|
23
29
|
const logOnce = (() => {
|
|
@@ -50,7 +56,7 @@ const addLineToGradle = (
|
|
|
50
56
|
return lines.join('\n');
|
|
51
57
|
};
|
|
52
58
|
|
|
53
|
-
const modifyAppBuildGradle = (
|
|
59
|
+
export const modifyAppBuildGradle = (
|
|
54
60
|
gradle: string,
|
|
55
61
|
language: 'groovy' | 'kotlin',
|
|
56
62
|
): string => {
|
|
@@ -138,7 +144,136 @@ const withIapAndroid: ConfigPlugin<{addDeps?: boolean} | void> = (
|
|
|
138
144
|
};
|
|
139
145
|
|
|
140
146
|
/** Ensure Podfile uses CocoaPods CDN and no stale local OpenIAP entry remains. */
|
|
141
|
-
|
|
147
|
+
type WithIapIosOptions = {
|
|
148
|
+
enableOnside?: boolean;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const ensureOnsidePod = (content: string): string => {
|
|
152
|
+
const podLine =
|
|
153
|
+
" pod 'OnsideKit', :git => 'https://github.com/onside-io/OnsideKit-iOS.git'";
|
|
154
|
+
const podRegex = /^\s*pod\s+'OnsideKit'\b.*$/m;
|
|
155
|
+
|
|
156
|
+
if (podRegex.test(content)) {
|
|
157
|
+
return content;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const targetMatch = content.match(/target\s+'[^']+'\s+do\s*\n/);
|
|
161
|
+
if (!targetMatch) {
|
|
162
|
+
WarningAggregator.addWarningIOS(
|
|
163
|
+
'expo-iap',
|
|
164
|
+
'Could not find a target block in Podfile when adding OnsideKit; skipping installation.',
|
|
165
|
+
);
|
|
166
|
+
return content;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const insertIndex = targetMatch.index! + targetMatch[0].length;
|
|
170
|
+
const before = content.slice(0, insertIndex);
|
|
171
|
+
const after = content.slice(insertIndex);
|
|
172
|
+
|
|
173
|
+
logOnce('📦 expo-iap: Added OnsideKit pod to Podfile');
|
|
174
|
+
|
|
175
|
+
return `${before}${podLine}\n${after}`;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export type AutolinkState = {expoIap: boolean; onside: boolean};
|
|
179
|
+
|
|
180
|
+
type AutolinkEntry = {name: string; enable: boolean};
|
|
181
|
+
|
|
182
|
+
export function computeAutolinkModules(
|
|
183
|
+
existing: string[],
|
|
184
|
+
desired: AutolinkEntry[],
|
|
185
|
+
): {modules: string[]; added: string[]; removed: string[]} {
|
|
186
|
+
let modules = [...existing];
|
|
187
|
+
const added: string[] = [];
|
|
188
|
+
const removed: string[] = [];
|
|
189
|
+
|
|
190
|
+
for (const entry of desired) {
|
|
191
|
+
const hasModule = modules.includes(entry.name);
|
|
192
|
+
if (entry.enable && !hasModule) {
|
|
193
|
+
modules = [...modules, entry.name];
|
|
194
|
+
added.push(entry.name);
|
|
195
|
+
} else if (!entry.enable && hasModule) {
|
|
196
|
+
modules = modules.filter((module) => module !== entry.name);
|
|
197
|
+
removed.push(entry.name);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {modules, added, removed};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const syncAutolinking = (state: AutolinkState) => {
|
|
205
|
+
if (!fs.existsSync(AUTOLINKING_CONFIG_PATH)) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const raw = fs.readFileSync(AUTOLINKING_CONFIG_PATH, 'utf8');
|
|
211
|
+
const config = JSON.parse(raw);
|
|
212
|
+
const iosConfig = config.ios ?? (config.ios = {});
|
|
213
|
+
const existing: string[] = Array.isArray(iosConfig.modules)
|
|
214
|
+
? iosConfig.modules.filter((module: string) => module !== 'OneSideModule')
|
|
215
|
+
: [];
|
|
216
|
+
|
|
217
|
+
const desiredEntries: {
|
|
218
|
+
name: string;
|
|
219
|
+
enable: boolean;
|
|
220
|
+
addLog: string;
|
|
221
|
+
removeLog: string;
|
|
222
|
+
}[] = [
|
|
223
|
+
{
|
|
224
|
+
name: 'ExpoIapModule',
|
|
225
|
+
enable: state.expoIap,
|
|
226
|
+
addLog: '🔗 expo-iap: Enabled ExpoIapModule autolinking',
|
|
227
|
+
removeLog: '🧹 expo-iap: Disabled ExpoIapModule autolinking',
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'OnsideIapModule',
|
|
231
|
+
enable: state.onside,
|
|
232
|
+
addLog: '🔗 expo-iap: Enabled OnsideIapModule autolinking',
|
|
233
|
+
removeLog: '🧹 expo-iap: Disabled OnsideIapModule autolinking',
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
|
|
237
|
+
const {
|
|
238
|
+
modules: nextModules,
|
|
239
|
+
added,
|
|
240
|
+
removed,
|
|
241
|
+
} = computeAutolinkModules(
|
|
242
|
+
existing,
|
|
243
|
+
desiredEntries.map(({name, enable}) => ({name, enable})),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
for (const name of added) {
|
|
247
|
+
const entry = desiredEntries.find((candidate) => candidate.name === name);
|
|
248
|
+
if (entry) {
|
|
249
|
+
logOnce(entry.addLog);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const name of removed) {
|
|
254
|
+
const entry = desiredEntries.find((candidate) => candidate.name === name);
|
|
255
|
+
if (entry) {
|
|
256
|
+
logOnce(entry.removeLog);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (added.length > 0 || removed.length > 0) {
|
|
261
|
+
iosConfig.modules = nextModules;
|
|
262
|
+
fs.writeFileSync(
|
|
263
|
+
AUTOLINKING_CONFIG_PATH,
|
|
264
|
+
`${JSON.stringify(config, null, 2)}\n`,
|
|
265
|
+
'utf8',
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
WarningAggregator.addWarningIOS(
|
|
270
|
+
'expo-iap',
|
|
271
|
+
`Failed to sync Expo IAP autolinking modules: ${String(error)}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const withIapIOS: ConfigPlugin<WithIapIosOptions | void> = (config, props) => {
|
|
142
277
|
return withDangerousMod(config, [
|
|
143
278
|
'ios',
|
|
144
279
|
async (config) => {
|
|
@@ -166,33 +301,134 @@ const withIapIOS: ConfigPlugin = (config) => {
|
|
|
166
301
|
logOnce('🧹 expo-iap: Removed local OpenIAP pod from Podfile');
|
|
167
302
|
}
|
|
168
303
|
|
|
304
|
+
// 3) Optionally install OnsideKit when enabled in config
|
|
305
|
+
if (props?.enableOnside) {
|
|
306
|
+
content = ensureOnsidePod(content);
|
|
307
|
+
}
|
|
308
|
+
|
|
169
309
|
fs.writeFileSync(podfilePath, content);
|
|
170
310
|
return config;
|
|
171
311
|
},
|
|
172
312
|
]);
|
|
173
313
|
};
|
|
174
314
|
|
|
175
|
-
export interface
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
315
|
+
export interface ModuleSelectionResult {
|
|
316
|
+
selection: 'auto' | 'expo-iap' | 'onside';
|
|
317
|
+
includeExpoIap: boolean;
|
|
318
|
+
includeOnside: boolean;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
type ModuleKey = 'expoIap' | 'onside';
|
|
322
|
+
|
|
323
|
+
type ModuleRules = Record<
|
|
324
|
+
ModuleKey,
|
|
325
|
+
{
|
|
326
|
+
when: Partial<Record<ModuleSelectionResult['selection'], boolean>>;
|
|
327
|
+
default: (args: {
|
|
328
|
+
config: ExpoConfig;
|
|
329
|
+
options?: ExpoIapPluginCommonOptions;
|
|
330
|
+
}) => boolean;
|
|
331
|
+
}
|
|
332
|
+
>;
|
|
333
|
+
|
|
334
|
+
const MODULE_RULES: ModuleRules = {
|
|
335
|
+
expoIap: {
|
|
336
|
+
when: {
|
|
337
|
+
'expo-iap': true,
|
|
338
|
+
onside: false,
|
|
339
|
+
},
|
|
340
|
+
default: ({options}) => options?.modules?.expoIap ?? true,
|
|
341
|
+
},
|
|
342
|
+
onside: {
|
|
343
|
+
when: {
|
|
344
|
+
'expo-iap': false,
|
|
345
|
+
onside: true,
|
|
346
|
+
},
|
|
347
|
+
default: ({config, options}) =>
|
|
348
|
+
options?.modules?.onside ?? config.ios?.onside?.enabled ?? true,
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export function resolveModuleSelection(
|
|
353
|
+
config: ExpoConfig,
|
|
354
|
+
options?: ExpoIapPluginCommonOptions | void,
|
|
355
|
+
): ModuleSelectionResult {
|
|
356
|
+
const normalizedOptions = (options ?? undefined) as
|
|
357
|
+
| ExpoIapPluginCommonOptions
|
|
358
|
+
| undefined;
|
|
359
|
+
|
|
360
|
+
const selection = normalizedOptions?.module ?? 'auto';
|
|
361
|
+
|
|
362
|
+
const includeExpoIap = pickModuleState(
|
|
363
|
+
'expoIap',
|
|
364
|
+
selection,
|
|
365
|
+
config,
|
|
366
|
+
normalizedOptions,
|
|
367
|
+
);
|
|
368
|
+
const includeOnside = pickModuleState(
|
|
369
|
+
'onside',
|
|
370
|
+
selection,
|
|
371
|
+
config,
|
|
372
|
+
normalizedOptions,
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
return {selection, includeExpoIap, includeOnside};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function pickModuleState(
|
|
379
|
+
key: ModuleKey,
|
|
380
|
+
selection: ModuleSelectionResult['selection'],
|
|
381
|
+
config: ExpoConfig,
|
|
382
|
+
options?: ExpoIapPluginCommonOptions,
|
|
383
|
+
): boolean {
|
|
384
|
+
const rules = MODULE_RULES[key];
|
|
385
|
+
const explicit = rules.when[selection];
|
|
386
|
+
if (explicit !== undefined) {
|
|
387
|
+
return explicit;
|
|
388
|
+
}
|
|
389
|
+
const override = options?.modules?.[key];
|
|
390
|
+
if (override !== undefined) {
|
|
391
|
+
return override;
|
|
392
|
+
}
|
|
393
|
+
return rules.default({config, options});
|
|
185
394
|
}
|
|
186
395
|
|
|
187
|
-
const
|
|
396
|
+
const withIAP: ConfigPlugin<ExpoIapPluginCommonOptions | void> = (
|
|
188
397
|
config,
|
|
189
398
|
options,
|
|
190
399
|
) => {
|
|
191
400
|
try {
|
|
401
|
+
const {includeExpoIap, includeOnside} = resolveModuleSelection(
|
|
402
|
+
config as ExpoConfig,
|
|
403
|
+
options,
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const autolinkState: AutolinkState = {
|
|
407
|
+
expoIap: includeExpoIap,
|
|
408
|
+
onside: includeOnside,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
if (includeOnside) {
|
|
412
|
+
config.ios = {
|
|
413
|
+
...config.ios,
|
|
414
|
+
onside: {
|
|
415
|
+
...(config.ios?.onside ?? {}),
|
|
416
|
+
enabled: true,
|
|
417
|
+
},
|
|
418
|
+
} as typeof config.ios;
|
|
419
|
+
} else if (config.ios?.onside?.enabled) {
|
|
420
|
+
config.ios.onside.enabled = false;
|
|
421
|
+
}
|
|
422
|
+
|
|
192
423
|
// Respect explicit flag; fall back to presence of localPath only when flag is unset
|
|
193
424
|
const isLocalDev = options?.enableLocalDev ?? !!options?.localPath;
|
|
194
|
-
|
|
195
|
-
|
|
425
|
+
const shouldConfigureAndroid = includeExpoIap;
|
|
426
|
+
const shouldAddAndroidDeps = includeExpoIap && !isLocalDev;
|
|
427
|
+
|
|
428
|
+
// Apply Android modifications (skip when Expo IAP disabled)
|
|
429
|
+
let result = shouldConfigureAndroid
|
|
430
|
+
? withIapAndroid(config, {addDeps: shouldAddAndroidDeps})
|
|
431
|
+
: config;
|
|
196
432
|
|
|
197
433
|
// iOS: choose one path to avoid overlap
|
|
198
434
|
if (isLocalDev) {
|
|
@@ -222,10 +458,14 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
222
458
|
}
|
|
223
459
|
} else {
|
|
224
460
|
// Ensure iOS Podfile is set up to resolve public CocoaPods specs
|
|
225
|
-
result = withIapIOS(result);
|
|
226
|
-
|
|
461
|
+
result = withIapIOS(result, {enableOnside: includeOnside});
|
|
462
|
+
if (includeExpoIap) {
|
|
463
|
+
logOnce('📦 [expo-iap] Using OpenIAP from CocoaPods');
|
|
464
|
+
}
|
|
227
465
|
}
|
|
228
466
|
|
|
467
|
+
syncAutolinking(autolinkState);
|
|
468
|
+
|
|
229
469
|
return result;
|
|
230
470
|
} catch (error) {
|
|
231
471
|
WarningAggregator.addWarningAndroid(
|
|
@@ -237,4 +477,4 @@ const withIap: ConfigPlugin<ExpoIapPluginOptions | void> = (
|
|
|
237
477
|
}
|
|
238
478
|
};
|
|
239
479
|
|
|
240
|
-
export default createRunOncePlugin(
|
|
480
|
+
export default createRunOncePlugin(withIAP, pkg.name, pkg.version);
|
package/plugin/tsconfig.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/
|
|
1
|
+
{"root":["./src/expoconfig.augmentation.d.ts","./src/withiap.ts","./src/withlocalopeniap.ts"],"version":"5.9.2"}
|
package/src/ExpoIapModule.ts
CHANGED
|
@@ -1,10 +1,51 @@
|
|
|
1
|
-
import {requireNativeModule} from 'expo-modules-core';
|
|
1
|
+
import {requireNativeModule, UnavailabilityError} from 'expo-modules-core';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const ExpoIapModule =
|
|
3
|
+
type NativeIapModuleName = 'ExpoIapOnside' | 'ExpoIap';
|
|
4
|
+
|
|
5
|
+
const {module: ExpoIapModule, name: resolvedNativeModuleName} =
|
|
6
|
+
resolveNativeModule();
|
|
7
|
+
|
|
8
|
+
export const USING_ONSIDE_SDK = resolvedNativeModuleName === 'ExpoIapOnside';
|
|
6
9
|
|
|
7
10
|
// Platform-specific error codes from native modules
|
|
8
11
|
export const NATIVE_ERROR_CODES = ExpoIapModule.ERROR_CODES || {};
|
|
9
12
|
|
|
10
13
|
export default ExpoIapModule;
|
|
14
|
+
|
|
15
|
+
function resolveNativeModule(): {
|
|
16
|
+
module: any;
|
|
17
|
+
name: NativeIapModuleName;
|
|
18
|
+
} {
|
|
19
|
+
const candidates: NativeIapModuleName[] = ['ExpoIapOnside', 'ExpoIap'];
|
|
20
|
+
|
|
21
|
+
for (const name of candidates) {
|
|
22
|
+
try {
|
|
23
|
+
const module = requireNativeModule(name);
|
|
24
|
+
return {module, name};
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (name === 'ExpoIapOnside' && isMissingModuleError(error, name)) {
|
|
27
|
+
// Onside module is optional. If unavailable, fall back to ExpoIap.
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
throw new UnavailabilityError(
|
|
36
|
+
'expo-iap',
|
|
37
|
+
'ExpoIap native module is unavailable',
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isMissingModuleError(error: unknown, moduleName: string): boolean {
|
|
42
|
+
if (error instanceof UnavailabilityError) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (error instanceof Error) {
|
|
47
|
+
return error.message.includes(`Cannot find native module '${moduleName}'`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
}
|