airship-expo-plugin
Version:
Airship Expo config plugin
267 lines (266 loc) • 13.7 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withAirshipIOS = void 0;
const config_plugins_1 = require("@expo/config-plugins");
const fs_1 = require("fs");
const path_1 = require("path");
const assert_1 = __importDefault(require("assert"));
const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
const DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME = "NotificationServiceExtension";
const NOTIFICATION_SERVICE_FILE_NAME = "AirshipNotificationService.swift";
const NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME = "NotificationServiceExtension-Info.plist";
const AIRSHP_PLUGIN_EXTENDER_DIR_NAME = "AirshipPluginExtender";
const withCapabilities = (config, props) => {
return (0, config_plugins_1.withInfoPlist)(config, (plist) => {
if (!Array.isArray(plist.modResults.UIBackgroundModes)) {
plist.modResults.UIBackgroundModes = [];
}
if (!plist.modResults.UIBackgroundModes.includes("remote-notification")) {
plist.modResults.UIBackgroundModes.push("remote-notification");
}
return plist;
});
};
const withAPNSEnvironment = (config, props) => {
return (0, config_plugins_1.withEntitlementsPlist)(config, (plist) => {
plist.modResults['aps-environment'] = props.mode;
return plist;
});
};
const withNotificationServiceExtension = (config, props) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'ios',
async (config) => {
await writeNotificationServiceFilesAsync(props, config.modRequest.projectRoot);
return config;
},
]);
};
async function writeNotificationServiceFilesAsync(props, projectRoot) {
if (!props.notificationService) {
return;
}
const pluginDir = require.resolve("airship-expo-plugin/package.json");
const sourceDir = (0, path_1.join)(pluginDir, "../plugin/NotificationServiceExtension/");
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME;
const extensionPath = (0, path_1.join)(projectRoot, "ios", targetName);
if (!(0, fs_1.existsSync)(extensionPath)) {
(0, fs_1.mkdirSync)(extensionPath, { recursive: true });
}
// Copy the NotificationService.swift file into the iOS expo project as AirshipNotificationService.swift.
var notificationServiceFile = props.notificationService == "DEFAULT_AIRSHIP_SERVICE_EXTENSION" ? (0, path_1.join)(sourceDir, NOTIFICATION_SERVICE_FILE_NAME) : props.notificationService;
(0, fs_1.readFile)(notificationServiceFile, 'utf8', (err, data) => {
if (err || !data) {
console.error("Airship couldn't read file " + notificationServiceFile);
console.error(err);
return;
}
if (!props.notificationServiceInfo) {
const regexp = /class [A-Za-z]+:/;
const newSubStr = "class AirshipNotificationService:";
data = data.replace(regexp, newSubStr);
}
(0, fs_1.writeFileSync)((0, path_1.join)(extensionPath, NOTIFICATION_SERVICE_FILE_NAME), data);
});
// Copy the Info.plist (default to NotificationServiceExtension-Info.plist if null) file into the iOS expo project.
(0, fs_1.readFile)(props.notificationServiceInfo ?? (0, path_1.join)(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME), 'utf8', (err, data) => {
if (err || !data) {
console.error("Airship couldn't read file " + (props.notificationServiceInfo ?? (0, path_1.join)(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME)));
console.error(err);
return;
}
const infoPlistFilename = props.notificationServiceTargetName ? props.notificationServiceTargetName + "-Info.plist" : NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME;
(0, fs_1.writeFileSync)((0, path_1.join)(extensionPath, infoPlistFilename), data);
});
}
;
const withExtensionTargetInXcodeProject = (config, props) => {
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME;
return (0, config_plugins_1.withXcodeProject)(config, newConfig => {
const xcodeProject = newConfig.modResults;
if (!!xcodeProject.pbxTargetByName(targetName)) {
console.log(targetName + " already exists in project. Skipping...");
return newConfig;
}
const infoPlistFilename = props.notificationServiceTargetName ? props.notificationServiceTargetName + "-Info.plist" : NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME;
// Create new PBXGroup for the extension
const extGroup = xcodeProject.addPbxGroup([NOTIFICATION_SERVICE_FILE_NAME, infoPlistFilename], targetName, targetName);
// Add the new PBXGroup to the top level group. This makes the
// files / folder appear in the file explorer in Xcode.
const groups = xcodeProject.hash.project.objects["PBXGroup"];
Object.keys(groups).forEach(function (key) {
if (typeof groups[key] === "object" && groups[key].name === undefined && groups[key].path === undefined) {
xcodeProject.addToPbxGroup(extGroup.uuid, key);
}
});
// WORK AROUND for xcodeProject.addTarget BUG (making the pod install to fail somehow)
// Xcode projects don't contain these if there is only one target in the app
// An upstream fix should be made to the code referenced in this link:
// - https://github.com/apache/cordova-node-xcode/blob/8b98cabc5978359db88dc9ff2d4c015cba40f150/lib/pbxProject.js#L860
const projObjects = xcodeProject.hash.project.objects;
projObjects['PBXTargetDependency'] = projObjects['PBXTargetDependency'] || {};
projObjects['PBXContainerItemProxy'] = projObjects['PBXTargetDependency'] || {};
// Add the Notification Service Extension Target
// This adds PBXTargetDependency and PBXContainerItemProxy
const notificationServiceExtensionTarget = xcodeProject.addTarget(targetName, "app_extension", targetName, `${config.ios?.bundleIdentifier}.${targetName}`);
// Add build phases to the new Target
xcodeProject.addBuildPhase([NOTIFICATION_SERVICE_FILE_NAME], "PBXSourcesBuildPhase", "Sources", notificationServiceExtensionTarget.uuid);
xcodeProject.addBuildPhase([], "PBXResourcesBuildPhase", "Resources", notificationServiceExtensionTarget.uuid);
xcodeProject.addBuildPhase([], "PBXFrameworksBuildPhase", "Frameworks", notificationServiceExtensionTarget.uuid);
// Edit the new Target Build Settings and Deployment info
const configurations = xcodeProject.pbxXCBuildConfigurationSection();
for (const key in configurations) {
if (typeof configurations[key].buildSettings !== "undefined"
&& configurations[key].buildSettings.PRODUCT_NAME == `"${targetName}"`) {
const buildSettingsObj = configurations[key].buildSettings;
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = props.deploymentTarget ?? "15.0";
buildSettingsObj.SWIFT_VERSION = "5.0";
buildSettingsObj.DEVELOPMENT_TEAM = props?.developmentTeamID;
buildSettingsObj.CODE_SIGN_STYLE = "Automatic";
}
}
// Add development teams to the target
xcodeProject.addTargetAttribute("DevelopmentTeam", props?.developmentTeamID, notificationServiceExtensionTarget);
return newConfig;
});
};
const withAirshipServiceExtensionPod = (config, props) => {
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME;
return (0, config_plugins_1.withPodfile)(config, async (config) => {
const airshipServiceExtensionPodfileSnippet = `
target '${targetName}' 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 'AirshipServiceExtension'
end
`;
let results;
try {
results = (0, generateCode_1.mergeContents)({
tag: "AirshipServiceExtension",
src: config.modResults.contents,
newSrc: airshipServiceExtensionPodfileSnippet,
anchor: /target .* do/,
offset: 0,
comment: '#'
});
}
catch (error) {
if (error.code === 'ERR_NO_MATCH') {
throw new Error(`Cannot add AirshipServiceExtension to the project's ios/Podfile because it's malformed. Please report this with a copy of your project Podfile.`);
}
throw error;
}
if (results.didMerge || results.didClear) {
config.modResults.contents = results.contents;
}
return config;
});
};
const withEasManagedCredentials = (config, props) => {
(0, assert_1.default)(config.ios?.bundleIdentifier, "Missing 'ios.bundleIdentifier' in app config.");
config.extra = getEasManagedCredentialsConfigExtra(config, props);
return config;
};
function getEasManagedCredentialsConfigExtra(config, props) {
return {
...config.extra,
eas: {
...config.extra?.eas,
build: {
...config.extra?.eas?.build,
experimental: {
...config.extra?.eas?.build?.experimental,
ios: {
...config.extra?.eas?.build?.experimental?.ios,
appExtensions: [
...(config.extra?.eas?.build?.experimental?.ios?.appExtensions ?? []),
{
// Sync up with the new target
targetName: props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME,
bundleIdentifier: `${config?.ios?.bundleIdentifier}.${props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME}`,
}
]
}
}
}
}
};
}
const withAirshipExtender = (config, props) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'ios',
async (config) => {
await writeAirshipExtenderFileAsync(props, config.modRequest.projectRoot);
return config;
},
]);
};
async function writeAirshipExtenderFileAsync(props, projectRoot) {
if (!props.airshipExtender) {
return;
}
const fileName = (0, path_1.basename)(props.airshipExtender);
const extenderDestinationPath = (0, path_1.join)(projectRoot, "ios", AIRSHP_PLUGIN_EXTENDER_DIR_NAME);
if (!(0, fs_1.existsSync)(extenderDestinationPath)) {
(0, fs_1.mkdirSync)(extenderDestinationPath, { recursive: true });
}
// Copy the Airship Extender file into the iOS expo project.
(0, fs_1.readFile)(props.airshipExtender, 'utf8', (err, data) => {
if (err || !data) {
console.error("Airship couldn't read file " + (props.airshipExtender));
console.error(err);
return;
}
(0, fs_1.writeFileSync)((0, path_1.join)(extenderDestinationPath, fileName), data);
});
}
;
const withAirshipExtenderInMainTarget = (config, props) => {
return (0, config_plugins_1.withXcodeProject)(config, newConfig => {
const xcodeProject = newConfig.modResults;
if (!props.airshipExtender) {
return newConfig;
}
if (!!xcodeProject.pbxGroupByName(AIRSHP_PLUGIN_EXTENDER_DIR_NAME)) {
console.log(AIRSHP_PLUGIN_EXTENDER_DIR_NAME + " already exists in project. Skipping...");
return newConfig;
}
const extenderFileName = (0, path_1.basename)(props.airshipExtender);
const mainAppTarget = xcodeProject.getFirstTarget();
// Create new PBXGroup for the Airship Extender
const extGroup = xcodeProject.addPbxGroup([], AIRSHP_PLUGIN_EXTENDER_DIR_NAME, AIRSHP_PLUGIN_EXTENDER_DIR_NAME);
// Add the new PBXGroup to the top level group. This makes the
// files / folder appear in the file explorer in Xcode.
const groups = xcodeProject.hash.project.objects["PBXGroup"];
Object.keys(groups).forEach(function (key) {
if (typeof groups[key] === "object" && groups[key].name === undefined && groups[key].path === undefined) {
xcodeProject.addToPbxGroup(extGroup.uuid, key);
}
});
// Add the AirshipExtender source file to the Main Target Build Phases
xcodeProject.addSourceFile(extenderFileName, {
target: mainAppTarget.uuid
}, extGroup.uuid);
return newConfig;
});
};
const withAirshipIOS = (config, props) => {
config = withCapabilities(config, props);
config = withAPNSEnvironment(config, props);
if (props.notificationService) {
config = withNotificationServiceExtension(config, props);
config = withExtensionTargetInXcodeProject(config, props);
config = withAirshipServiceExtensionPod(config, props);
config = withEasManagedCredentials(config, props);
}
if (props.airshipExtender) {
config = withAirshipExtender(config, props);
config = withAirshipExtenderInMainTarget(config, props);
}
return config;
};
exports.withAirshipIOS = withAirshipIOS;