diff --git a/apps/react-native-branch/app.json b/apps/react-native-branch/app.json index ef4ab01a..2b2c9236 100644 --- a/apps/react-native-branch/app.json +++ b/apps/react-native-branch/app.json @@ -5,22 +5,13 @@ "splash": { "image": "https://github.com/expo.png" }, - "ios": { - "config": { - "branch": { - "apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8" - } - } - }, - "android": { - "config": { - "branch": { + "plugins": [ + [ + "@config-plugins/react-native-branch", + { "apiKey": "key_live_f9f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8" } - } - }, - "plugins": [ - "@config-plugins/react-native-branch" + ] ] } } diff --git a/packages/react-native-branch/android/src/main/java/expo/modules/adapters/branch/BranchReactActivityLifecycleListener.kt b/packages/react-native-branch/android/src/main/java/expo/modules/adapters/branch/BranchReactActivityLifecycleListener.kt index 36ef72f8..ec062953 100644 --- a/packages/react-native-branch/android/src/main/java/expo/modules/adapters/branch/BranchReactActivityLifecycleListener.kt +++ b/packages/react-native-branch/android/src/main/java/expo/modules/adapters/branch/BranchReactActivityLifecycleListener.kt @@ -9,7 +9,7 @@ import io.branch.rnbranch.RNBranchModule class BranchReactActivityLifecycleListener(activityContext: Context) : ReactActivityLifecycleListener { - override fun onCreate(activity: Activity, savedInstanceState: Bundle?) { + override fun onResume(activity: Activity) { RNBranchModule.initSession(activity.getIntent().getData(), activity); } diff --git a/packages/react-native-branch/ios/ExpoAdapterBranch/BranchAppDelegate.swift b/packages/react-native-branch/ios/ExpoAdapterBranch/BranchAppDelegate.swift index 248ed7b1..8932f767 100644 --- a/packages/react-native-branch/ios/ExpoAdapterBranch/BranchAppDelegate.swift +++ b/packages/react-native-branch/ios/ExpoAdapterBranch/BranchAppDelegate.swift @@ -3,6 +3,10 @@ import RNBranch public class BranchAppDelegate: ExpoAppDelegateSubscriber { public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { + if Bundle.main.object(forInfoDictionaryKey: "branch_test_environment") as? Bool ?? false { + RNBranch.useTestInstance() + } + RNBranch.initSession(launchOptions: launchOptions, isReferrable: true) return true } diff --git a/packages/react-native-branch/src/__tests__/withBranchAndroid.test.ts b/packages/react-native-branch/src/__tests__/withBranchAndroid.test.ts index d259e820..6770c1f4 100644 --- a/packages/react-native-branch/src/__tests__/withBranchAndroid.test.ts +++ b/packages/react-native-branch/src/__tests__/withBranchAndroid.test.ts @@ -1,7 +1,10 @@ import { AndroidConfig } from "expo/config-plugins"; import { resolve } from "path"; -import { getBranchApiKey, setBranchApiKey } from "../withBranchAndroid"; +import { + setBranchApiKeys, + enableBranchTestEnvironment, +} from "../withBranchAndroid"; const { findMetaDataItem, getMainApplication, readAndroidManifestAsync } = AndroidConfig.Manifest; @@ -12,26 +15,12 @@ const sampleManifestPath = resolve( "react-native-AndroidManifest.xml", ); -describe(getBranchApiKey, () => { - it(`returns null if no android branch api key is provided`, () => { - expect(getBranchApiKey({ android: { config: {} } } as any)).toBe(null); - }); - - it(`returns apikey if android branch api key is provided`, () => { - expect( - getBranchApiKey({ - android: { config: { branch: { apiKey: "MY-API-KEY" } } }, - } as any), - ).toBe("MY-API-KEY"); - }); -}); - -describe(setBranchApiKey, () => { +describe(setBranchApiKeys, () => { it("sets branch api key in AndroidManifest.xml if given", async () => { let androidManifestJson = await readAndroidManifestAsync(sampleManifestPath); - androidManifestJson = await setBranchApiKey( - "MY-API-KEY", + androidManifestJson = await setBranchApiKeys( + { apiKey: "MY-API-KEY", testApiKey: "MY-TEST-API-KEY" }, androidManifestJson, ); let mainApplication = getMainApplication(androidManifestJson); @@ -39,14 +28,39 @@ describe(setBranchApiKey, () => { expect( findMetaDataItem(mainApplication, "io.branch.sdk.BranchKey"), ).toBeGreaterThan(-1); + expect( + findMetaDataItem(mainApplication, "io.branch.sdk.BranchKey.test"), + ).toBeGreaterThan(-1); // Unset the item - androidManifestJson = await setBranchApiKey(null, androidManifestJson); + // @ts-expect-error Explicitly unset the API keys to ensure its removed + androidManifestJson = setBranchApiKeys({}, androidManifestJson); mainApplication = getMainApplication(androidManifestJson); expect(findMetaDataItem(mainApplication, "io.branch.sdk.BranchKey")).toBe( -1, ); + expect( + findMetaDataItem(mainApplication, "io.branch.sdk.BranchKey.test"), + ).toBe(-1); + }); +}); + +describe(enableBranchTestEnvironment, () => { + it("sets branch test mode meta data item in AndroidManifest.xml", async () => { + let androidManifestJson = + await readAndroidManifestAsync(sampleManifestPath); + + androidManifestJson = await enableBranchTestEnvironment( + true, + androidManifestJson, + ); + + const mainApplication = getMainApplication(androidManifestJson); + + expect( + findMetaDataItem(mainApplication, "io.branch.sdk.TestMode"), + ).toBeGreaterThan(-1); }); }); diff --git a/packages/react-native-branch/src/__tests__/withBranchIOS.test.ts b/packages/react-native-branch/src/__tests__/withBranchIOS.test.ts index 11e0a3b0..e3f2785f 100644 --- a/packages/react-native-branch/src/__tests__/withBranchIOS.test.ts +++ b/packages/react-native-branch/src/__tests__/withBranchIOS.test.ts @@ -1,27 +1,36 @@ -import { getBranchApiKey, setBranchApiKey } from "../withBranchIOS"; +import { + setBranchApiKeys, + enableBranchTestEnvironment, +} from "../withBranchIOS"; -describe(getBranchApiKey, () => { - it(`returns null if no api key is provided`, () => { - expect(getBranchApiKey({})).toBe(null); +describe(setBranchApiKeys, () => { + it(`sets branch_key.live if the api key is given`, () => { + expect(setBranchApiKeys({ apiKey: "LIVE-API-KEY" }, {})).toMatchObject({ + branch_key: { + live: "LIVE-API-KEY", + }, + }); }); - it(`returns the api key if provided`, () => { + it(`sets branch_key.test if the test api key is given`, () => { expect( - getBranchApiKey({ ios: { config: { branch: { apiKey: "123" } } } }), - ).toBe("123"); - }); -}); - -describe(setBranchApiKey, () => { - it(`sets branch_key.live if the api key is given`, () => { - expect(setBranchApiKey("123", {})).toMatchObject({ + setBranchApiKeys( + { apiKey: "LIVE-API-KEY", testApiKey: "TEST-API-KEY" }, + {}, + ), + ).toMatchObject({ branch_key: { - live: "123", + live: "LIVE-API-KEY", + test: "TEST-API-KEY", }, }); }); +}); - it(`makes no changes to the infoPlist no api key is provided`, () => { - expect(setBranchApiKey(null, {})).toMatchObject({}); +describe(enableBranchTestEnvironment, () => { + it(`must assign the passed boolean value into branch_key.branch_test_mode`, () => { + expect(enableBranchTestEnvironment(true, {})).toMatchObject({ + branch_test_environment: true, + }); }); }); diff --git a/packages/react-native-branch/src/types.ts b/packages/react-native-branch/src/types.ts index 9930f305..eeba00f6 100644 --- a/packages/react-native-branch/src/types.ts +++ b/packages/react-native-branch/src/types.ts @@ -1,5 +1,12 @@ export type ConfigData = { apiKey?: string; + testApiKey?: string; iosAppDomain?: string; iosUniversalLinkDomains?: string[]; + enableTestEnvironment?: boolean; +}; + +export type BranchKeys = { + apiKey: string; + testApiKey?: string; }; diff --git a/packages/react-native-branch/src/withBranch.ts b/packages/react-native-branch/src/withBranch.ts index 57dc39e9..f3da11de 100644 --- a/packages/react-native-branch/src/withBranch.ts +++ b/packages/react-native-branch/src/withBranch.ts @@ -4,22 +4,17 @@ import { ConfigData } from "./types"; import { withBranchAndroid } from "./withBranchAndroid"; import { withBranchIOS } from "./withBranchIOS"; -const withBranch: ConfigPlugin = ( - config, - { apiKey, iosAppDomain, iosUniversalLinkDomains } = {}, -) => { - config = withBranchAndroid(config, { apiKey }); - config = withBranchIOS(config, { - apiKey, - iosAppDomain, - iosUniversalLinkDomains, - }); +const withBranch: ConfigPlugin = (config, branchConfig = {}) => { + config = withBranchAndroid(config, branchConfig); + config = withBranchIOS(config, branchConfig); + return config; }; let pkg: { name: string; version?: string } = { name: "react-native-branch", }; + try { const branchPkg = require("react-native-branch/package.json"); pkg = branchPkg; diff --git a/packages/react-native-branch/src/withBranchAndroid.ts b/packages/react-native-branch/src/withBranchAndroid.ts index 555ff197..598cc25d 100644 --- a/packages/react-native-branch/src/withBranchAndroid.ts +++ b/packages/react-native-branch/src/withBranchAndroid.ts @@ -1,10 +1,11 @@ -import { ExpoConfig } from "expo/config"; import { AndroidConfig, ConfigPlugin, withAndroidManifest, } from "expo/config-plugins"; +import { BranchKeys, ConfigData } from "./types"; + const { addMetaDataItemToMainApplication, getMainApplicationOrThrow, @@ -12,41 +13,81 @@ const { } = AndroidConfig.Manifest; const META_BRANCH_KEY = "io.branch.sdk.BranchKey"; +const META_BRANCH_KEY_TEST = "io.branch.sdk.BranchKey.test"; +const META_BRANCH_KEY_TEST_MODE = "io.branch.sdk.TestMode"; -export function getBranchApiKey(config: ExpoConfig) { - return config.android?.config?.branch?.apiKey ?? null; -} - -export function setBranchApiKey( - apiKey: string, +export function setBranchApiKeys( + { apiKey, testApiKey }: BranchKeys, androidManifest: AndroidConfig.Manifest.AndroidManifest, ) { const mainApplication = getMainApplicationOrThrow(androidManifest); if (apiKey) { - // If the item exists, add it back addMetaDataItemToMainApplication(mainApplication, META_BRANCH_KEY, apiKey); } else { - // Remove any existing item removeMetaDataItemFromMainApplication(mainApplication, META_BRANCH_KEY); } + if (testApiKey) { + addMetaDataItemToMainApplication( + mainApplication, + META_BRANCH_KEY_TEST, + testApiKey, + ); + } else { + removeMetaDataItemFromMainApplication( + mainApplication, + META_BRANCH_KEY_TEST, + ); + } + return androidManifest; } -export const withBranchAndroid: ConfigPlugin<{ apiKey?: string }> = ( - config, - data, -) => { - const apiKey = data.apiKey ?? getBranchApiKey(config); - if (!apiKey) { - throw new Error( - "Branch API key is required: expo.android.config.branch.apiKey", +export function enableBranchTestEnvironment( + enableTestEnvironment: boolean, + androidManifest: AndroidConfig.Manifest.AndroidManifest, +) { + const mainApplication = getMainApplicationOrThrow(androidManifest); + + addMetaDataItemToMainApplication( + mainApplication, + META_BRANCH_KEY_TEST_MODE, + `${enableTestEnvironment}`, + ); + + return androidManifest; +} + +export const withBranchAndroid: ConfigPlugin = (config, data) => { + // Fall back to the Expo Config `branch.apiKey` if not provided in plugin + // config. The `branch` property in the Expo Config is deprecated and will be + // removed in SDK 56. + // TODO(@hassankhan): Remove fallback when updating for SDK 56 + if (config.android?.config?.branch?.apiKey) { + console.warn( + "react-native-branch: Using `config.android.config.branch.apiKey` is deprecated. " + + "Pass `apiKey` directly in the plugin config instead.", ); } + const apiKey = data.apiKey ?? config.android?.config?.branch?.apiKey; + const { testApiKey, enableTestEnvironment = false } = data; + + if (!apiKey) { + throw new Error("Branch API key is required: apiKey must be provided"); + } config = withAndroidManifest(config, (config) => { - config.modResults = setBranchApiKey(apiKey, config.modResults); + config.modResults = setBranchApiKeys( + { apiKey, testApiKey }, + config.modResults, + ); + + config.modResults = enableBranchTestEnvironment( + enableTestEnvironment, + config.modResults, + ); + return config; }); diff --git a/packages/react-native-branch/src/withBranchIOS.ts b/packages/react-native-branch/src/withBranchIOS.ts index 1d728513..2d0a66a8 100644 --- a/packages/react-native-branch/src/withBranchIOS.ts +++ b/packages/react-native-branch/src/withBranchIOS.ts @@ -2,7 +2,6 @@ import { mergeContents, MergeResults, } from "@expo/config-plugins/build/utils/generateCode"; -import { type ExpoConfig } from "expo/config"; import { type ConfigPlugin, InfoPlist, @@ -13,24 +12,17 @@ import { globSync } from "glob"; import * as fs from "node:fs"; import * as path from "node:path"; -import { ConfigData } from "./types"; +import { BranchKeys, ConfigData } from "./types"; -export function getBranchApiKey(config: Pick) { - return config.ios?.config?.branch?.apiKey ?? null; -} - -export function setBranchApiKey( - apiKey: string | null, +export function setBranchApiKeys( + { apiKey, testApiKey }: BranchKeys, infoPlist: InfoPlist, ): InfoPlist { - if (apiKey === null) { - return infoPlist; - } - return { ...infoPlist, branch_key: { live: apiKey, + ...(testApiKey && { test: testApiKey }), }, }; } @@ -46,18 +38,38 @@ export function addBridgingHeaderImport(src: string): MergeResults { }); } +export function enableBranchTestEnvironment( + enableTestEnvironment: boolean, + infoPlist: InfoPlist, +) { + return { + ...infoPlist, + branch_test_environment: enableTestEnvironment, + }; +} + export const withBranchIOS: ConfigPlugin = (config, data) => { // Ensure object exist if (!config.ios) { config.ios = {}; } - const apiKey = data.apiKey ?? getBranchApiKey(config); - if (!apiKey) { - throw new Error( - "Branch API key is required: expo.ios.config.branch.apiKey", + // Fall back to the Expo Config `branch.apiKey` if not provided in plugin + // config. The `branch` property in the Expo Config is deprecated and will be + // removed in SDK 56. + // TODO(@hassankhan): Remove fallback when updating for SDK 56 + if (config.ios?.config?.branch?.apiKey) { + console.warn( + "react-native-branch: Using `config.ios.config.branch.apiKey` is deprecated. " + + "Pass `apiKey` directly in the plugin config instead.", ); } + const apiKey = data.apiKey ?? config.ios?.config?.branch?.apiKey; + const { testApiKey, enableTestEnvironment = false } = data; + + if (!apiKey) { + throw new Error("Branch API key is required: apiKey must be provided"); + } // Add `React/RCTBridge` to bridging header withDangerousMod(config, [ @@ -91,18 +103,29 @@ export const withBranchIOS: ConfigPlugin = (config, data) => { // Update the infoPlist with the branch key and branch domain config = withInfoPlist(config, (config) => { - config.modResults = setBranchApiKey(apiKey, config.modResults); + config.modResults = setBranchApiKeys( + { apiKey, testApiKey }, + config.modResults, + ); + + config.modResults = enableBranchTestEnvironment( + enableTestEnvironment, + config.modResults, + ); + if (data.iosAppDomain) { config.modResults.branch_app_domain = data.iosAppDomain; } else { delete config.modResults.branch_app_domain; } + if (data.iosUniversalLinkDomains) { config.modResults.branch_universal_link_domains = data.iosUniversalLinkDomains; } else { delete config.modResults.branch_universal_link_domains; } + return config; });