Skip to content

Commit

Permalink
feat: Add push notification support for expo (#191)
Browse files Browse the repository at this point in the history
* feat: Add push notification support for expo

* fix: remove wrong readme

* chore: improve docs
  • Loading branch information
TheNerdGuyLulu authored Apr 30, 2024
1 parent f148100 commit e1df100
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 12 deletions.
44 changes: 43 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
5 changes: 5 additions & 0 deletions sandboxes/IntercomExpo/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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==

[email protected]:
version "0.73.6"
resolved "https://registry.npmjs.org/react-native/-/react-native-0.73.6.tgz"
Expand Down
34 changes: 28 additions & 6 deletions src/expo-plugins/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {
AndroidConfig,
ConfigPlugin,
createRunOncePlugin,
withAppDelegate,
AndroidConfig,
withMainApplication,
withAndroidManifest,
withAppDelegate,
withInfoPlist,
withMainApplication,
} from '@expo/config-plugins';

import {
addImports,
appendContentsInsideDeclarationBlock,
Expand All @@ -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<IntercomPluginProps> = (_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;
});
Expand Down Expand Up @@ -81,12 +93,20 @@ const appDelegate: ConfigPlugin<IntercomPluginProps> = (_config, props) =>
withAppDelegate(_config, (config) => {
let stringContents = config.modResults.contents;
stringContents = addObjcImports(stringContents, ['<IntercomModule.h>']);

// 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;
});
Expand All @@ -105,6 +125,7 @@ const infoPlist: ConfigPlugin<IntercomPluginProps> = (

return newConfig;
};

const withIntercomIOS: ConfigPlugin<IntercomPluginProps> = (config, props) => {
let newConfig = appDelegate(config, props);
newConfig = infoPlist(newConfig, props);
Expand All @@ -118,6 +139,7 @@ const withIntercomReactNative: ConfigPlugin<IntercomPluginProps> = (
let newConfig = config;
newConfig = withIntercomAndroid(newConfig, props);
newConfig = withIntercomIOS(newConfig, props);
newConfig = withIntercomPushNotification(newConfig, props);
return newConfig;
};

Expand Down
88 changes: 88 additions & 0 deletions src/expo-plugins/withPushNotifications.ts
Original file line number Diff line number Diff line change
@@ -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<IntercomPluginProps> = (_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, [
'<UserNotifications/UserNotifications.h>',
]);

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<IntercomPluginProps> = (_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<IntercomPluginProps> = (
config,
props
) => {
let newConfig = config;
newConfig = appDelegate(config, props);
newConfig = infoPlist(config, props);
return newConfig;
};
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit e1df100

Please sign in to comment.