From d451f82f54974b7b3da1477a7e1f221628860f62 Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 27 Feb 2024 15:22:03 -0800 Subject: Share Extension/Intents (#2587) * add native ios code outside of ios project * helper script * going to be a lot of these commits to squash...backing up * save * start of an expo plugin * create info.plist * copy the view controller * maybe working * working * wait working now * working plugin * use current scheme * update intent path * use better params * support text in uri * build * use better encoding * handle images * cleanup ios plugin * android * move bash script to /scripts * handle cases where loaded data is uiimage rather than uri * remove unnecessary logic, allow more than 4 images and just take first 4 * android build plugin * limit images to four on android * use js for plugins, no need to build * revert changes to app config * use correct scheme on android * android readme * move ios extension to /modules * remove unnecessary event * revert typo * plugin readme * scripts readme * add configurable scheme to .env, default to `bluesky` * remove debug * revert .gitignore change * add comment about updating .env to app.config.js for those modifying scheme * modify .env * update android module to use the proper url * update ios extension * remove comment * parse and validate incoming image uris * fix types * rm oops * fix a few typos --- plugins/shareExtension/README.md | 22 ++++++ plugins/shareExtension/withAppEntitlements.js | 13 ++++ .../shareExtension/withExtensionEntitlements.js | 33 ++++++++ plugins/shareExtension/withExtensionInfoPlist.js | 39 ++++++++++ .../shareExtension/withExtensionViewController.js | 31 ++++++++ plugins/shareExtension/withIntentFilters.js | 89 ++++++++++++++++++++++ plugins/shareExtension/withShareExtensions.js | 47 ++++++++++++ plugins/shareExtension/withXcodeTarget.js | 55 +++++++++++++ 8 files changed, 329 insertions(+) create mode 100644 plugins/shareExtension/README.md create mode 100644 plugins/shareExtension/withAppEntitlements.js create mode 100644 plugins/shareExtension/withExtensionEntitlements.js create mode 100644 plugins/shareExtension/withExtensionInfoPlist.js create mode 100644 plugins/shareExtension/withExtensionViewController.js create mode 100644 plugins/shareExtension/withIntentFilters.js create mode 100644 plugins/shareExtension/withShareExtensions.js create mode 100644 plugins/shareExtension/withXcodeTarget.js (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/README.md b/plugins/shareExtension/README.md new file mode 100644 index 000000000..2b57e624a --- /dev/null +++ b/plugins/shareExtension/README.md @@ -0,0 +1,22 @@ +# Share extension plugin for Expo + +This plugin handles moving the necessary files into their respective iOS and Android targets and updating the build +phases, plists, manifests, etc. + +## Steps + +### ios + +1. Update entitlements +2. Set the app group to group. +3. Add the extension plist +4. Add the view controller +5. Update the xcode project's build phases + +### android + +1. Update the manifest with the intents the app can receive + +## 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/shareExtension/withAppEntitlements.js b/plugins/shareExtension/withAppEntitlements.js new file mode 100644 index 000000000..6f9136c37 --- /dev/null +++ b/plugins/shareExtension/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.${config.ios.bundleIdentifier}`, + ] + return config + }) +} + +module.exports = {withAppEntitlements} diff --git a/plugins/shareExtension/withExtensionEntitlements.js b/plugins/shareExtension/withExtensionEntitlements.js new file mode 100644 index 000000000..e6bbf9d23 --- /dev/null +++ b/plugins/shareExtension/withExtensionEntitlements.js @@ -0,0 +1,33 @@ +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 shareExtensionEntitlements = { + 'com.apple.security.application-groups': [ + `group.${config.ios?.bundleIdentifier}`, + ], + } + + fs.mkdirSync(path.dirname(extensionEntitlementsPath), { + recursive: true, + }) + fs.writeFileSync( + extensionEntitlementsPath, + plist.default.build(shareExtensionEntitlements), + ) + + return config + }) +} + +module.exports = {withExtensionEntitlements} diff --git a/plugins/shareExtension/withExtensionInfoPlist.js b/plugins/shareExtension/withExtensionInfoPlist.js new file mode 100644 index 000000000..9afc4d5f9 --- /dev/null +++ b/plugins/shareExtension/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 = 'Extension' + 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/shareExtension/withExtensionViewController.js b/plugins/shareExtension/withExtensionViewController.js new file mode 100644 index 000000000..cd29bea7d --- /dev/null +++ b/plugins/shareExtension/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/shareExtension/withIntentFilters.js b/plugins/shareExtension/withIntentFilters.js new file mode 100644 index 000000000..605fcfd05 --- /dev/null +++ b/plugins/shareExtension/withIntentFilters.js @@ -0,0 +1,89 @@ +const {withAndroidManifest} = require('@expo/config-plugins') + +const withIntentFilters = config => { + // eslint-disable-next-line no-shadow + return withAndroidManifest(config, config => { + const intents = [ + { + action: [ + { + $: { + 'android:name': 'android.intent.action.SEND', + }, + }, + ], + category: [ + { + $: { + 'android:name': 'android.intent.category.DEFAULT', + }, + }, + ], + data: [ + { + $: { + 'android:mimeType': 'image/*', + }, + }, + ], + }, + { + action: [ + { + $: { + 'android:name': 'android.intent.action.SEND', + }, + }, + ], + category: [ + { + $: { + 'android:name': 'android.intent.category.DEFAULT', + }, + }, + ], + data: [ + { + $: { + 'android:mimeType': 'text/plain', + }, + }, + ], + }, + { + action: [ + { + $: { + 'android:name': 'android.intent.action.SEND_MULTIPLE', + }, + }, + ], + category: [ + { + $: { + 'android:name': 'android.intent.category.DEFAULT', + }, + }, + ], + data: [ + { + $: { + 'android:mimeType': 'image/*', + }, + }, + ], + }, + ] + + const intentFilter = + config.modResults.manifest.application?.[0].activity?.[0]['intent-filter'] + + if (intentFilter) { + intentFilter.push(...intents) + } + + return config + }) +} + +module.exports = {withIntentFilters} diff --git a/plugins/shareExtension/withShareExtensions.js b/plugins/shareExtension/withShareExtensions.js new file mode 100644 index 000000000..55a26c75e --- /dev/null +++ b/plugins/shareExtension/withShareExtensions.js @@ -0,0 +1,47 @@ +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 {withIntentFilters} = require('./withIntentFilters') + +const SHARE_EXTENSION_NAME = 'Share-with-Bluesky' +const SHARE_EXTENSION_CONTROLLER_NAME = 'ShareViewController' + +const withShareExtensions = config => { + return withPlugins(config, [ + // IOS + withAppEntitlements, + [ + withExtensionEntitlements, + { + extensionName: SHARE_EXTENSION_NAME, + }, + ], + [ + withExtensionInfoPlist, + { + extensionName: SHARE_EXTENSION_NAME, + }, + ], + [ + withExtensionViewController, + { + extensionName: SHARE_EXTENSION_NAME, + controllerName: SHARE_EXTENSION_CONTROLLER_NAME, + }, + ], + [ + withXcodeTarget, + { + extensionName: SHARE_EXTENSION_NAME, + controllerName: SHARE_EXTENSION_CONTROLLER_NAME, + }, + ], + // Android + withIntentFilters, + ]) +} + +module.exports = withShareExtensions diff --git a/plugins/shareExtension/withXcodeTarget.js b/plugins/shareExtension/withXcodeTarget.js new file mode 100644 index 000000000..4f43c0926 --- /dev/null +++ b/plugins/shareExtension/withXcodeTarget.js @@ -0,0 +1,55 @@ +const {withXcodeProject} = require('@expo/config-plugins') + +const withXcodeTarget = (config, {extensionName, controllerName}) => { + // eslint-disable-next-line no-shadow + return withXcodeProject(config, config => { + const 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, + ) + + 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"` + } + } + } + + return config + }) +} + +module.exports = {withXcodeTarget} -- cgit 1.4.1 From ed2e6d654e4736b0b15ba4dafd062f850b329774 Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 27 Feb 2024 18:34:09 -0800 Subject: Fix entitlements (#3008) --- plugins/shareExtension/withExtensionEntitlements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/withExtensionEntitlements.js b/plugins/shareExtension/withExtensionEntitlements.js index e6bbf9d23..ffb767b50 100644 --- a/plugins/shareExtension/withExtensionEntitlements.js +++ b/plugins/shareExtension/withExtensionEntitlements.js @@ -14,7 +14,7 @@ const withExtensionEntitlements = (config, {extensionName}) => { const shareExtensionEntitlements = { 'com.apple.security.application-groups': [ - `group.${config.ios?.bundleIdentifier}`, + `group.bsky.app`, ], } -- cgit 1.4.1 From 622d0daad6171ce2fc1c4d7b1f89e04364924c57 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 27 Feb 2024 18:41:53 -0800 Subject: More entitlement fixes --- plugins/shareExtension/withAppEntitlements.js | 2 +- plugins/shareExtension/withExtensionEntitlements.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/withAppEntitlements.js b/plugins/shareExtension/withAppEntitlements.js index 6f9136c37..e69091540 100644 --- a/plugins/shareExtension/withAppEntitlements.js +++ b/plugins/shareExtension/withAppEntitlements.js @@ -4,7 +4,7 @@ const withAppEntitlements = config => { // eslint-disable-next-line no-shadow return withEntitlementsPlist(config, async config => { config.modResults['com.apple.security.application-groups'] = [ - `group.${config.ios.bundleIdentifier}`, + `group.bsky.app`, ] return config }) diff --git a/plugins/shareExtension/withExtensionEntitlements.js b/plugins/shareExtension/withExtensionEntitlements.js index ffb767b50..f647c97c0 100644 --- a/plugins/shareExtension/withExtensionEntitlements.js +++ b/plugins/shareExtension/withExtensionEntitlements.js @@ -13,9 +13,7 @@ const withExtensionEntitlements = (config, {extensionName}) => { ) const shareExtensionEntitlements = { - 'com.apple.security.application-groups': [ - `group.bsky.app`, - ], + 'com.apple.security.application-groups': [`group.bsky.app`], } fs.mkdirSync(path.dirname(extensionEntitlementsPath), { -- cgit 1.4.1 From 00ac76d0fe8f85704f5bd429caf6faa61d01e215 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 27 Feb 2024 19:40:22 -0800 Subject: Fix group id --- plugins/shareExtension/withAppEntitlements.js | 2 +- plugins/shareExtension/withExtensionEntitlements.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/withAppEntitlements.js b/plugins/shareExtension/withAppEntitlements.js index e69091540..4ce81ea61 100644 --- a/plugins/shareExtension/withAppEntitlements.js +++ b/plugins/shareExtension/withAppEntitlements.js @@ -4,7 +4,7 @@ const withAppEntitlements = config => { // eslint-disable-next-line no-shadow return withEntitlementsPlist(config, async config => { config.modResults['com.apple.security.application-groups'] = [ - `group.bsky.app`, + `group.app.bsky`, ] return config }) diff --git a/plugins/shareExtension/withExtensionEntitlements.js b/plugins/shareExtension/withExtensionEntitlements.js index f647c97c0..7bee79d1a 100644 --- a/plugins/shareExtension/withExtensionEntitlements.js +++ b/plugins/shareExtension/withExtensionEntitlements.js @@ -13,7 +13,7 @@ const withExtensionEntitlements = (config, {extensionName}) => { ) const shareExtensionEntitlements = { - 'com.apple.security.application-groups': [`group.bsky.app`], + 'com.apple.security.application-groups': [`group.app.bsky`], } fs.mkdirSync(path.dirname(extensionEntitlementsPath), { -- cgit 1.4.1 From c4d30a0b7fb3e73b208a01d0f62130535d549392 Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 27 Feb 2024 19:48:04 -0800 Subject: please 🙏 (#3010) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/shareExtension/withXcodeTarget.js | 1 + 1 file changed, 1 insertion(+) (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/withXcodeTarget.js b/plugins/shareExtension/withXcodeTarget.js index 4f43c0926..694624691 100644 --- a/plugins/shareExtension/withXcodeTarget.js +++ b/plugins/shareExtension/withXcodeTarget.js @@ -44,6 +44,7 @@ const withXcodeTarget = (config, {extensionName, controllerName}) => { buildSettingsObj.SWIFT_EMIT_LOC_STRINGS = 'YES' buildSettingsObj.SWIFT_VERSION = '5.0' buildSettingsObj.TARGETED_DEVICE_FAMILY = `"1,2"` + buildSettingsObj.DEVELOPMENT_TEAM = 'B3LX46C5HS' } } } -- cgit 1.4.1 From 2a60faf4fef206ba4817c59eec46989af4a9f905 Mon Sep 17 00:00:00 2001 From: Hailey Date: Tue, 27 Feb 2024 20:34:11 -0800 Subject: add dev team (#3011) --- plugins/shareExtension/withXcodeTarget.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'plugins/shareExtension') diff --git a/plugins/shareExtension/withXcodeTarget.js b/plugins/shareExtension/withXcodeTarget.js index 694624691..ce70b392d 100644 --- a/plugins/shareExtension/withXcodeTarget.js +++ b/plugins/shareExtension/withXcodeTarget.js @@ -49,6 +49,13 @@ const withXcodeTarget = (config, {extensionName, controllerName}) => { } } + pbxProject.addTargetAttribute( + 'DevelopmentTeam', + 'B3LX46C5HS', + extensionName, + ) + pbxProject.addTargetAttribute('DevelopmentTeam', 'B3LX46C5HS') + return config }) } -- cgit 1.4.1