@braze/expo-plugin
Version:
Config plugin for @braze/react-native-sdk package
348 lines (301 loc) • 14.3 kB
text/typescript
import { ConfigPlugin, withInfoPlist, withXcodeProject, withDangerousMod, withEntitlementsPlist } from "expo/config-plugins";
import { ConfigProps } from "./types";
const fs = require('fs');
const path = require('path');
const BRAZE_IOS_RICH_PUSH_TARGET = 'BrazeExpoRichPush';
const BRAZE_IOS_PUSH_STORY_TARGET = 'BrazeExpoPushStories';
const BRAZE_IOS_RICH_PUSH_FILES = [
'NotificationService.swift',
`${BRAZE_IOS_RICH_PUSH_TARGET}-Info.plist`,
`${BRAZE_IOS_RICH_PUSH_TARGET}.entitlements`
];
const BRAZE_IOS_PUSH_STORY_FILES = [
'NotificationViewController.swift',
`${BRAZE_IOS_PUSH_STORY_TARGET}-Info.plist`,
`${BRAZE_IOS_PUSH_STORY_TARGET}.entitlements`
];
const BRAZE_IOS_NOTIFICATION_SERVICE_POD = 'BrazeNotificationService';
const BRAZE_IOS_PUSH_STORY_POD = 'BrazePushStory';
const withBrazeInfoPlist: ConfigPlugin<ConfigProps> = (config, props) => {
return withInfoPlist(config, (config) => {
delete config.modResults.Braze;
const { iosApiKey, baseUrl } = props;
// Always create the Braze plist dictionary so non-credential settings (push,
// geofence, etc.) are available to BrazeAppDelegate for both the legacy and
// delayed initialization paths.
config.modResults.Braze = {};
if (iosApiKey) {
config.modResults.Braze.ApiKey = iosApiKey;
}
if (baseUrl) {
config.modResults.Braze.Endpoint = baseUrl;
}
if (props.sessionTimeout != null) {
config.modResults.Braze.SessionTimeout = props.sessionTimeout;
}
if (props.enableSdkAuthentication != null) {
config.modResults.Braze.EnableSDKAuth = props.enableSdkAuthentication;
}
if (props.logLevel != null) {
config.modResults.Braze.LogLevel = props.logLevel;
}
if (props.enableGeofence != null) {
config.modResults.Braze.EnableGeofence = props.enableGeofence;
}
if (props.minimumTriggerIntervalInSeconds != null) {
config.modResults.Braze.TriggerInterval = props.minimumTriggerIntervalInSeconds;
}
if (props.enableAutomaticLocationCollection != null) {
config.modResults.Braze.EnableAutomaticLocationCollection = props.enableAutomaticLocationCollection;
}
if (props.enableAutomaticGeofenceRequests != null) {
config.modResults.Braze.EnableAutomaticGeofenceRequests = props.enableAutomaticGeofenceRequests;
}
if (props.dismissModalOnOutsideTap != null) {
config.modResults.Braze.DismissModalOnOutsideTap = props.dismissModalOnOutsideTap;
}
if (props.enableBrazeIosPush != null) {
config.modResults.Braze.UseBrazePush = props.enableBrazeIosPush;
}
if (props.iosRequestPushPermissionsAutomatically != null) {
config.modResults.Braze.RequestPushPermissionsAutomatically = props.iosRequestPushPermissionsAutomatically;
}
if (props.iosPushStoryAppGroup != null) {
config.modResults.Braze.BrazePushStoryAppGroup = props.iosPushStoryAppGroup;
}
if (props.iosUseUUIDAsDeviceId != null) {
config.modResults.Braze.UseUUIDAsDeviceId = props.iosUseUUIDAsDeviceId;
}
if (props.iosForwardUniversalLinks != null) {
config.modResults.Braze.ForwardUniversalLinks = props.iosForwardUniversalLinks;
}
return config;
});
}
const withBrazeEntitlements: ConfigPlugin<ConfigProps> = (config, props) => {
return withEntitlementsPlist(config, (config) => {
// Add the app group to the main application target's entitlements.
if (props.enableBrazeIosPushStories === true && props.iosPushStoryAppGroup != null) {
const appGroupsKey = 'com.apple.security.application-groups';
const existingAppGroups = config.modResults[appGroupsKey];
if (Array.isArray(existingAppGroups) && !existingAppGroups.includes(props.iosPushStoryAppGroup)) {
config.modResults[appGroupsKey] = existingAppGroups.concat([props.iosPushStoryAppGroup]);
} else {
config.modResults[appGroupsKey] = [props.iosPushStoryAppGroup];
}
}
return config;
});
};
// Modify the Xcode project to include the Notification Service Extension and its relevant files.
const withBrazeXcodeProject: ConfigPlugin<ConfigProps> = (config, props) => {
return withXcodeProject(config, (config) => {
if (props.enableBrazeIosRichPush === true || props.enableBrazeIosPushStories === true) {
// Initialize with an empty object if these top-level objects are non-existent.
// This guarantees that the extension targets will have a destination.
const objects = config.modResults.hash.project.objects;
objects['PBXTargetDependency'] = objects['PBXTargetDependency'] || {};
objects['PBXContainerItemProxy'] = objects['PBXContainerItemProxy'] || {};
const groups = objects['PBXGroup'];
const xcconfigs = objects['XCBuildConfiguration'];
// Retrieve Swift version and code signing settings from main target to apply to dependency targets.
let swiftVersion;
let codeSignStyle;
let codeSignIdentity;
let otherCodeSigningFlags;
let developmentTeam;
let provisioningProfile;
for (const configUUID of Object.keys(xcconfigs)) {
const buildSettings = xcconfigs[configUUID].buildSettings;
if (!swiftVersion && buildSettings && buildSettings.SWIFT_VERSION) {
swiftVersion = buildSettings.SWIFT_VERSION;
codeSignStyle = buildSettings.CODE_SIGN_STYLE;
codeSignIdentity = buildSettings.CODE_SIGN_IDENTITY;
otherCodeSigningFlags = buildSettings.OTHER_CODE_SIGN_FLAGS;
developmentTeam = buildSettings.DEVELOPMENT_TEAM;
provisioningProfile = buildSettings.PROVISIONING_PROFILE_SPECIFIER;
break;
}
}
// Rich Push Notification Service Extension
if (props.enableBrazeIosRichPush === true && !config.modResults.pbxGroupByName(BRAZE_IOS_RICH_PUSH_TARGET)) {
// Add the Notification Service Extension target.
const richPushTarget = config.modResults.addTarget(
BRAZE_IOS_RICH_PUSH_TARGET,
'app_extension',
BRAZE_IOS_RICH_PUSH_TARGET,
`${config.ios?.bundleIdentifier}.${BRAZE_IOS_RICH_PUSH_TARGET}`,
);
// Add the relevant files to the PBX group.
const brazeNotificationServiceGroup = config.modResults.addPbxGroup(
BRAZE_IOS_RICH_PUSH_FILES,
BRAZE_IOS_RICH_PUSH_TARGET,
BRAZE_IOS_RICH_PUSH_TARGET,
);
for (const groupUUID of Object.keys(groups)) {
if (typeof groups[groupUUID] === 'object'
&& groups[groupUUID].name === undefined
&& groups[groupUUID].path === undefined) {
config.modResults.addToPbxGroup(brazeNotificationServiceGroup.uuid, groupUUID);
}
};
for (const configUUID of Object.keys(xcconfigs)) {
const buildSettings = xcconfigs[configUUID].buildSettings;
if (buildSettings && buildSettings.PRODUCT_NAME === `"${BRAZE_IOS_RICH_PUSH_TARGET}"`) {
buildSettings.SWIFT_VERSION = swiftVersion;
buildSettings.CODE_SIGN_ENTITLEMENTS = `${BRAZE_IOS_RICH_PUSH_TARGET}/${BRAZE_IOS_RICH_PUSH_TARGET}.entitlements`;
if (codeSignStyle) { buildSettings.CODE_SIGN_STYLE = codeSignStyle; }
if (codeSignIdentity) { buildSettings.CODE_SIGN_IDENTITY = codeSignIdentity; }
if (otherCodeSigningFlags) { buildSettings.OTHER_CODE_SIGN_FLAGS = otherCodeSigningFlags; }
if (developmentTeam) { buildSettings.DEVELOPMENT_TEAM = developmentTeam; }
if (provisioningProfile) { buildSettings.PROVISIONING_PROFILE_SPECIFIER = provisioningProfile; }
}
}
// Set up target build phase scripts.
config.modResults.addBuildPhase(
[
'NotificationService.swift',
],
'PBXSourcesBuildPhase',
'Sources',
richPushTarget.uuid
);
config.modResults.addBuildPhase(
['UserNotifications.framework'],
'PBXFrameworksBuildPhase',
'Frameworks',
richPushTarget.uuid
);
}
// Push Stories Notification Content Extension
if (props.enableBrazeIosPushStories === true
&& props.iosPushStoryAppGroup != null
&& !config.modResults.pbxGroupByName(BRAZE_IOS_PUSH_STORY_TARGET)) {
// Add the Notification Content Extension target.
const pushStoriesTarget = config.modResults.addTarget(
BRAZE_IOS_PUSH_STORY_TARGET,
'app_extension',
BRAZE_IOS_PUSH_STORY_TARGET,
`${config.ios?.bundleIdentifier}.${BRAZE_IOS_PUSH_STORY_TARGET}`,
);
// Add the relevant files to the PBX group.
const brazeNotificationServiceGroup = config.modResults.addPbxGroup(
BRAZE_IOS_PUSH_STORY_FILES,
BRAZE_IOS_PUSH_STORY_TARGET,
BRAZE_IOS_PUSH_STORY_TARGET,
);
for (const groupUUID of Object.keys(groups)) {
if (typeof groups[groupUUID] === 'object'
&& groups[groupUUID].name === undefined
&& groups[groupUUID].path === undefined) {
config.modResults.addToPbxGroup(brazeNotificationServiceGroup.uuid, groupUUID);
}
};
for (const configUUID of Object.keys(xcconfigs)) {
const buildSettings = xcconfigs[configUUID].buildSettings;
if (buildSettings && buildSettings.PRODUCT_NAME === `"${BRAZE_IOS_PUSH_STORY_TARGET}"`) {
buildSettings.BRAZE_PUSH_STORY_APP_GROUP = props.iosPushStoryAppGroup;
buildSettings.SWIFT_VERSION = swiftVersion;
buildSettings.CODE_SIGN_ENTITLEMENTS = `${BRAZE_IOS_PUSH_STORY_TARGET}/${BRAZE_IOS_PUSH_STORY_TARGET}.entitlements`;
if (codeSignStyle) { buildSettings.CODE_SIGN_STYLE = codeSignStyle; }
if (codeSignIdentity) { buildSettings.CODE_SIGN_IDENTITY = codeSignIdentity; }
if (otherCodeSigningFlags) { buildSettings.OTHER_CODE_SIGN_FLAGS = otherCodeSigningFlags; }
if (developmentTeam) { buildSettings.DEVELOPMENT_TEAM = developmentTeam; }
if (provisioningProfile) { buildSettings.PROVISIONING_PROFILE_SPECIFIER = provisioningProfile; }
}
}
// Set up target build phase scripts.
config.modResults.addBuildPhase(
[
'NotificationViewController.swift',
],
'PBXSourcesBuildPhase',
'Sources',
pushStoriesTarget.uuid
);
config.modResults.addBuildPhase(
[
'UserNotifications.framework',
'UserNotificationsUI.framework'
],
'PBXFrameworksBuildPhase',
'Frameworks',
pushStoriesTarget.uuid
);
}
}
return config;
});
};
// Direct modifications to the project files.
// Used for any operations that can't be contained within direct manipulation of the Xcode project or properties.
const withBrazeDangerousMod: ConfigPlugin<ConfigProps> = (config, props) => {
return withDangerousMod(config, [
'ios',
(config) => {
const projectRoot = config.modRequest.projectRoot;
// Modify the Podfile for rich push.
if (props.enableBrazeIosRichPush === true) {
// Copy Rich Push files to project path.
const absoluteSource = require.resolve('@braze/expo-plugin/ios/ExpoAdapterBraze/RichPush/NotificationService.swift');
const sourcePath = path.dirname(absoluteSource);
const destinationPath = `${projectRoot}/ios/${BRAZE_IOS_RICH_PUSH_TARGET}`;
if (!fs.existsSync(`${destinationPath}`)) {
fs.mkdirSync(`${destinationPath}`);
}
for (const file of BRAZE_IOS_RICH_PUSH_FILES) {
fs.copyFileSync(`${sourcePath}/${file}`, `${destinationPath}/${file}`);
}
// Modify Podfile to include `BrazeNotificationService`.
const podfilePath = `${projectRoot}/ios/Podfile`;
const podfile = fs.readFileSync(podfilePath);
if (!podfile.includes(BRAZE_IOS_NOTIFICATION_SERVICE_POD)) {
const notificationServiceTarget =
`
target '${BRAZE_IOS_RICH_PUSH_TARGET}' do
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
pod '${BRAZE_IOS_NOTIFICATION_SERVICE_POD}'
end
`
fs.appendFileSync(podfilePath, notificationServiceTarget);
}
}
// Modify the Podfile for Push Stories.
if (props.enableBrazeIosPushStories === true) {
// Copy Push Stories files to project path.
const absoluteSource = require.resolve('@braze/expo-plugin/ios/ExpoAdapterBraze/PushStories/NotificationViewController.swift');
const sourcePath = path.dirname(absoluteSource);
const destinationPath = `${projectRoot}/ios/${BRAZE_IOS_PUSH_STORY_TARGET}`;
if (!fs.existsSync(`${destinationPath}`)) {
fs.mkdirSync(`${destinationPath}`);
}
for (const file of BRAZE_IOS_PUSH_STORY_FILES) {
fs.copyFileSync(`${sourcePath}/${file}`, `${destinationPath}/${file}`);
}
// Modify Podfile to include `BrazePushStory`.
const podfilePath = `${projectRoot}/ios/Podfile`;
const podfile = fs.readFileSync(podfilePath);
if (!podfile.includes(BRAZE_IOS_PUSH_STORY_POD)) {
const notificationServiceTarget =
`
target '${BRAZE_IOS_PUSH_STORY_TARGET}' do
use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
pod '${BRAZE_IOS_PUSH_STORY_POD}'
end
`
fs.appendFileSync(podfilePath, notificationServiceTarget);
}
}
return config;
},
]);
};
export const withIOSBrazeSdk: ConfigPlugin<ConfigProps> = (config, props) => {
config = withBrazeInfoPlist(config, props);
config = withBrazeEntitlements(config, props);
config = withBrazeXcodeProject(config, props);
config = withBrazeDangerousMod(config, props);
return config;
};