UNPKG

@intercom/intercom-react-native

Version:

React Native wrapper to bridge our iOS and Android SDK

216 lines (190 loc) 6.62 kB
import path from 'path'; import fs from 'fs'; import { type ConfigPlugin, withDangerousMod, withAndroidManifest, AndroidConfig, } from '@expo/config-plugins'; import type { IntercomPluginProps } from './@types'; const SERVICE_CLASS_NAME = 'IntercomFirebaseMessagingService'; function hasExpoNotifications(): boolean { try { require('expo-notifications'); return true; } catch (e: any) { return e?.code !== 'MODULE_NOT_FOUND'; } } /** * Generates the Kotlin source for the FirebaseMessagingService that * forwards FCM tokens and Intercom push messages to the Intercom SDK. */ function generateFirebaseServiceKotlin(packageName: string): string { const extendsExpo = hasExpoNotifications(); const baseClass = extendsExpo ? 'ExpoFirebaseMessagingService' : 'FirebaseMessagingService'; const baseImport = extendsExpo ? 'import expo.modules.notifications.service.ExpoFirebaseMessagingService' : 'import com.google.firebase.messaging.FirebaseMessagingService'; return `package ${packageName} ${baseImport} import com.google.firebase.messaging.RemoteMessage import com.intercom.reactnative.IntercomModule class ${SERVICE_CLASS_NAME} : ${baseClass}() { override fun onNewToken(refreshedToken: String) { IntercomModule.sendTokenToIntercom(application, refreshedToken) super.onNewToken(refreshedToken) } override fun onMessageReceived(remoteMessage: RemoteMessage) { if (IntercomModule.isIntercomPush(remoteMessage)) { IntercomModule.handleRemotePushMessage(application, remoteMessage) } else { super.onMessageReceived(remoteMessage) } } } `; } /** * Uses withDangerousMod to write the Kotlin FirebaseMessagingService file * into the app's Android source directory, and ensures firebase-messaging * is on the app module's compile classpath. */ const writeFirebaseService: ConfigPlugin<IntercomPluginProps> = (_config) => withDangerousMod(_config, [ 'android', (config) => { const packageName = config.android?.package; if (!packageName) { throw new Error( '@intercom/intercom-react-native: android.package must be defined in your Expo config to use Android push notifications.' ); } const projectRoot = config.modRequest.projectRoot; const packagePath = packageName.replace(/\./g, '/'); const serviceDir = path.join( projectRoot, 'android', 'app', 'src', 'main', 'java', packagePath ); fs.mkdirSync(serviceDir, { recursive: true }); fs.writeFileSync( path.join(serviceDir, `${SERVICE_CLASS_NAME}.kt`), generateFirebaseServiceKotlin(packageName), 'utf-8' ); // The native module declares firebase-messaging as an `implementation` // dependency, which keeps it private to the library. Since our generated // service lives in the app module, we need firebase-messaging on the // app's compile classpath too. We read the version from the native // module's build.gradle so it stays in sync automatically. const packageRoot = path.resolve(__dirname, '..', '..', '..'); const nativeBuildGradle = fs.readFileSync( path.join(packageRoot, 'android', 'build.gradle'), 'utf-8' ); const versionMatch = nativeBuildGradle.match( /com\.google\.firebase:firebase-messaging:([\d.]+)/ ); const firebaseMessagingVersion = versionMatch ? versionMatch[1] : '24.1.2'; const buildGradlePath = path.join( projectRoot, 'android', 'app', 'build.gradle' ); const buildGradle = fs.readFileSync(buildGradlePath, 'utf-8'); if (!buildGradle.includes('firebase-messaging')) { const updatedBuildGradle = buildGradle.replace( /dependencies\s*\{/, `dependencies {\n implementation("com.google.firebase:firebase-messaging:${firebaseMessagingVersion}")` ); fs.writeFileSync(buildGradlePath, updatedBuildGradle, 'utf-8'); } return config; }, ]); const registerServiceInManifest: ConfigPlugin<IntercomPluginProps> = ( _config ) => withAndroidManifest(_config, (config) => { const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( config.modResults ); const packageName = config.android?.package; if (!packageName) { throw new Error( '@intercom/intercom-react-native: android.package must be defined in your Expo config to use Android push notifications.' ); } const serviceName = `.${SERVICE_CLASS_NAME}`; const existingService = mainApplication.service?.find( (s) => s.$?.['android:name'] === serviceName ); const hasExistingFcmService = mainApplication.service?.some( (s) => s.$?.['android:name'] !== serviceName && ([] as any[]) .concat(s['intent-filter'] ?? []) .some((f: any) => ([] as any[]) .concat(f.action ?? []) .some( (a: any) => a.$?.['android:name'] === 'com.google.firebase.MESSAGING_EVENT' ) ) ); if (hasExistingFcmService) { console.warn( '@intercom/intercom-react-native: An existing FirebaseMessagingService was found in AndroidManifest.xml. ' + 'Skipping automatic Intercom service registration to avoid conflicts. ' + 'You will need to route Intercom pushes manually using IntercomModule.isIntercomPush() and IntercomModule.handleRemotePushMessage().' ); return config; } if (!existingService) { if (!mainApplication.service) { mainApplication.service = []; } mainApplication.service.push({ '$': { 'android:name': serviceName, 'android:exported': 'false' as any, }, 'intent-filter': [ { $: { 'android:priority': '10', } as any, action: [ { $: { 'android:name': 'com.google.firebase.MESSAGING_EVENT', }, }, ], }, ], } as any); } return config; }); export const withAndroidPushNotifications: ConfigPlugin<IntercomPluginProps> = ( config, props ) => { let newConfig = config; newConfig = writeFirebaseService(newConfig, props); newConfig = registerServiceInManifest(newConfig, props); return newConfig; };