Repository navigation
Expand file tree
/
Copy pathapp.plugin.js
More file actions
409 lines (359 loc) · 14.2 KB
/
Copy pathapp.plugin.js
File metadata and controls
409 lines (359 loc) · 14.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
const {
withProjectBuildGradle,
withSettingsGradle,
withAppBuildGradle,
withGradleProperties,
withPodfile,
withPodfileProperties,
createRunOncePlugin,
} = require('@expo/config-plugins');
const {
mergeContents,
} = require('@expo/config-plugins/build/utils/generateCode');
const pkg = require('./package.json');
const MAVEN_REPO =
'https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository';
/**
* The repository must be scoped to Didit's own group. Unfiltered, Gradle
* probes raw.githubusercontent.com for EVERY dependency in the consumer's
* build; the host rate-limits (429), and Gradle then disables the repository
* for the rest of the run - failing unrelated dependencies like
* androidx.autofill or hermes-android (issue #40). With the content filter,
* only me.didit artifacts are ever requested here, which also makes the
* injection position relative to google()/mavenCentral() irrelevant: Gradle
* skips this repository entirely for every other group.
*/
const MAVEN_LINE = [
' maven {',
` url "${MAVEN_REPO}"`,
' content { includeGroup "me.didit" }',
' }',
].join('\n');
/**
* The native iOS SDK is not published to the CocoaPods trunk, so its podspec is
* fetched straight from the sdk-ios repo at a tagged version.
*
* That version MUST be the one `SdkReactNative.podspec` depends on, otherwise
* CocoaPods sees two incompatible requirements for `DiditSDK` and every
* `pod install` fails. Both read `diditNativeSdkVersions.ios` from package.json
* for exactly that reason - a URL hardcoded here silently drifts out of sync on
* the next release (issues #19 and #28).
*/
const IOS_SDK_VERSION =
pkg.diditNativeSdkVersions && pkg.diditNativeSdkVersions.ios;
if (!IOS_SDK_VERSION) {
throw new Error(
'@didit-protocol/sdk-react-native: missing "diditNativeSdkVersions.ios" in package.json - ' +
'the Didit iOS SDK podspec URL cannot be resolved.'
);
}
const DEFAULT_PODSPEC_URL = `https://raw.githubusercontent.com/didit-protocol/sdk-ios/${IOS_SDK_VERSION}/DiditSDK.podspec`;
const VALID_VARIANTS = ['all', 'core', 'autodetection', 'nfc'];
const VALID_LINKAGES = ['cocoapods', 'spm'];
const PODFILE_PROPS_KEY = 'didit.iosVariant';
const POD_BLOCK_TAG = 'didit-sdk-react-native-pod';
function normalizeVariant(value, fallback) {
if (typeof value !== 'string') {
return fallback;
}
const normalized = value.toLowerCase();
return VALID_VARIANTS.includes(normalized) ? normalized : fallback;
}
function variantFromBooleans(nfcEnabled, autoDetectionEnabled = true) {
if (nfcEnabled && autoDetectionEnabled) return 'all';
if (!nfcEnabled && autoDetectionEnabled) return 'autodetection';
if (nfcEnabled && !autoDetectionEnabled) return 'nfc';
return 'core';
}
function normalizeOptions(props = {}) {
const legacyIosVariant = variantFromBooleans(
props.iosNfcEnabled !== false,
props.iosAutoDetectionEnabled !== false
);
const legacyAndroidVariant =
props.androidNfcEnabled === false ? 'core' : 'all';
return {
androidVariant: normalizeVariant(
props.androidVariant,
legacyAndroidVariant
),
iosVariant: normalizeVariant(props.iosVariant, legacyIosVariant),
iosLinkage: normalizeLinkage(props.iosLinkage),
iosPodspecUrl: normalizePodspecUrl(props.iosPodspecUrl),
iosLocationEnabled: props.iosLocationEnabled === true,
};
}
/**
* How CocoaPods gets the native iOS SDK: `spm` resolves it from the sdk-ios
* repo through SwiftPM (no `pod 'DiditSDK'` line in the Podfile at all),
* `cocoapods` keeps the raw-podspec flow. See issue #27.
*/
function normalizeLinkage(value) {
if (typeof value !== 'string' || value.trim() === '') {
return 'cocoapods';
}
const linkage = value.trim().toLowerCase();
if (!VALID_LINKAGES.includes(linkage)) {
throw new Error(
`@didit-protocol/sdk-react-native: invalid "iosLinkage" (${value}) - supported values are ${VALID_LINKAGES.join(
', '
)}.`
);
}
return linkage;
}
/**
* Escape hatch for consumers who need to point CocoaPods at a different
* DiditSDK podspec (a fork, a mirror, or a prerelease tag).
*/
function normalizePodspecUrl(value) {
if (typeof value !== 'string' || value.trim() === '') {
return DEFAULT_PODSPEC_URL;
}
const url = value.trim();
if (url.includes("'")) {
// The URL is emitted inside a single-quoted Ruby string; a stray quote
// would produce a Podfile that fails to parse rather than a clear error.
throw new Error(
`@didit-protocol/sdk-react-native: invalid "iosPodspecUrl" (${url}) - it must not contain a single quote.`
);
}
return url;
}
function diditPodBlock(iosVariant, options, isRubyExpression = false) {
const variantAssignment = isRubyExpression
? ` $DiditSdkIosVariant = ${iosVariant}`
: ` $DiditSdkIosVariant = '${iosVariant}'`;
const lines = [
variantAssignment,
` $DiditSdkIosLinkage = '${options.iosLinkage}'`,
` $DiditSdkIosLocationEnabled = ${options.iosLocationEnabled}`,
];
if (
options.iosLinkage === 'spm' &&
(options.iosLocationEnabled || pkg.diditNativeSdkVersions.source)
) {
throw new Error(
'Location and native source builds require cocoapods iosLinkage.'
);
}
if (options.iosLinkage === 'spm') {
// SdkReactNative.podspec declares DiditSDK as a SwiftPM dependency, so the
// Podfile needs no pod declaration - only the globals it reads.
return lines.join('\n');
}
lines.push(
' didit_sdk_subspec = case $DiditSdkIosVariant',
" when 'all' then 'DiditSDK/All'",
" when 'core' then 'DiditSDK/Core'",
" when 'autodetection' then 'DiditSDK/AutoDetection'",
" when 'nfc' then 'DiditSDK/NFC'",
' else',
' raise "Invalid $DiditSdkIosVariant \'#{$DiditSdkIosVariant}\'. Supported values: all, core, autodetection, nfc."',
' end'
);
if (pkg.diditNativeSdkVersions.source) {
lines.push(
` didit_source_script = Pod::Executable.execute_command('node', ['--print', "require.resolve('@didit-protocol/sdk-react-native/native-sdk-source')"]).strip`,
` didit_source_path = Pod::Executable.execute_command('node', [didit_source_script, 'ios']).strip`,
' pod didit_sdk_subspec, :path => didit_source_path',
" pod 'DiditSDK/Location', :path => didit_source_path if $DiditSdkIosLocationEnabled"
);
} else {
lines.push(
` pod didit_sdk_subspec, :podspec => '${options.iosPodspecUrl}'`,
` pod 'DiditSDK/Location', :podspec => '${options.iosPodspecUrl}' if $DiditSdkIosLocationEnabled`
);
}
return lines.join('\n');
}
// ── Android ──────────────────────────────────────────────────────────────────
/**
* Injects the Didit Maven repository into android/build.gradle
* (allprojects.repositories — used by Expo projects).
*/
function withDiditBuildGradle(config) {
return withProjectBuildGradle(config, (mod) => {
if (mod.modResults.contents.includes(MAVEN_REPO)) {
return mod;
}
const match = mod.modResults.contents.match(
/allprojects\s*\{[\s\S]*?repositories\s*\{/
);
if (match) {
mod.modResults.contents = mod.modResults.contents.replace(
/allprojects\s*\{[\s\S]*?repositories\s*\{/,
(m) => `${m}\n${MAVEN_LINE}`
);
}
return mod;
});
}
/**
* Injects the Didit Maven repository into android/settings.gradle
* (dependencyResolutionManagement.repositories — used by React Native CLI projects).
*/
function withDiditSettingsGradle(config) {
return withSettingsGradle(config, (mod) => {
if (mod.modResults.contents.includes(MAVEN_REPO)) {
return mod;
}
const match = mod.modResults.contents.match(
/dependencyResolutionManagement\s*\{[^}]*repositories\s*\{/
);
if (match) {
mod.modResults.contents = mod.modResults.contents.replace(
/dependencyResolutionManagement\s*\{[^}]*repositories\s*\{/,
(m) => `${m}\n${MAVEN_LINE}`
);
}
return mod;
});
}
function withDiditNativeSource(config) {
return withSettingsGradle(config, (mod) => {
const line = `apply from: new File(providers.exec { commandLine('node', '--print', "require.resolve('@didit-protocol/sdk-react-native/package.json')") }.standardOutput.asText.get().trim()).toPath().resolveSibling('android/native-source.gradle').toFile()`;
if (!mod.modResults.contents.includes('android/native-source.gradle')) {
mod.modResults.contents += `\n${line}\n`;
}
return mod;
});
}
/**
* Adds BouncyCastle dependency exclusions and packaging rules to
* android/app/build.gradle to avoid duplicate class and META-INF
* conflicts from BouncyCastle transitive dependencies.
*
* The DiditSDK uses jdk18on BouncyCastle modules. Host apps may have
* other dependencies that pull in older jdk15to18 or jdk15on variants,
* causing duplicate class errors during Android builds.
*/
const DIDIT_ANDROID_BLOCK = `
configurations.configureEach {
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15to18'
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15to18'
exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15to18'
exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
}
packaging {
resources {
pickFirsts += ['org/bouncycastle/**']
excludes += 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
}
}`;
/**
* Applies to EVERY Android variant, because every variant ships
* `didit-sdk-core`, and `didit-sdk-core` alone already pulls Bouncy Castle
* jdk18on through two independent chains:
*
* didit-sdk-core -> com.reown:android-core -> com.reown:foundation
* -> org.bouncycastle:bcprov-jdk18on
* didit-sdk-core -> com.reown:android-core -> org.web3j:crypto
* -> org.web3j:utils -> org.bouncycastle:bcprov-jdk18on
*
* (`didit-sdk-nfc` adds two more via a direct declaration and jmrtd.)
* That collides with the jdk15to18 family host apps commonly bring in through
* expo-updates, so release builds fail with duplicate classes. The variant
* allowlist this replaces covered only `all` and `nfc`, which broke
* `autodetection` (issue #34) and left `core` exposed to the same failure.
*
* Verified by resolving `me.didit:didit-sdk-core:4.5.3` on its own with Gradle:
* 291 modules resolved, including org.bouncycastle:bcprov-jdk18on:1.78.1.
*/
function withDiditPackagingExclusion(config) {
return withAppBuildGradle(config, (mod) => {
if (mod.modResults.contents.includes('bcprov-jdk15to18')) {
return mod;
}
mod.modResults.contents = mod.modResults.contents.replace(
/android\s*\{/,
(m) => `${m}\n${DIDIT_ANDROID_BLOCK}`
);
return mod;
});
}
function withDiditAndroidVariantProperty(config, androidVariant) {
return withGradleProperties(config, (mod) => {
mod.modResults = mod.modResults.filter(
(item) =>
item.key !== 'diditSdkAndroidVariant' &&
item.key !== 'diditSdkAndroidNfcEnabled'
);
mod.modResults.push({
type: 'property',
key: 'diditSdkAndroidVariant',
value: androidVariant,
});
return mod;
});
}
/**
* Applies all Android Gradle mods so the plugin works with either project structure.
*/
function withDiditAndroidMaven(config, options) {
config = withDiditBuildGradle(config);
config = withDiditSettingsGradle(config);
config = withDiditNativeSource(config);
config = withDiditAndroidVariantProperty(config, options.androidVariant);
config = withDiditPackagingExclusion(config);
return config;
}
// ── iOS ──────────────────────────────────────────────────────────────────────
function withDiditIosVariantProperty(config, options) {
return withPodfileProperties(config, (mod) => {
mod.modResults[PODFILE_PROPS_KEY] = options.iosVariant;
return mod;
});
}
/**
* Injects a single `pod 'DiditSDK', :podspec => ...` block into the host
* Podfile so CocoaPods knows where to fetch the Didit native iOS SDK from
* (it isn't published to the trunk).
*
* Idempotency, value updates, and the `# @generated begin … sync-…` marker
* comments are all handled by `mergeContents` — repeated `expo prebuild`
* runs converge to a stable Podfile and toggling `iosNfcEnabled` rewrites
* the block in place.
*/
function withDiditIosPodspec(config, options) {
return withPodfile(config, (mod) => {
const isExpoPodfile = mod.modResults.contents.includes('use_expo_modules!');
const newSrc = isExpoPodfile
? diditPodBlock(
`podfile_properties.fetch('${PODFILE_PROPS_KEY}', '${options.iosVariant}')`,
options,
true
)
: diditPodBlock(options.iosVariant, options);
const result = mergeContents({
tag: POD_BLOCK_TAG,
src: mod.modResults.contents,
newSrc,
anchor: isExpoPodfile ? /use_expo_modules!/ : /target\s+'.+'\s+do/,
offset: isExpoPodfile ? 0 : 1,
comment: '#',
});
if (result.didMerge || result.didClear) {
mod.modResults.contents = result.contents;
}
return mod;
});
}
// ── Combined Plugin ──────────────────────────────────────────────────────────
/**
* Expo config plugin for @didit-protocol/sdk-react-native.
*
* Automatically configures:
* - Android: Adds the Didit Maven repository to Gradle, packaging exclusions,
* and writes `diditSdkAndroidVariant` to gradle.properties.
* - iOS: Writes `didit.iosVariant` to Podfile.properties.json and adds
* a DiditSDK variant block to the Podfile.
*/
function withDiditSdk(config, props) {
const options = normalizeOptions(props);
config = withDiditAndroidMaven(config, options);
config = withDiditIosVariantProperty(config, options);
config = withDiditIosPodspec(config, options);
return config;
}
module.exports = createRunOncePlugin(withDiditSdk, pkg.name, pkg.version);