Last active
October 4, 2023 14:01
-
-
Save giautm/f6ff2d44ed9d47d36dbc689f0a710e54 to your computer and use it in GitHub Desktop.
Expo plugin to config react-native-branch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
AndroidConfig, | |
WarningAggregator, | |
withAndroidManifest, | |
withAppDelegate, | |
withDangerousMod, | |
withInfoPlist, | |
withMainActivity, | |
withPlugins, | |
} = require('@expo/config-plugins') | |
const { | |
createGeneratedHeaderComment, | |
mergeContents, | |
removeGeneratedContents, | |
} = require('@expo/config-plugins/build/utils/generateCode') | |
const fs = require('fs') | |
const path = require('path') | |
async function readFileAsync(path) { | |
return fs.promises.readFile(path, 'utf8') | |
} | |
async function saveFileAsync(path, content) { | |
return fs.promises.writeFile(path, content, 'utf8') | |
} | |
// Fork of config-plugins mergeContents, but appends the contents to the end of the file. | |
function 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 } | |
} | |
function addBranchAppDelegateImport(src) { | |
const newSrc = ['#import <RNBranch/RNBranch.h>'] | |
return mergeContents({ | |
tag: 'rn-branch-import', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /#import "AppDelegate\.h"/, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
// Match against `UMModuleRegistryAdapter` (unimodules), and React Native without unimodules (Expo Modules). | |
const MATCH_INIT = /(?:(self\.|_)(\w+)\s?=\s?\[\[UMModuleRegistryAdapter alloc\])|(?:RCTBridge\s?\*\s?(\w+)\s?=\s?\[\[RCTBridge alloc\])/g | |
function addBranchAppDelegateInit(src) { | |
const newSrc = [] | |
newSrc.push( | |
' // [RNBranch useTestInstance];', | |
' [RNBranch initSessionWithLaunchOptions:launchOptions isReferrable:YES];', | |
) | |
return mergeContents({ | |
tag: 'rn-branch-init', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: MATCH_INIT, | |
offset: 0, | |
comment: '//', | |
}) | |
} | |
function addBranchAppDelegateOpenURL(src) { | |
const newSrc = [ | |
' if ([RNBranch application:application openURL:url options:options]) {', | |
' // do other deep link routing for the Facebook SDK, Pinterest SDK, etc', | |
' }', | |
] | |
return mergeContents({ | |
tag: 'rn-branch-open-url', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /\(UIApplication \*\)application openURL:/, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
function addBranchAppDelegateContinueUserActivity(src) { | |
const newSrc = [ | |
' if ([RNBranch continueUserActivity:userActivity]) {', | |
' return YES;', | |
' }', | |
] | |
return mergeContents({ | |
tag: 'rn-branch-continue-user-activity', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /\(UIApplication \*\)application continueUserActivity:/, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
// Starting with iOS | |
function withBranchIos(config, data) { | |
// Ensure object exist | |
if (!config.ios) { | |
config.ios = {} | |
} | |
// Update the infoPlist with the branch key and branch domain | |
config = withInfoPlist(config, (config) => { | |
config.modResults.branch_app_domain = data.branch_app_domain | |
config.modResults.branch_key = data.branch_key | |
return config | |
}) | |
// Update the AppDelegate.m | |
config = withAppDelegate(config, (config) => { | |
config.modResults.contents = addBranchAppDelegateImport( | |
config.modResults.contents, | |
).contents | |
config.modResults.contents = addBranchAppDelegateInit( | |
config.modResults.contents, | |
).contents | |
config.modResults.contents = addBranchAppDelegateOpenURL( | |
config.modResults.contents, | |
).contents | |
config.modResults.contents = addBranchAppDelegateContinueUserActivity( | |
config.modResults.contents, | |
).contents | |
return config | |
}) | |
return config | |
} | |
async function editMainApplication(config, action) { | |
const mainApplicationPath = path.join( | |
config.modRequest.platformProjectRoot, | |
'app', | |
'src', | |
'main', | |
'java', | |
...config.android?.package?.split('.'), | |
'MainApplication.java', | |
) | |
try { | |
const mainApplication = action(await readFileAsync(mainApplicationPath)) | |
return await saveFileAsync(mainApplicationPath, mainApplication) | |
} catch (e) { | |
WarningAggregator.addWarningAndroid( | |
'rn-branch-plugin', | |
`Couldn't modify MainApplication.java - ${e}.`, | |
) | |
} | |
} | |
async function editProguardRules(config, action) { | |
const proguardRulesPath = path.join( | |
config.modRequest.platformProjectRoot, | |
'app', | |
'proguard-rules.pro', | |
) | |
try { | |
const proguardRules = action(await readFileAsync(proguardRulesPath)) | |
return await saveFileAsync(proguardRulesPath, proguardRules) | |
} catch (e) { | |
WarningAggregator.addWarningAndroid( | |
'rn-branch-plugin', | |
`Couldn't modify proguard-rules.pro - ${e}.`, | |
) | |
} | |
} | |
function addBranchMainApplicationImport(src, packageId) { | |
const newSrc = ['import io.branch.rnbranch.RNBranchModule;'] | |
return mergeContents({ | |
tag: 'rn-branch-import', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: `package ${packageId};`, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
function addBranchGetAutoInstance(src) { | |
const newSrc = [' RNBranchModule.getAutoInstance(this);'] | |
return mergeContents({ | |
tag: 'rn-branch-auto-instance', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /super\.onCreate\(\);/, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
function addBranchMainActivityImport(src, packageId) { | |
const newSrc = [ | |
'import android.content.Intent;', | |
'import io.branch.rnbranch.*;', | |
] | |
return mergeContents({ | |
tag: 'rn-branch-import', | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: `package ${packageId};`, | |
offset: 1, | |
comment: '//', | |
}) | |
} | |
function addBranchInitSession(src) { | |
const tag = 'rn-branch-init-session' | |
try { | |
const newSrc = [ | |
' RNBranchModule.initSession(getIntent().getData(), this);', | |
] | |
return mergeContents({ | |
tag, | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /super\.onStart\(\);/, | |
offset: 1, | |
comment: '//', | |
}) | |
} catch (err) { | |
if (err.code !== 'ERR_NO_MATCH') { | |
throw err | |
} | |
} | |
const newSrc = [ | |
' @Override', | |
' protected void onStart() {', | |
' super.onStart();', | |
' RNBranchModule.initSession(getIntent().getData(), this);', | |
' }', | |
] | |
return mergeContents({ | |
tag, | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: `getMainComponentName`, | |
offset: 3, | |
comment: '//', | |
}) | |
} | |
function addBranchOnNewIntent(src) { | |
const tag = 'rn-branch-on-new-intent' | |
try { | |
const newSrc = [' RNBranchModule.onNewIntent(intent);'] | |
return mergeContents({ | |
tag, | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: /super\.onNewIntent\(intent\);/, | |
offset: 1, | |
comment: '//', | |
}) | |
} catch (err) { | |
if (err.code !== 'ERR_NO_MATCH') { | |
throw err | |
} | |
} | |
const newSrc = [ | |
' @Override', | |
' public void onNewIntent(Intent intent) {', | |
' super.onNewIntent(intent);', | |
' RNBranchModule.onNewIntent(intent);', | |
' }', | |
] | |
return mergeContents({ | |
tag, | |
src, | |
newSrc: newSrc.join('\n'), | |
anchor: `getMainComponentName`, | |
offset: 3, | |
comment: '//', | |
}) | |
} | |
const { | |
addMetaDataItemToMainApplication, | |
getMainApplicationOrThrow, | |
} = AndroidConfig.Manifest | |
// Splitting this function out of the mod makes it easier to test. | |
async function setCustomConfigAsync(config, androidManifest, data) { | |
// Get the <application /> tag and assert if it doesn't exist. | |
const mainApplication = getMainApplicationOrThrow(androidManifest) | |
addMetaDataItemToMainApplication( | |
mainApplication, | |
// value for `android:name` | |
'io.branch.sdk.BranchKey', | |
// value for `android:value` | |
data.branch_key, | |
) | |
return androidManifest | |
} | |
function withBranchAndroid(config, data) { | |
// Insert the branch_key into the AndroidManifest | |
config = withAndroidManifest(config, async (config) => { | |
// Modifiers can be async, but try to keep them fast. | |
config.modResults = await setCustomConfigAsync( | |
config, | |
config.modResults, | |
data, | |
) | |
return config | |
}) | |
// Directly edit MainApplication.java | |
config = withDangerousMod(config, [ | |
'android', | |
async (config) => { | |
await editMainApplication(config, (mainApplication) => { | |
mainApplication = addBranchMainApplicationImport( | |
mainApplication, | |
config.android?.package, | |
).contents | |
mainApplication = addBranchGetAutoInstance(mainApplication).contents | |
return mainApplication | |
}) | |
return config | |
}, | |
]) | |
// Update proguard rules directly | |
config = withDangerousMod(config, [ | |
'android', | |
async (config) => { | |
await editProguardRules(config, (proguardRules) => { | |
return appendContents({ | |
tag: 'rn-branch-dont-warn', | |
src: proguardRules, | |
newSrc: ['-dontwarn io.branch.**'].join('\n'), | |
comment: '#', | |
}).contents | |
}) | |
return config | |
}, | |
]) | |
// Insert the required Branch code into MainActivity.java | |
config = withMainActivity(config, (config) => { | |
config.modResults.contents = addBranchMainActivityImport( | |
config.modResults.contents, | |
config.android?.package, | |
).contents | |
config.modResults.contents = addBranchInitSession( | |
config.modResults.contents, | |
).contents | |
config.modResults.contents = addBranchOnNewIntent( | |
config.modResults.contents, | |
).contents | |
return config | |
}) | |
return config | |
} | |
module.exports = (config, data) => | |
withPlugins(config, [ | |
[withBranchIos, data], | |
[withBranchAndroid, data], | |
]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment