-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [2.1.0] Add expo compatibility plugin (#11)
* feat: add expo plugin * chore: version change ---------
- Loading branch information
1 parent
e3fc6bb
commit bf9f7af
Showing
7 changed files
with
2,928 additions
and
79 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./lib/commonjs/plugin/withReactNativePaypalReborn'); |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { | ||
withAndroidManifest, | ||
AndroidConfig, | ||
type ConfigPlugin, | ||
} from '@expo/config-plugins'; | ||
|
||
const { getMainActivityOrThrow } = AndroidConfig.Manifest; | ||
|
||
export const withReactNativePaypalRebornAndroid: ConfigPlugin = ( | ||
expoConfig | ||
) => { | ||
return withAndroidManifest(expoConfig, (config) => { | ||
config.modResults = addPaypalIntentFilter(config.modResults); | ||
return config; | ||
}); | ||
}; | ||
|
||
type ManifestData = { | ||
$: { | ||
[key: string]: string | undefined; | ||
'android:host'?: string; | ||
'android:pathPrefix'?: string; | ||
'android:scheme'?: string; | ||
}; | ||
}; | ||
|
||
// Add new intent filter | ||
// <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>; | ||
const intentActionView = 'android.intent.action.VIEW'; | ||
const intentCategoryDefault = 'android.intent.category.DEFAULT'; | ||
const intentCategoryBrowsable = 'android.intent.category.BROWSABLE'; | ||
const intentDataBraintree = '${applicationId}.braintree'; | ||
|
||
export const addPaypalIntentFilter = ( | ||
modResults: AndroidConfig.Manifest.AndroidManifest | ||
): AndroidConfig.Manifest.AndroidManifest => { | ||
const mainActivity = getMainActivityOrThrow(modResults); | ||
// We want always to add the data to the first intent filter | ||
const intentFilters = mainActivity['intent-filter']; | ||
if (!intentFilters?.length) { | ||
console.warn( | ||
'withReactNativePaypalRebornAndroid.addPaypalIntentFilter: No .Intent Filters' | ||
); | ||
return modResults; | ||
} | ||
const { | ||
isIntentActionExist, | ||
isIntentCategoryBrowsableExist, | ||
isIntentCategoryDefaultExist, | ||
isIntentDataBraintreeExist, | ||
} = checkAndroidManifestData(intentFilters); | ||
|
||
if ( | ||
isIntentActionExist && | ||
isIntentCategoryBrowsableExist && | ||
isIntentCategoryDefaultExist && | ||
isIntentDataBraintreeExist | ||
) { | ||
console.warn( | ||
'withReactNativePaypalRebornAndroid: AndroidManifest not require any changes' | ||
); | ||
return modResults; | ||
} | ||
intentFilters.push({ | ||
action: [ | ||
{ | ||
$: { 'android:name': intentActionView }, | ||
}, | ||
], | ||
category: [ | ||
{ $: { 'android:name': intentCategoryDefault } }, | ||
{ $: { 'android:name': intentCategoryBrowsable } }, | ||
], | ||
data: [{ $: { 'android:scheme': '${applicationId}.braintree' } }], | ||
}); | ||
return modResults; | ||
}; | ||
|
||
const checkAndroidManifestData = ( | ||
intentFilters: AndroidConfig.Manifest.ManifestIntentFilter[] | ||
) => ({ | ||
isIntentActionExist: isElementInAndroidManifestExist( | ||
intentFilters, | ||
intentActionView, | ||
'action' | ||
), | ||
isIntentCategoryDefaultExist: isElementInAndroidManifestExist( | ||
intentFilters, | ||
intentCategoryDefault, | ||
'category' | ||
), | ||
isIntentCategoryBrowsableExist: isElementInAndroidManifestExist( | ||
intentFilters, | ||
intentCategoryBrowsable, | ||
'category' | ||
), | ||
isIntentDataBraintreeExist: isElementInAndroidManifestExist( | ||
intentFilters, | ||
intentDataBraintree, | ||
'data' | ||
), | ||
}); | ||
|
||
const isElementInAndroidManifestExist = ( | ||
intentFilters: AndroidConfig.Manifest.ManifestIntentFilter[] | undefined, | ||
value: string, | ||
type: 'action' | 'data' | 'category' | ||
) => | ||
!!intentFilters?.some((intentFilter) => | ||
intentFilter[type]?.find((item) => { | ||
switch (type) { | ||
case 'action': | ||
case 'category': | ||
return item.$['android:name'] === value; | ||
case 'data': | ||
const typedItem = item as ManifestData; | ||
return typedItem.$['android:scheme'] === value; | ||
} | ||
}) | ||
); |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
/* eslint-disable no-bitwise */ | ||
import { | ||
withAppDelegate, | ||
withInfoPlist, | ||
type ConfigPlugin, | ||
IOSConfig, | ||
} from '@expo/config-plugins'; | ||
import eol from 'eol'; | ||
import type { ReactNativePaypalRebornPluginProps } from './withReactNativePaypalReborn'; | ||
|
||
export const withReactNativePaypalRebornAppDelegate: ConfigPlugin< | ||
ReactNativePaypalRebornPluginProps | ||
> = (expoConfig, { xCodeProjectAppName }) => { | ||
return withAppDelegate(expoConfig, (config) => { | ||
const appDelegate = config.modResults; | ||
let contents = eol.split(appDelegate.contents); | ||
// Step 1 Edit Import part | ||
// Editing import part for -swift.h file to be able to use Braintree | ||
const importSwiftHeaderFileContent = `#import "${xCodeProjectAppName}-Swift.h"`; | ||
const importSwiftHeaderFileIndex = contents.findIndex((content) => | ||
content.includes(importSwiftHeaderFileContent) | ||
); | ||
// If importSwiftHeaderFileContent do not exist in AppDelegate.mm | ||
if (!~importSwiftHeaderFileIndex) { | ||
contents = [importSwiftHeaderFileContent, ...contents]; | ||
} | ||
const importExpoModulesSwiftHeader = `#import "ExpoModulesCore-Swift.h"`; | ||
const importExpoModulesSwiftHeaderFileIndex = contents.findIndex( | ||
(content) => content.includes(importExpoModulesSwiftHeader) | ||
); | ||
// If importExpoModulesSwiftHeader do not exist in AppDelegate.mm | ||
if (!~importExpoModulesSwiftHeaderFileIndex) { | ||
contents = [importExpoModulesSwiftHeader, ...contents]; | ||
} | ||
// Step 2 Add configure method in didFinishLaunchingWithOptions | ||
const didFinishLaunchingWithOptions = 'didFinishLaunchingWithOptions'; | ||
const payPalRebornConfigureLine = ' [PaypalRebornConfig configure];'; | ||
let didFinishLaunchingWithOptionsElementIndex = contents.findIndex( | ||
(content) => content.includes(didFinishLaunchingWithOptions) | ||
); | ||
const payPalRebornConfigureLineIndex = contents.findIndex((content) => | ||
content.includes(payPalRebornConfigureLine) | ||
); | ||
// If didFinishLaunchingWithOptions exist in AppDelegate.mm and payPalRebornConfigureLine do not exist | ||
if ( | ||
!~payPalRebornConfigureLineIndex && | ||
!!~didFinishLaunchingWithOptionsElementIndex | ||
) { | ||
contents.splice( | ||
// We are adding +2 to the index to insert content after '{' block | ||
didFinishLaunchingWithOptionsElementIndex + 2, | ||
0, | ||
payPalRebornConfigureLine | ||
); | ||
} | ||
// Step 3 Add method to properly handle openUrl method in AppDelegate.m | ||
const openUrlMethod = | ||
'- (BOOL)application:(UIApplication *)application openURL'; | ||
const payPalRebornOpenUrlLines = [ | ||
' if ([url.scheme localizedCaseInsensitiveCompare:[PaypalRebornConfig getPaymentUrlScheme]] == NSOrderedSame) {', | ||
' return [PaypalRebornConfig handleUrl:url];', | ||
' }', | ||
]; | ||
const openUrlMethodElementIndex = contents.findIndex((content) => | ||
content.includes(openUrlMethod) | ||
); | ||
const payPalRebornOpenUrlLineIndex = contents.findIndex((content) => | ||
content.includes(payPalRebornOpenUrlLines?.[0] ?? '') | ||
); | ||
// If openUrlMethodElementIndex exist in AppDelegate.mm and payPalRebornOpenUrlLineIndex do not exist | ||
if (!~payPalRebornOpenUrlLineIndex && !!~openUrlMethodElementIndex) { | ||
contents.splice( | ||
// We are adding +1 to the index to insert content after '{' block | ||
openUrlMethodElementIndex + 1, | ||
0, | ||
...payPalRebornOpenUrlLines | ||
); | ||
} | ||
config.modResults.contents = contents.join('\n'); | ||
return config; | ||
}); | ||
}; | ||
|
||
/** | ||
* Add a new wrapper Swift file to the Xcode project for Swift compatibility. | ||
*/ | ||
export const withSwiftPaypalRebornWrapperFile: ConfigPlugin = (config) => { | ||
return IOSConfig.XcodeProjectFile.withBuildSourceFile(config, { | ||
filePath: 'PaypalRebornConfig.swift', | ||
contents: [ | ||
'import Braintree', | ||
'import Foundation', | ||
'', | ||
'@objc public class PaypalRebornConfig: NSObject {', | ||
'', | ||
'@objc(configure)', | ||
'public static func configure() {', | ||
' BTAppContextSwitcher.sharedInstance.returnURLScheme = self.getPaymentUrlScheme()', | ||
'}', | ||
'', | ||
'@objc(getPaymentUrlScheme)', | ||
'public static func getPaymentUrlScheme() -> String {', | ||
' let bundleIdentifier = Bundle.main.bundleIdentifier ?? ""', | ||
' return bundleIdentifier + ".braintree"', | ||
'}', | ||
'', | ||
'@objc(handleUrl:)', | ||
'public static func handleUrl(url: URL) -> Bool {', | ||
' return BTAppContextSwitcher.sharedInstance.handleOpen(url)', | ||
'}', | ||
'}', | ||
].join('\n'), | ||
}); | ||
}; | ||
|
||
export const withReactNativePaypalRebornPlist: ConfigPlugin = (expoConfig) => { | ||
return withInfoPlist(expoConfig, (config) => { | ||
const bundleIdentifier = config.ios?.bundleIdentifier ?? ''; | ||
const bundleIdentifierWithBraintreeSchema = `${bundleIdentifier}.braintree`; | ||
const bundleUrlTypes = config.modResults.CFBundleURLTypes; | ||
const isBraintreeSchemaNotExist = !bundleUrlTypes?.find((urlTypes) => { | ||
urlTypes.CFBundleURLSchemes.includes(bundleIdentifierWithBraintreeSchema); | ||
}); | ||
// If Braintree url schema for specific bundle id not exist then add this entry | ||
if (isBraintreeSchemaNotExist) { | ||
config.modResults.CFBundleURLTypes = bundleUrlTypes?.map( | ||
(bundleUrlType) => { | ||
const isUrlSchemaContainBundleIdentifier = | ||
bundleUrlType.CFBundleURLSchemes.includes(bundleIdentifier); | ||
if (isUrlSchemaContainBundleIdentifier) { | ||
bundleUrlType.CFBundleURLSchemes.push( | ||
bundleIdentifierWithBraintreeSchema | ||
); | ||
} | ||
return bundleUrlType; | ||
} | ||
); | ||
} | ||
return config; | ||
}); | ||
}; |
Oops, something went wrong.