diff --git a/README.md b/README.md index d5b40aa5..85c5e2a1 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,9 @@ Add this permission to your `Info.plist` #### iOS: Push Notifications +>**Note**: You should request user permission to display push notifications. +e.g. [react-native-permissions](https://github.com/zoontek/react-native-permissions) + Add **Push Notifications** and **Background Modes > Remote Notifications** [Details HERE](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app) @@ -470,11 +473,50 @@ The plugin provides props for extra customization. Every time you change the pro } ``` +#### Push notifications + +Add the following configurations into your `app.json` or `app.config.js`: + +Place your `google-services.json` inside the project's root and link it + +```json +{ + "expo": { + ... + "android": { + "googleServicesFile": "./google-services.json", + ... + } + } +``` + +Add the necessary permission descriptions to infoPlist key. + +```json +{ + "expo": { + ... + "ios": { + ... + "infoPlist": { + "NSCameraUsageDescription": "This is just a sample text to access the Camera", + "NSPhotoLibraryUsageDescription": "This is just a sample text to access the Photo Library" + } + ... + } + } +} +``` + +>**Note**: You should request user permission to display push notifications. +e.g. [react-native-permissions](https://github.com/zoontek/react-native-permissions) + Next, rebuild your app as described in the ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) guide. + #### Limitations -- **No push notifications support**: Intercom push notifications currently aren't supported by this config plugin extension. This will be added in the future. +- **No deep links support**: Deep Linking currently is not supported by this config plugin extension. This will be added in the future. ## Methods diff --git a/package.json b/package.json index 22f193ac..e7c92f3b 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "access": "public" }, "devDependencies": { - "@expo/config-plugins": "^7.8.4", + "@expo/config-plugins": "^7.9.1", "@react-native-community/eslint-config": "^2.0.0", "@types/jest": "^26.0.0", "@types/mocha": "^8.2.2", diff --git a/sandboxes/IntercomExpo/yarn.lock b/sandboxes/IntercomExpo/yarn.lock index e17f047d..dae495be 100644 --- a/sandboxes/IntercomExpo/yarn.lock +++ b/sandboxes/IntercomExpo/yarn.lock @@ -5568,6 +5568,11 @@ react-native-mmkv-storage@^0.9.1: resolved "https://registry.yarnpkg.com/react-native-mmkv-storage/-/react-native-mmkv-storage-0.9.1.tgz#0db7e8c1726713dce68704bb8795dc64096c8cbb" integrity sha512-FzSx4PKxK2ocT/OuKGlaVziWZyQYHYLUx9595i1oXY263C5mG19PN5RiBgEGL2S5lK4VGUCzO85GAcsrNPtpOg== +react-native-permissions@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/react-native-permissions/-/react-native-permissions-4.1.5.tgz#db4d1ddbf076570043f4fd4168f54bb6020aec92" + integrity sha512-r6VMRacASmtRHS+GZ+5HQCp9p9kiE+UU9magHOZCXZLTJitdTuVHWZRrb4v4oqZGU+zAp3mZhTQftuMMv+WLUg== + react-native@0.73.6: version "0.73.6" resolved "https://registry.npmjs.org/react-native/-/react-native-0.73.6.tgz" diff --git a/src/expo-plugins/index.ts b/src/expo-plugins/index.ts index c2d40b61..8c4564d5 100644 --- a/src/expo-plugins/index.ts +++ b/src/expo-plugins/index.ts @@ -1,12 +1,13 @@ import { + AndroidConfig, ConfigPlugin, createRunOncePlugin, - withAppDelegate, - AndroidConfig, - withMainApplication, withAndroidManifest, + withAppDelegate, withInfoPlist, + withMainApplication, } from '@expo/config-plugins'; + import { addImports, appendContentsInsideDeclarationBlock, @@ -16,20 +17,31 @@ import { insertContentsInsideObjcFunctionBlock, } from '@expo/config-plugins/build/ios/codeMod'; import type { IntercomPluginProps, IntercomRegion } from './@types'; +import { withIntercomPushNotification } from './withPushNotifications'; const mainApplication: ConfigPlugin = (_config, props) => withMainApplication(_config, (config) => { let stringContents = config.modResults.contents; stringContents = addImports( stringContents, - ['com.intercom.reactnative.IntercomModule;'], - false + ['com.intercom.reactnative.IntercomModule'], + config.modResults.language === 'java' + ); + + // Remove previous code + stringContents = stringContents.replace( + /IntercomModule\.initialize\(.*?\)\s*;?\n?/g, + '' ); + stringContents = appendContentsInsideDeclarationBlock( stringContents, 'onCreate', - `IntercomModule.initialize(this, "${props.androidApiKey}", "${props.appId}");` + `IntercomModule.initialize(this, "${props.androidApiKey}", "${ + props.appId + }")${config.modResults.language === 'java' ? ';' : ''}\n` ); + config.modResults.contents = stringContents; return config; }); @@ -81,12 +93,20 @@ const appDelegate: ConfigPlugin = (_config, props) => withAppDelegate(_config, (config) => { let stringContents = config.modResults.contents; stringContents = addObjcImports(stringContents, ['']); + + // Remove previous code + stringContents = stringContents.replace( + /\s*\[IntercomModule initialize:@"(.*)" withAppId:@"(.*)"];/g, + '' + ); + stringContents = insertContentsInsideObjcFunctionBlock( stringContents, 'application didFinishLaunchingWithOptions:', `[IntercomModule initialize:@"${props.iosApiKey}" withAppId:@"${props.appId}"];`, { position: 'tailBeforeLastReturn' } ); + config.modResults.contents = stringContents; return config; }); @@ -105,6 +125,7 @@ const infoPlist: ConfigPlugin = ( return newConfig; }; + const withIntercomIOS: ConfigPlugin = (config, props) => { let newConfig = appDelegate(config, props); newConfig = infoPlist(newConfig, props); @@ -118,6 +139,7 @@ const withIntercomReactNative: ConfigPlugin = ( let newConfig = config; newConfig = withIntercomAndroid(newConfig, props); newConfig = withIntercomIOS(newConfig, props); + newConfig = withIntercomPushNotification(newConfig, props); return newConfig; }; diff --git a/src/expo-plugins/withPushNotifications.ts b/src/expo-plugins/withPushNotifications.ts new file mode 100644 index 00000000..6da02956 --- /dev/null +++ b/src/expo-plugins/withPushNotifications.ts @@ -0,0 +1,88 @@ +import { + ConfigPlugin, + withAppDelegate, + withInfoPlist, +} from '@expo/config-plugins'; +import type { IntercomPluginProps } from './@types'; +import { + addObjcImports, + findObjcFunctionCodeBlock, + insertContentsInsideObjcFunctionBlock, +} from '@expo/config-plugins/build/ios/codeMod'; + +const appDelegate: ConfigPlugin = (_config) => + withAppDelegate(_config, (config) => { + const pushCode = ` + // START INTERCOM PUSH + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound) + completionHandler:^(BOOL granted, NSError *_Nullable error) { + }]; + [[UIApplication sharedApplication] registerForRemoteNotifications]; + // END INTERCOM PUSH +`; + + const setDeviceTokenCode = '[IntercomModule setDeviceToken:deviceToken];'; + + let stringContents = config.modResults.contents; + stringContents = addObjcImports(stringContents, [ + '', + ]); + + if (!stringContents.includes(pushCode.trim())) { + stringContents = insertContentsInsideObjcFunctionBlock( + stringContents, + 'application didFinishLaunchingWithOptions:', + pushCode, + { position: 'tailBeforeLastReturn' } + ); + } + + const didRegisterBlock = findObjcFunctionCodeBlock( + stringContents, + 'application didRegisterForRemoteNotificationsWithDeviceToken:' + ); + + if (!didRegisterBlock?.code.includes(setDeviceTokenCode)) { + stringContents = insertContentsInsideObjcFunctionBlock( + stringContents, + 'application didRegisterForRemoteNotificationsWithDeviceToken:', + setDeviceTokenCode, + { position: 'tailBeforeLastReturn' } + ); + } + + config.modResults.contents = stringContents; + return config; + }); + +const infoPlist: ConfigPlugin = (_config) => { + const newConfig = withInfoPlist(_config, (config) => { + const keys = { remoteNotification: 'remote-notification' }; + + if (!config.modResults.UIBackgroundModes) { + config.modResults.UIBackgroundModes = []; + } + + if ( + config.modResults.UIBackgroundModes?.indexOf(keys.remoteNotification) === + -1 + ) { + config.modResults.UIBackgroundModes?.push(keys.remoteNotification); + } + + return config; + }); + + return newConfig; +}; + +export const withIntercomPushNotification: ConfigPlugin = ( + config, + props +) => { + let newConfig = config; + newConfig = appDelegate(config, props); + newConfig = infoPlist(config, props); + return newConfig; +}; diff --git a/yarn.lock b/yarn.lock index bdc9a3dc..871c15a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1204,10 +1204,10 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@expo/config-plugins@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.8.4.tgz#533b5d536c1dc8b5544d64878b51bda28f2e1a1f" - integrity sha512-hv03HYxb/5kX8Gxv/BTI8TLc9L06WzqAfHRRXdbar4zkLcP2oTzvsLEF4/L/TIpD3rsnYa0KU42d0gWRxzPCJg== +"@expo/config-plugins@^7.9.1": + version "7.9.1" + resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-7.9.1.tgz#fe4f7e4f9d4e87f2dcf2344ffdc59eb466dd5d2e" + integrity sha512-ICt6Jed1J0tPYMQrJ8K5Qusgih2I6pZ2PU4VSvxsN3T4n97L13XpYV1vyq1Uc/HMl3UhOwldipmgpEbCfeDqsQ== dependencies: "@expo/config-types" "^50.0.0-alpha.1" "@expo/fingerprint" "^0.6.0"