diff options
Diffstat (limited to 'plugins/notificationsExtension')
8 files changed, 289 insertions, 0 deletions
diff --git a/plugins/notificationsExtension/README.md b/plugins/notificationsExtension/README.md new file mode 100644 index 000000000..31b8bfe7d --- /dev/null +++ b/plugins/notificationsExtension/README.md @@ -0,0 +1,17 @@ +# Notifications extension plugin for Expo + +This plugin handles moving the necessary files into their respective iOS directories + +## Steps + +### ios + +1. Update entitlements +2. Set the app group to group.<identifier> +3. Add the extension plist +4. Add the view controller +5. Update the xcode project's build phases + +## Credits + +Adapted from https://github.com/andrew-levy/react-native-safari-extension and https://github.com/timedtext/expo-config-plugin-ios-share-extension/blob/master/src/withShareExtensionXcodeTarget.ts diff --git a/plugins/notificationsExtension/withAppEntitlements.js b/plugins/notificationsExtension/withAppEntitlements.js new file mode 100644 index 000000000..4ce81ea61 --- /dev/null +++ b/plugins/notificationsExtension/withAppEntitlements.js @@ -0,0 +1,13 @@ +const {withEntitlementsPlist} = require('@expo/config-plugins') + +const withAppEntitlements = config => { + // eslint-disable-next-line no-shadow + return withEntitlementsPlist(config, async config => { + config.modResults['com.apple.security.application-groups'] = [ + `group.app.bsky`, + ] + return config + }) +} + +module.exports = {withAppEntitlements} diff --git a/plugins/notificationsExtension/withExtensionEntitlements.js b/plugins/notificationsExtension/withExtensionEntitlements.js new file mode 100644 index 000000000..0cc1c4ca8 --- /dev/null +++ b/plugins/notificationsExtension/withExtensionEntitlements.js @@ -0,0 +1,31 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withExtensionEntitlements = (config, {extensionName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const extensionEntitlementsPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + `${extensionName}.entitlements`, + ) + + const notificationsExtensionEntitlements = { + 'com.apple.security.application-groups': [`group.app.bsky`], + } + + fs.mkdirSync(path.dirname(extensionEntitlementsPath), { + recursive: true, + }) + fs.writeFileSync( + extensionEntitlementsPath, + plist.default.build(notificationsExtensionEntitlements), + ) + + return config + }) +} + +module.exports = {withExtensionEntitlements} diff --git a/plugins/notificationsExtension/withExtensionInfoPlist.js b/plugins/notificationsExtension/withExtensionInfoPlist.js new file mode 100644 index 000000000..b0c6cfa89 --- /dev/null +++ b/plugins/notificationsExtension/withExtensionInfoPlist.js @@ -0,0 +1,39 @@ +const {withInfoPlist} = require('@expo/config-plugins') +const plist = require('@expo/plist') +const path = require('path') +const fs = require('fs') + +const withExtensionInfoPlist = (config, {extensionName}) => { + // eslint-disable-next-line no-shadow + return withInfoPlist(config, config => { + const plistPath = path.join( + config.modRequest.projectRoot, + 'modules', + extensionName, + 'Info.plist', + ) + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + 'Info.plist', + ) + + const extPlist = plist.default.parse(fs.readFileSync(plistPath).toString()) + + extPlist.MainAppScheme = config.scheme + extPlist.CFBundleName = '$(PRODUCT_NAME)' + extPlist.CFBundleDisplayName = 'Bluesky Notifications' + extPlist.CFBundleIdentifier = '$(PRODUCT_BUNDLE_IDENTIFIER)' + extPlist.CFBundleVersion = '$(CURRENT_PROJECT_VERSION)' + extPlist.CFBundleExecutable = '$(EXECUTABLE_NAME)' + extPlist.CFBundlePackageType = '$(PRODUCT_BUNDLE_PACKAGE_TYPE)' + extPlist.CFBundleShortVersionString = '$(MARKETING_VERSION)' + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.writeFileSync(targetPath, plist.default.build(extPlist)) + + return config + }) +} + +module.exports = {withExtensionInfoPlist} diff --git a/plugins/notificationsExtension/withExtensionViewController.js b/plugins/notificationsExtension/withExtensionViewController.js new file mode 100644 index 000000000..cd29bea7d --- /dev/null +++ b/plugins/notificationsExtension/withExtensionViewController.js @@ -0,0 +1,31 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const withExtensionViewController = ( + config, + {controllerName, extensionName}, +) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const controllerPath = path.join( + config.modRequest.projectRoot, + 'modules', + extensionName, + `${controllerName}.swift`, + ) + + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + `${controllerName}.swift`, + ) + + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + fs.copyFileSync(controllerPath, targetPath) + + return config + }) +} + +module.exports = {withExtensionViewController} diff --git a/plugins/notificationsExtension/withNotificationsExtension.js b/plugins/notificationsExtension/withNotificationsExtension.js new file mode 100644 index 000000000..6a00cfd23 --- /dev/null +++ b/plugins/notificationsExtension/withNotificationsExtension.js @@ -0,0 +1,55 @@ +const {withPlugins} = require('@expo/config-plugins') +const {withAppEntitlements} = require('./withAppEntitlements') +const {withXcodeTarget} = require('./withXcodeTarget') +const {withExtensionEntitlements} = require('./withExtensionEntitlements') +const {withExtensionInfoPlist} = require('./withExtensionInfoPlist') +const {withExtensionViewController} = require('./withExtensionViewController') +const {withSounds} = require('./withSounds') + +const EXTENSION_NAME = 'BlueskyNSE' +const EXTENSION_CONTROLLER_NAME = 'NotificationService' + +const withNotificationsExtension = config => { + const soundFiles = ['dm.aiff'] + + return withPlugins(config, [ + // IOS + withAppEntitlements, + [ + withExtensionEntitlements, + { + extensionName: EXTENSION_NAME, + }, + ], + [ + withExtensionInfoPlist, + { + extensionName: EXTENSION_NAME, + }, + ], + [ + withExtensionViewController, + { + extensionName: EXTENSION_NAME, + controllerName: EXTENSION_CONTROLLER_NAME, + }, + ], + [ + withSounds, + { + extensionName: EXTENSION_NAME, + soundFiles, + }, + ], + [ + withXcodeTarget, + { + extensionName: EXTENSION_NAME, + controllerName: EXTENSION_CONTROLLER_NAME, + soundFiles, + }, + ], + ]) +} + +module.exports = withNotificationsExtension diff --git a/plugins/notificationsExtension/withSounds.js b/plugins/notificationsExtension/withSounds.js new file mode 100644 index 000000000..652afd545 --- /dev/null +++ b/plugins/notificationsExtension/withSounds.js @@ -0,0 +1,27 @@ +const {withXcodeProject} = require('@expo/config-plugins') +const path = require('path') +const fs = require('fs') + +const withSounds = (config, {extensionName, soundFiles}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + for (const file of soundFiles) { + const soundPath = path.join(config.modRequest.projectRoot, 'assets', file) + + const targetPath = path.join( + config.modRequest.platformProjectRoot, + extensionName, + file, + ) + + if (!fs.existsSync(path.dirname(targetPath))) { + fs.mkdirSync(path.dirname(targetPath), {recursive: true}) + } + fs.copyFileSync(soundPath, targetPath) + } + + return config + }) +} + +module.exports = {withSounds} diff --git a/plugins/notificationsExtension/withXcodeTarget.js b/plugins/notificationsExtension/withXcodeTarget.js new file mode 100644 index 000000000..e9c7dae39 --- /dev/null +++ b/plugins/notificationsExtension/withXcodeTarget.js @@ -0,0 +1,76 @@ +const {withXcodeProject, IOSConfig} = require('@expo/config-plugins') +const path = require('path') +const PBXFile = require('xcode/lib/pbxFile') + +const withXcodeTarget = ( + config, + {extensionName, controllerName, soundFiles}, +) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + let pbxProject = config.modResults + + const target = pbxProject.addTarget( + extensionName, + 'app_extension', + extensionName, + ) + pbxProject.addBuildPhase([], 'PBXSourcesBuildPhase', 'Sources', target.uuid) + pbxProject.addBuildPhase( + [], + 'PBXResourcesBuildPhase', + 'Resources', + target.uuid, + ) + const pbxGroupKey = pbxProject.pbxCreateGroup(extensionName, extensionName) + pbxProject.addFile(`${extensionName}/Info.plist`, pbxGroupKey) + pbxProject.addSourceFile( + `${extensionName}/${controllerName}.swift`, + {target: target.uuid}, + pbxGroupKey, + ) + + for (const file of soundFiles) { + pbxProject.addSourceFile( + `${extensionName}/${file}`, + {target: target.uuid}, + pbxGroupKey, + ) + } + + var configurations = pbxProject.pbxXCBuildConfigurationSection() + for (var key in configurations) { + if (typeof configurations[key].buildSettings !== 'undefined') { + var buildSettingsObj = configurations[key].buildSettings + if ( + typeof buildSettingsObj.PRODUCT_NAME !== 'undefined' && + buildSettingsObj.PRODUCT_NAME === `"${extensionName}"` + ) { + buildSettingsObj.CLANG_ENABLE_MODULES = 'YES' + buildSettingsObj.INFOPLIST_FILE = `"${extensionName}/Info.plist"` + buildSettingsObj.CODE_SIGN_ENTITLEMENTS = `"${extensionName}/${extensionName}.entitlements"` + buildSettingsObj.CODE_SIGN_STYLE = 'Automatic' + buildSettingsObj.CURRENT_PROJECT_VERSION = `"${config.ios?.buildNumber}"` + buildSettingsObj.GENERATE_INFOPLIST_FILE = 'YES' + buildSettingsObj.MARKETING_VERSION = `"${config.version}"` + buildSettingsObj.PRODUCT_BUNDLE_IDENTIFIER = `"${config.ios?.bundleIdentifier}.${extensionName}"` + buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES' + buildSettingsObj.SWIFT_VERSION = '5.0' + buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1,2"` + buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS' + } + } + } + + pbxProject.addTargetAttribute( + 'DevelopmentTeam', + 'B3LX46C5HS', + extensionName, + ) + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS') + + return config + }) +} + +module.exports = {withXcodeTarget} |