Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add devTeam and default service extension #8

Merged
merged 9 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Airship Expo Plugin Changelog

## Version 1.3.2-beta - December 013, 2024
Beta version that fixes the Notification Service Extension for EAS builds.

## Version 1.3.1 - December 06, 2024
Patch version that fixes the support for Aiship Notification Service Extension when using `use_frameworks`.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "airship-expo-plugin",
"version": "1.3.1",
"version": "1.3.2-beta",
"description": "Airship Expo config plugin",
"main": "./app.plugin.js",
"scripts": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// NotificationService.swift


import AirshipServiceExtension

class AirshipNotificationService: UANotificationServiceExtension {

}
10 changes: 9 additions & 1 deletion plugin/src/withAirship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,20 @@ export type AirshipIOSPluginProps = {
/**
* Optional. The local path to a custom Notification Service Extension.
*/
notificationService?: string;
notificationService?: 'DEFAULT_AIRSHIP_SERVICE_EXTENSION' | string;
/**
* Optional. Airship will use a default one if not provided.
* The local path to a Notification Service Extension Info.plist.
*/
notificationServiceInfo?: string;
/**
* Optional. Defaults to NotificationServiceExtension if not provided.
*/
notificationServiceTargetName?: string;
/**
* Optional. The Apple Development Team ID used to configure the Notification Service Extension target.
*/
developmentTeamID?: string;
}

export type AirshipPluginProps = {
Expand Down
85 changes: 66 additions & 19 deletions plugin/src/withAirshipIOS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import {

import { readFile, writeFileSync, existsSync, mkdirSync } from 'fs';
import { basename, join } from 'path';
import assert from 'assert';

import { ExpoConfig } from '@expo/config-types';

import { AirshipIOSPluginProps } from './withAirship';
import { mergeContents, MergeResults } from '@expo/config-plugins/build/utils/generateCode';

const NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME = "AirshipNotificationServiceExtension";
const DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME = "NotificationServiceExtension";
const NOTIFICATION_SERVICE_FILE_NAME = "AirshipNotificationService.swift";
const NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME = "AirshipNotificationServiceExtension-Info.plist";
const NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME = "NotificationServiceExtension-Info.plist";

const withCapabilities: ConfigPlugin<AirshipIOSPluginProps> = (config, props) => {
return withInfoPlist(config, (plist) => {
Expand Down Expand Up @@ -54,17 +57,18 @@ async function writeNotificationServiceFilesAsync(props: AirshipIOSPluginProps,

const pluginDir = require.resolve("airship-expo-plugin/package.json");
const sourceDir = join(pluginDir, "../plugin/NotificationServiceExtension/");

const extensionPath = join(projectRoot, "ios", NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME);
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME
const extensionPath = join(projectRoot, "ios", targetName);

if (!existsSync(extensionPath)) {
mkdirSync(extensionPath, { recursive: true });
}

// Copy the NotificationService.swift file into the iOS expo project as AirshipNotificationService.swift.
readFile(props.notificationService, 'utf8', (err, data) => {
var notificationServiceFile = props.notificationService == "DEFAULT_AIRSHIP_SERVICE_EXTENSION" ? join(sourceDir, NOTIFICATION_SERVICE_FILE_NAME) : props.notificationService;
readFile(notificationServiceFile, 'utf8', (err, data) => {
if (err || !data) {
console.error("Airship couldn't read file " + props.notificationService);
console.error("Airship couldn't read file " + notificationServiceFile);
console.error(err);
return;
}
Expand All @@ -78,31 +82,34 @@ async function writeNotificationServiceFilesAsync(props: AirshipIOSPluginProps,
writeFileSync(join(extensionPath, NOTIFICATION_SERVICE_FILE_NAME), data);
});

// Copy the Info.plist (default to AirshipNotificationServiceExtension-Info.plist if null) file into the iOS expo project as AirshipNotificationServiceExtension-Info.plist.
// Copy the Info.plist (default to NotificationServiceExtension-Info.plist if null) file into the iOS expo project.
readFile(props.notificationServiceInfo ?? join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME), 'utf8', (err, data) => {
if (err || !data) {
console.error("Airship couldn't read file " + (props.notificationServiceInfo ?? join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME)));
console.error(err);
return;
}
writeFileSync(join(extensionPath, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME), data);
const infoPlistFilename = props.notificationServiceTargetName ? props.notificationServiceTargetName + "-Info.plist" : NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME;
writeFileSync(join(extensionPath, infoPlistFilename), data);
});
};

const withExtensionTargetInXcodeProject: ConfigPlugin<AirshipIOSPluginProps> = (config, props) => {
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME

return withXcodeProject(config, newConfig => {
const xcodeProject = newConfig.modResults;

if (!!xcodeProject.pbxTargetByName(NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME)) {
console.log(NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME + " already exists in project. Skipping...");
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, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME],
NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME,
NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME
[NOTIFICATION_SERVICE_FILE_NAME, infoPlistFilename],
targetName,
targetName
);

// Add the new PBXGroup to the top level group. This makes the
Expand All @@ -125,10 +132,10 @@ const withExtensionTargetInXcodeProject: ConfigPlugin<AirshipIOSPluginProps> = (
// Add the Notification Service Extension Target
// This adds PBXTargetDependency and PBXContainerItemProxy
const notificationServiceExtensionTarget = xcodeProject.addTarget(
NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME,
targetName,
"app_extension",
NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME,
`${config.ios?.bundleIdentifier}.${NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME}`
targetName,
`${config.ios?.bundleIdentifier}.${targetName}`
);

// Add build phases to the new Target
Expand All @@ -155,22 +162,29 @@ const withExtensionTargetInXcodeProject: ConfigPlugin<AirshipIOSPluginProps> = (
const configurations = xcodeProject.pbxXCBuildConfigurationSection();
for (const key in configurations) {
if (typeof configurations[key].buildSettings !== "undefined"
&& configurations[key].buildSettings.PRODUCT_NAME == `"${NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME}"`
&& configurations[key].buildSettings.PRODUCT_NAME == `"${targetName}"`
) {
const buildSettingsObj = configurations[key].buildSettings;
buildSettingsObj.IPHONEOS_DEPLOYMENT_TARGET = "14.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: ConfigPlugin<AirshipIOSPluginProps> = (config, props) => {
const targetName = props.notificationServiceTargetName ?? DEFAULT_NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME

return withPodfile(config, async (config) => {
const airshipServiceExtensionPodfileSnippet = `
target '${NOTIFICATION_SERVICE_EXTENSION_TARGET_NAME}' do
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'
Expand Down Expand Up @@ -204,13 +218,46 @@ const withAirshipServiceExtensionPod: ConfigPlugin<AirshipIOSPluginProps> = (con
});
};

const withEasManagedCredentials: ConfigPlugin<AirshipIOSPluginProps> = (config, props) => {
assert(config.ios?.bundleIdentifier, "Missing 'ios.bundleIdentifier' in app config.")
config.extra = getEasManagedCredentialsConfigExtra(config as ExpoConfig, props);
return config;
}

function getEasManagedCredentialsConfigExtra(config: ExpoConfig, props: AirshipIOSPluginProps): {[k: string]: any} {
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}`,
}
]
}
}
}
}
}
}

export const withAirshipIOS: ConfigPlugin<AirshipIOSPluginProps> = (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);
}
return config;
};
Loading