Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add push notification support for expo #191

Merged
merged 4 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,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 @@ -471,11 +474,38 @@ The plugin provides props for extra customization. Every time you change the pro
}
```

Link your `google-service.json`

```
"android": {
"googleServicesFile": "./google-services.json",
...
}
```
uddish marked this conversation as resolved.
Show resolved Hide resolved

Add the necessary permission descriptions to infoPlist key.

```
"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.

>**Note**: Intercom appends ```npx prebuild --clean``` when changing plugin configurations.

TheNerdGuyLulu marked this conversation as resolved.
Show resolved Hide resolved
#### 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;
};