react-native-expo-braintree
Version:
React native and expo wrapper around braintree sdk fro android and ios
236 lines (226 loc) • 8.58 kB
JavaScript
;
import { AndroidConfig, withAndroidManifest, withMainActivity, withProjectBuildGradle, WarningAggregator } from '@expo/config-plugins';
import { addImports } from '@expo/config-plugins/build/android/codeMod';
import { mergeContents, createGeneratedHeaderComment, removeGeneratedContents } from '@expo/config-plugins/build/utils/generateCode';
const {
getMainActivityOrThrow
} = AndroidConfig.Manifest;
export const withExpoBraintreeAndroid = (expoConfig, {
host,
pathPrefix,
addFallbackUrlScheme,
initialize3DSecure,
initializeGooglePay
}) => {
let newConfig = withAndroidManifest(expoConfig, config => {
config.modResults = addBraintreeLinks(config.modResults, host, pathPrefix, addFallbackUrlScheme, initialize3DSecure);
return config;
});
newConfig = withMainActivity(expoConfig, config => {
const {
modResults
} = config;
const {
language
} = modResults;
const withImports = addImports(modResults.contents, ['com.expobraintree.ExpoBraintreeModule'], language === 'java');
const newSrc = [` ExpoBraintreeModule.init()${language === 'java' ? ';' : ''}`];
if (initialize3DSecure === 'true') {
newSrc.push(` ExpoBraintreeModule.initThreeDSecure(this)${language === 'java' ? ';' : ''}`);
}
if (initializeGooglePay === 'true') {
newSrc.push(` ExpoBraintreeModule.initGooglePay(this)${language === 'java' ? ';' : ''}`);
}
const withInit = mergeContents({
src: withImports,
comment: ' // add BraintreeModule import',
tag: 'braintree-module-init',
offset: 1,
anchor: /(?<=^.*super\.onCreate.*$)/m,
newSrc: newSrc.join('\n')
});
return {
...config,
modResults: {
...modResults,
contents: withInit.contents
}
};
});
return newConfig;
};
// Add new intent filter for App Links
// <activity>
// ...
// <intent-filter android:autoVerify="true">
// <action android:name="android.intent.action.VIEW" />
// <category android:name="android.intent.category.DEFAULT" />
// <category android:name="android.intent.category.BROWSABLE" />
// <data android:scheme="http" />
// <data android:scheme="https" />
// <data android:host="myownpersonaldomain.com" />
// <data android:pathPrefix="/braintree-payments"/>
// </intent-filter>
// </activity>;
// If you provide a fallbackUrlScheme it will also add a new intent filter for that
// <activity>
// ...
// <intent-filter>
// <action android:name="android.intent.action.VIEW" />
// <category android:name="android.intent.category.DEFAULT" />
// <category android:name="android.intent.category.BROWSABLE" />
// <data android:scheme="${applicationId}.braintree" />
// </intent-filter>
// </activity>;
export const addBraintreeLinks = (modResults, host, pathPrefix, addFallbackUrlScheme, initialize3DSecure) => {
const mainActivity = getMainActivityOrThrow(modResults);
const intentFilters = mainActivity['intent-filter'];
// Host is required props for a plugin
if (!host) {
WarningAggregator.addWarningAndroid('withExpoBraintree addBraintreeLinks', `No Host provided for withExpoBraintree.android addBraintreeLinks`);
}
// Check if the intent filter already exists
if (hasIntentFilter(intentFilters, host, pathPrefix)) {
WarningAggregator.addWarningAndroid('withExpoBraintree addBraintreeLinks', `withExpoBraintreeAndroid: AndroidManifest not require any changes`);
return modResults;
}
const newIntentFilter = {
action: [{
$: {
'android:name': 'android.intent.action.VIEW'
}
}],
category: [{
$: {
'android:name': 'android.intent.category.DEFAULT'
}
}, {
$: {
'android:name': 'android.intent.category.BROWSABLE'
}
}],
data: [{
$: {
'android:scheme': 'http'
}
}, {
$: {
'android:scheme': 'https'
}
}, {
$: {
'android:host': host
}
}],
$: {
'android:autoVerify': 'true'
}
};
// If there is pathPrefix then we add that
if (pathPrefix) {
newIntentFilter.data?.push({
$: {
'android:pathPrefix': pathPrefix
}
});
}
const newFallbackUrlSchemeIntentFilter = {
action: [{
$: {
'android:name': 'android.intent.action.VIEW'
}
}],
category: [{
$: {
'android:name': 'android.intent.category.DEFAULT'
}
}, {
$: {
'android:name': 'android.intent.category.BROWSABLE'
}
}],
data: [{
$: {
'android:scheme': '${applicationId}.braintree'
}
}]
};
// Add the intent-filter to the main activity
mainActivity['intent-filter'] = [...(mainActivity['intent-filter'] || []), newIntentFilter];
// If there is fallbackUrlScheme then we add that
if (initialize3DSecure === 'true' || addFallbackUrlScheme === 'true') {
// Add the intent-filter to the main activity
mainActivity['intent-filter'] = [...(mainActivity['intent-filter'] || []), newFallbackUrlSchemeIntentFilter];
}
return modResults;
};
/**
* Check if an intent-filter with the same data already exists
* @param {object} intentFilters - The AndroidManifest intent filters
* @param {string} host - The host to check
* @param {string} pathPrefix - The pathPrefix to check
* @returns {boolean} - Returns true if a matching intent filter is found
*/
function hasIntentFilter(intentFilters, host, pathPrefix) {
return intentFilters?.some(filter => {
const hasAutoVerify = filter.$ && filter.$['android:autoVerify'] === 'true';
const hasViewAction = filter.action?.some(action => action.$['android:name'] === 'android.intent.action.VIEW');
const hasDefaultCategory = filter.category?.some(category => category.$['android:name'] === 'android.intent.category.DEFAULT');
const hasBrowsableCategory = filter.category?.some(category => category.$['android:name'] === 'android.intent.category.BROWSABLE');
const hasHttpScheme = filter.data?.some(data => data.$['android:scheme'] === 'http');
const hasHttpsScheme = filter.data?.some(data => data.$['android:scheme'] === 'https');
const hasMatchingHost = filter.data?.some(data => data.$['android:host'] === host);
const hasMatchingPathPrefix = filter.data?.some(data => data.$['android:pathPrefix'] === pathPrefix);
// Check all conditions to ensure it matches the intent filter we want to add
return hasAutoVerify && hasViewAction && hasDefaultCategory && hasBrowsableCategory && hasHttpScheme && hasHttpsScheme && hasMatchingHost && (hasMatchingPathPrefix || !pathPrefix);
});
}
// Because we need the package to be added AFTER the React and Google maven packages, we create a new all projects.
// It's ok to have multiple all projects.repositories, so we create a new one since it's cheaper than tokenizing
// the existing block to find the correct place to insert our content.
const gradle3DSecureBraintreeRepo = [`allprojects {`, ` repositories {`, ` maven {`, ` url "https://cardinalcommerceprod.jfrog.io/artifactory/android"`, ` credentials {`, ` username 'braintree_team_sdk'`, ` password 'AKCp8jQcoDy2hxSWhDAUQKXLDPDx6NYRkqrgFLRc3qDrayg6rrCbJpsKKyMwaykVL8FWusJpp'`, ` }`, ` }`, ` }`, `}`].join('\n');
export const withExpoBraintreeAndroidGradle = expoConfig => {
return withProjectBuildGradle(expoConfig, config => {
if (config.modResults.language === 'groovy') {
config.modResults.contents = appendContents({
tag: 'expo-braintree-import',
src: config.modResults.contents,
newSrc: gradle3DSecureBraintreeRepo,
comment: '//'
}).contents;
} else {
throw new Error('Cannot add expo-braintree-import maven gradle because the build.gradle is not groovy');
}
return config;
});
};
export const appendContents = ({
src,
newSrc,
tag,
comment
}) => {
const header = createGeneratedHeaderComment(newSrc, tag, comment);
if (!src.includes(header)) {
// Ensure the old generated contents are removed.
const sanitizedTarget = removeGeneratedContents(src, tag);
const contentsToAdd = [
// @something
header,
// contents
newSrc,
// @end
`${comment} @generated end ${tag}`].join('\n');
return {
contents: sanitizedTarget ?? src + contentsToAdd,
didMerge: true,
didClear: !!sanitizedTarget
};
}
return {
contents: src,
didClear: false,
didMerge: false
};
};
//# sourceMappingURL=withExpoBraintree.android.js.map