UNPKG

react-native-expo-braintree

Version:

React native and expo wrapper around braintree sdk fro android and ios

164 lines (152 loc) 6.27 kB
/* eslint-disable no-bitwise */ import { IOSConfig, withAppDelegate, withInfoPlist, type ConfigPlugin, } from '@expo/config-plugins'; import eol from 'eol'; import type { ExpoBraintreePluginProps } from './withExpoBraintree'; export const withExpoBraintreeAppDelegate: ConfigPlugin< ExpoBraintreePluginProps > = (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 expoBraintreeConfigureLine = ' [BraintreeExpoConfig configure];'; let didFinishLaunchingWithOptionsElementIndex = contents.findIndex( (content) => content.includes(didFinishLaunchingWithOptions) ); const expoBraintreeConfigureLineIndex = contents.findIndex((content) => content.includes(expoBraintreeConfigureLine) ); // If didFinishLaunchingWithOptions exist in AppDelegate.mm and expoBraintreeConfigureLine do not exist if ( !~expoBraintreeConfigureLineIndex && !!~didFinishLaunchingWithOptionsElementIndex ) { contents.splice( // We are adding +2 to the index to insert content after '{' block didFinishLaunchingWithOptionsElementIndex + 2, 0, expoBraintreeConfigureLine ); } // Step 3 Add method to properly handle openUrl method in AppDelegate.m const openUrlMethod = '- (BOOL)application:(UIApplication *)application openURL'; const expoBraintreeOpenUrlLines = [ ' if ([url.scheme localizedCaseInsensitiveCompare:[BraintreeExpoConfig getPaymentUrlScheme]] == NSOrderedSame) {', ' return [BraintreeExpoConfig handleUrl:url];', ' }', ]; const openUrlMethodElementIndex = contents.findIndex((content) => content.includes(openUrlMethod) ); const expoBraintreeOpenUrlLineIndex = contents.findIndex((content) => content.includes(expoBraintreeOpenUrlLines?.[0] ?? '') ); // If openUrlMethodElementIndex exist in AppDelegate.mm and expoBraintreeOpenUrlLineIndex do not exist if (!~expoBraintreeOpenUrlLineIndex && !!~openUrlMethodElementIndex) { contents.splice( // We are adding +1 to the index to insert content after '{' block openUrlMethodElementIndex + 1, 0, ...expoBraintreeOpenUrlLines ); } config.modResults.contents = contents.join('\n'); return config; }); }; /** * Add a new wrapper Swift file to the Xcode project for Swift compatibility. */ export const withSwiftBraintreeWrapperFile: ConfigPlugin = (config) => { return IOSConfig.XcodeProjectFile.withBuildSourceFile(config, { filePath: 'BraintreeExpoConfig.swift', contents: [ 'import Braintree', 'import Foundation', '', '@objc public class BraintreeExpoConfig: 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 withExpoBraintreePlist: ConfigPlugin = (expoConfig) => { return withInfoPlist(expoConfig, (config) => { const bundleIdentifier = config.ios?.bundleIdentifier ?? ''; const bundleIdentifierWithBraintreeSchema = `${bundleIdentifier}.braintree`; const bundleUrlTypes = config.modResults.CFBundleURLTypes ?? []; // Check if an entry with the specific Braintree URL scheme already exists const isBraintreeEntryNotExist = !bundleUrlTypes.find((urlType) => { return urlType.CFBundleURLSchemes?.includes( bundleIdentifierWithBraintreeSchema ); }); // If Braintree entry doesn't exist, add a new one if (isBraintreeEntryNotExist) { bundleUrlTypes.push({ CFBundleURLSchemes: [bundleIdentifierWithBraintreeSchema], }); } // Assign the modified bundleUrlTypes back to the config config.modResults.CFBundleURLTypes = bundleUrlTypes; return config; }); }; /* * Add allowlist Venmo URL scheme * @see https://developer.paypal.com/braintree/docs/guides/venmo/client-side/ios/v6#allowlist-venmo-url-scheme */ export const withVenmoScheme: ConfigPlugin = (expoConfig) => { return withInfoPlist(expoConfig, (config) => { // Ensure LSApplicationQueriesSchemes exists in Info.plist config.modResults.LSApplicationQueriesSchemes = config.modResults.LSApplicationQueriesSchemes || []; // Hardcoded scheme for Venmo const venmoScheme = 'com.venmo.touch.v2'; // Add the Venmo scheme to the LSApplicationQueriesSchemes array if not already present if (!config.modResults.LSApplicationQueriesSchemes.includes(venmoScheme)) { config.modResults.LSApplicationQueriesSchemes.push(venmoScheme); } return config; }); };