From 9ba1d1e4bc782189d900aac5442d1fe4944d3cce Mon Sep 17 00:00:00 2001 From: Ulrich GIBERNE Date: Mon, 2 Dec 2024 15:00:27 -0400 Subject: [PATCH] remove default notification service and support notification channels --- .../AirshipNotificationService.swift | 8 ---- plugin/src/withAirship.ts | 21 +++++++- plugin/src/withAirshipAndroid.ts | 40 +++++++++++++++- plugin/src/withAirshipIOS.ts | 48 +++++++++++-------- 4 files changed, 87 insertions(+), 30 deletions(-) delete mode 100644 plugin/NotificationServiceExtension/AirshipNotificationService.swift diff --git a/plugin/NotificationServiceExtension/AirshipNotificationService.swift b/plugin/NotificationServiceExtension/AirshipNotificationService.swift deleted file mode 100644 index 1b5ec9b..0000000 --- a/plugin/NotificationServiceExtension/AirshipNotificationService.swift +++ /dev/null @@ -1,8 +0,0 @@ -// NotificationService.swift - - -import AirshipServiceExtension - -class AirshipNotificationService: UANotificationServiceExtension { - -} diff --git a/plugin/src/withAirship.ts b/plugin/src/withAirship.ts index 71f0f80..925ceaf 100644 --- a/plugin/src/withAirship.ts +++ b/plugin/src/withAirship.ts @@ -6,12 +6,31 @@ import { withAirshipIOS } from './withAirshipIOS'; const pkg = require('airship-expo-plugin/package.json'); export type AirshipAndroidPluginProps = { + /** + * Required. The notification icons for Android. + */ icon: string; + /** + * Optional. The local path to a Custom Notification Channels resource file. + */ + customNotificationChannels?: string; }; export type AirshipIOSPluginProps = { + /** + * Required. Used to configure APNs environment entitlement. + * The accepted values are "development" and "production". + */ mode: 'development' | 'production'; - notificationServiceExtension?: boolean; + /** + * Optional. The local path to a custom Notification Service Extension. + */ + notificationService?: string; + /** + * Optional. Airship will use a default one if not provided. + * The local path to a Notification Service Extension Info.plist. + */ + notificationServiceInfo?: string; } export type AirshipPluginProps = { diff --git a/plugin/src/withAirshipAndroid.ts b/plugin/src/withAirshipAndroid.ts index eba9004..c78d160 100644 --- a/plugin/src/withAirshipAndroid.ts +++ b/plugin/src/withAirshipAndroid.ts @@ -5,8 +5,8 @@ import { } from '@expo/config-plugins'; import { generateImageAsync, ImageOptions } from '@expo/image-utils'; -import { writeFileSync, existsSync, mkdirSync } from 'fs'; -import { resolve, basename } from 'path'; +import { readFile, writeFileSync, existsSync, mkdirSync } from 'fs'; +import { resolve, basename, join } from 'path'; import { AirshipAndroidPluginProps } from './withAirship'; @@ -18,6 +18,8 @@ const iconSizeMap: Record = { xxxhdpi: 96, }; +const NOTIFICATIONS_CHANNELS_FILE_NAME = "ua_custom_notification_channels.xml"; + async function writeNotificationIconImageFilesAsync(props: AirshipAndroidPluginProps, projectRoot: string) { const fileName = basename(props.icon) await Promise.all( @@ -72,8 +74,42 @@ const withCompileSDKVersionFix: ConfigPlugin = (confi }); }; +const withCustomNotificationChannels: ConfigPlugin = (config, props) => { + return withDangerousMod(config, [ + 'android', + async config => { + await writeNotificationChannelsFileAsync(props, config.modRequest.projectRoot); + return config; + }, + ]); +} + +// TODO copy the file from assets to xml res +async function writeNotificationChannelsFileAsync(props: AirshipAndroidPluginProps, projectRoot: string) { + if (!props.customNotificationChannels) { + return; + } + + const xmlResPath = join(projectRoot, "android/app/src/main/res/xml"); + + if (!existsSync(xmlResPath)) { + mkdirSync(xmlResPath, { recursive: true }); + } + + // Copy the custom notification channels file into the Android expo project as ua_custom_notification_channels.xml. + readFile(props.customNotificationChannels, 'utf8', (err, data) => { + if (err || !data) { + console.error("Airship couldn't read file " + props.customNotificationChannels); + console.error(err); + return; + } + writeFileSync(join(xmlResPath, NOTIFICATIONS_CHANNELS_FILE_NAME), data); + }); +}; + export const withAirshipAndroid: ConfigPlugin = (config, props) => { config = withCompileSDKVersionFix(config, props); config = withNotificationIcons(config, props); + config = withCustomNotificationChannels(config, props); return config; }; \ No newline at end of file diff --git a/plugin/src/withAirshipIOS.ts b/plugin/src/withAirshipIOS.ts index b56e456..d217699 100644 --- a/plugin/src/withAirshipIOS.ts +++ b/plugin/src/withAirshipIOS.ts @@ -8,7 +8,7 @@ import { } from '@expo/config-plugins'; import { readFile, writeFileSync, existsSync, mkdirSync } from 'fs'; -import { join } from 'path'; +import { basename, join } from 'path'; import { AirshipIOSPluginProps } from './withAirship'; import { mergeContents, MergeResults } from '@expo/config-plugins/build/utils/generateCode'; @@ -37,7 +37,21 @@ const withAPNSEnvironment: ConfigPlugin = (config, props) }); }; +const withNotificationServiceExtension: ConfigPlugin = (config, props) => { + return withDangerousMod(config, [ + 'ios', + async config => { + await writeNotificationServiceFilesAsync(props, config.modRequest.projectRoot); + return config; + }, + ]); +}; + async function writeNotificationServiceFilesAsync(props: AirshipIOSPluginProps, projectRoot: string) { + if (!props.notificationService) { + return; + } + const pluginDir = require.resolve("airship-expo-plugin/package.json"); const sourceDir = join(pluginDir, "../plugin/NotificationServiceExtension/"); @@ -47,20 +61,27 @@ async function writeNotificationServiceFilesAsync(props: AirshipIOSPluginProps, mkdirSync(extensionPath, { recursive: true }); } - // Copy the AirshipNotificationService.swift file into the iOS expo project. - readFile(join(sourceDir, NOTIFICATION_SERVICE_FILE_NAME), 'utf8', (err, data) => { + // Copy the NotificationService.swift file into the iOS expo project as AirshipNotificationService.swift. + readFile(props.notificationService, 'utf8', (err, data) => { if (err || !data) { - console.error("Airship couldn't read file " + join(sourceDir, NOTIFICATION_SERVICE_FILE_NAME)); + console.error("Airship couldn't read file " + props.notificationService); console.error(err); return; } + + if (!props.notificationServiceInfo) { + const regexp = /class [A-Za-z]+:/; + const newSubStr = "class AirshipNotificationService:"; + data = data.replace(regexp, newSubStr); + } + writeFileSync(join(extensionPath, NOTIFICATION_SERVICE_FILE_NAME), data); }); - // Copy the AirshipNotificationServiceExtension-Info.plist file into the iOS expo project. - readFile(join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME), 'utf8', (err, data) => { + // Copy the Info.plist (default to AirshipNotificationServiceExtension-Info.plist if null) file into the iOS expo project as AirshipNotificationServiceExtension-Info.plist. + readFile(props.notificationServiceInfo ?? join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME), 'utf8', (err, data) => { if (err || !data) { - console.error("Airship couldn't read file " + join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME)); + console.error("Airship couldn't read file " + (props.notificationServiceInfo ?? join(sourceDir, NOTIFICATION_SERVICE_INFO_PLIST_FILE_NAME))); console.error(err); return; } @@ -68,16 +89,6 @@ async function writeNotificationServiceFilesAsync(props: AirshipIOSPluginProps, }); }; -const withNotificationServiceExtension: ConfigPlugin = (config, props) => { - return withDangerousMod(config, [ - 'ios', - async config => { - await writeNotificationServiceFilesAsync(props, config.modRequest.projectRoot); - return config; - }, - ]); -}; - const withExtensionTargetInXcodeProject: ConfigPlugin = (config, props) => { return withXcodeProject(config, newConfig => { const xcodeProject = newConfig.modResults; @@ -194,8 +205,7 @@ const withAirshipServiceExtensionPod: ConfigPlugin = (con export const withAirshipIOS: ConfigPlugin = (config, props) => { config = withCapabilities(config, props); config = withAPNSEnvironment(config, props); - - if (props.notificationServiceExtension) { + if (props.notificationService) { config = withNotificationServiceExtension(config, props); config = withExtensionTargetInXcodeProject(config, props); config = withAirshipServiceExtensionPod(config, props);