├── .gitattributes ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── README.md ├── android ├── .classpath ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── afrihost │ │ └── firebase │ │ └── notifications │ │ ├── BundleJSONConverter.java │ │ ├── DisplayNotificationTask.java │ │ ├── FirebasePushNotificationsModule.java │ │ ├── FirebasePushNotificationsPackage.java │ │ ├── MessagingSerializer.java │ │ ├── MyWorker.java │ │ ├── RNFirebaseBackgroundMessagingService.java │ │ ├── RNFirebaseBackgroundNotificationActionReceiver.java │ │ ├── RNFirebaseBackgroundNotificationActionsService.java │ │ ├── RNFirebaseMessagingService.java │ │ ├── RNFirebaseNotificationManager.java │ │ ├── RNFirebaseNotificationReceiver.java │ │ ├── RNFirebaseNotifications.java │ │ ├── RNFirebaseNotificationsPackage.java │ │ ├── RNFirebaseNotificationsRebootReceiver.java │ │ └── Utils.java │ └── res │ └── values │ └── strings.xml ├── ios ├── FirebasePushNotifications.h ├── FirebasePushNotifications.m ├── FirebasePushNotifications.podspec ├── FirebasePushNotifications.xcodeproj │ └── project.pbxproj ├── FirebasePushNotifications.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RCTConvert+UIBackgroundFetchResult.h ├── RCTConvert+UIBackgroundFetchResult.m ├── RNFirebase.h ├── RNFirebase.m ├── RNFirebaseEvents.h ├── RNFirebaseMessaging.h ├── RNFirebaseMessaging.m ├── RNFirebaseUtil.h └── RNFirebaseUtil.m ├── package.json ├── react-native-firebase-push-notifications.podspec ├── src ├── index.js └── notifications │ ├── AndroidAction.js │ ├── AndroidChannel.js │ ├── AndroidChannelGroup.js │ ├── AndroidNotification.js │ ├── AndroidNotifications.js │ ├── AndroidRemoteInput.js │ ├── IOSNotification.js │ ├── IOSNotifications.js │ ├── Notification.js │ └── types.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrihost/react-native-firebase-push-notifications/7e3bcb8cd2a3bf7ee7003a5557f0847c68ebcb26/.npmignore -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | 5 | ## [1.0.0] - 2020-03-10 6 | ### Added 7 | - Support for core app 7.7 8 | - Removed deprecated method shouldEstablishDirectChannel 9 | - Removed deprecated method applicationReceivedRemoteMessage 10 | - Removed deprecated method sendMessage 11 | - Removed deprecated method remoteMessage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-firebase-push-notifications 2 | 3 | This project is allows you to recieve remote push notifications for iOS and Android. There is support for local notifications and messaging channel's which will be updated periodically . 4 | 5 | ❗ This is a work in progress, the remote notifications work and has been done as priority, the rest will follow periodically. 6 | 7 | Version 1 now uses the version 7.* of the firebase SDK. 8 | 9 | To get started follow the instructions below: 10 | 11 | ## 🚧 Getting started 12 | 13 | `$ npm install react-native-firebase-push-notifications --save` 14 | 15 | or 16 | 17 | `$ yarn add react-native-firebase-push-notifications` 18 | 19 | then for iOS 20 | 21 | `$ cd ios && pod install` 22 | 23 | ### Mostly automatic installation 24 | 25 | Do this if you are not on react-native 0.59 or less, there will not be much support for manual integration. 26 | 27 | `$ react-native link react-native-firebase-push-notifications` 28 | 29 | ## 🔧 Android Setup 30 | 31 | ### Add firebase credentials 32 | 33 | The Firebase console provides a `google-services.json` file containing a set of credentials for Android devices to use when authenticating with your Firebase project. 34 | 35 | ### Setup Credentials 36 | 37 | 1. Select your firebase android project 38 | 2. Select the android icon that will open the configuration section 39 | 1. Fill in the required information 40 | 2. Download the `google=services.json` file. Then Switch to the Project view in Android Studio to see your project root directory. Move the google-services.json file that you just downloaded into your Android app module root directory. eg. `/yourAppsName/android/app` 41 | 3. Add the google-services plugin inside `yourAppsName/android` 42 | 4. (Miss it if already have firebase) 43 | ```javascript buildscript { 44 | repositories { 45 | // Check that you have the following line (if not, add it): 46 | google() // Google's Maven repository 47 | } 48 | dependencies { 49 | ... 50 | // Add this line 51 | classpath 'com.google.gms:google-services:4.3.3' 52 | } 53 | allprojects { 54 | ... 55 | repositories { 56 | // Check that you have the following line (if not, add it): 57 | google() // Google's Maven repository 58 | ... 59 | } 60 | } 61 | ``` 62 | 63 | 5. (Miss it if already have firebase) 64 | App-level build.gradle (//build.gradle): 65 | 66 | ```javascript 67 | apply plugin: 'com.android.application' 68 | // Add this line 69 | apply plugin: 'com.google.gms.google-services' 70 | 71 | dependencies { 72 | // add the Firebase SDK for Google Analytics 73 | implementation 'com.google.firebase:firebase-analytics:17.2.2' 74 | // add SDKs for any other desired Firebase products 75 | // https://firebase.google.com/docs/android/setup#available-libraries 76 | } 77 | ``` 78 | 79 | 6. Finally, press 'Sync now' in the bar that appears in the IDE 80 | 7. Update the Android Manifest 81 | 1. add the permissions 82 | 83 | ``` 84 | 85 | 86 | 87 | 88 | ``` 89 | 90 | 2. Setup launch mode 91 | 92 | ``` 93 | 97 | ``` 98 | 99 | 3. icon & color (optional) 100 | If conflicts with existed firebase package, remove this lines, because they already exists. 101 | 102 | ``` 103 | 104 | 107 | 110 | 111 | ``` 112 | 113 | 4. Notification channels (Optional) - Work in progress 114 | If conflicts with existed firebase package, remove this lines, because they already exists. 115 | 116 | ``` 117 | 118 | 121 | 122 | ``` 123 | 124 | 5. Scheduled Notifications (Optional) - Work in progress 125 | 126 | ``` 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | ``` 139 | 8. Run `$ npx react-native run-android` to test if everything is working 140 | 141 | ## 🔧 iOS Setup 142 | 143 | ### Add firebase credentials 144 | 145 | The Firebase console provides a `GoogleService-Info.plist` file containing a set of credentials for iOS devices to use when authenticating with your Firebase project. 146 | 147 | ### Setup Credentials 148 | 149 | 1. Select your firebase iOS project 150 | 2. Select the iOS icon that will open the configuration section 151 | 1. Fill in the required information 152 | 2. Download the `GoogleService-Info.plist` then, Move the GoogleService-Info.plist file that you just downloaded into the root of your Xcode project and add it to all targets. 153 | 3. Add the firebase SDK if you are **not** using PODS. 154 | 3. Add the following in AppDelegate 155 | 156 | ``` 157 | #import "AppDelegate.h" 158 | .... 159 | #import "Firebase.h" <--- Add this 160 | #import "RNFirebaseMessaging.h" <--- Add this 161 | #import "FirebasePushNotifications.h" <--- Add this 162 | 163 | @implementation AppDelegate 164 | 165 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 166 | { 167 | [FIRApp configure]; <--Add this 168 | [FirebasePushNotifications configure]; <--Add this 169 | .......... 170 | .......... 171 | return YES; 172 | } 173 | ``` 174 | 4. Follow the cert instructions here: 175 | 1. Go to the firebase console 176 | 2. select the iOS app (settings) 177 | 3. click cloud messaging 178 | 4. Upload the Authentication Key and fill in the detatils 179 | 5. In Xcode, enable the following capabilities: 180 | 1. Push Notifications 181 | 2. Background modes > Remote notifications 182 | 6. Add the following code to `AppDelegate.m` 183 | 184 | 1. To recieve the notifications 185 | 186 | ``` 187 | - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { 188 | [[FirebasePushNotifications instance] didReceiveLocalNotification:notification]; 189 | } 190 | ``` 191 | 192 | 2. optional for remote notifications 193 | 194 | ``` 195 | #import "RNFirebaseMessaging.h" 196 | ..... 197 | ..... 198 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo 199 | fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{ 200 | [[FirebasePushNotifications instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; 201 | } 202 | 203 | - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { 204 | 205 | [[RNFirebaseMessaging instance] didRegisterUserNotificationSettings:notificationSettings]; 206 | } 207 | ``` 208 | 7. Run `$ npx react-native run-ios` to confirm the app communicates with firebase (You may need to uninstall and reinstall your app.) 209 | 210 | ⚠️ Notifications will only come through on a real physical device - this is a limitation set by Apple. 211 | 212 | ⚠️ You can only recieve the messages / notifications on iOS if you have permission - ensure you request and have the permission first. 213 | 214 | ## ✅ Usage 215 | 216 | ```javascript 217 | import { notifications, NotificationMessage, Android } from 'react-native-firebase-push-notifications' 218 | 219 | getToken = async () => { 220 | //get the messeging token 221 | const token = await notifications.getToken() 222 | //you can also call messages.getToken() (does the same thing) 223 | return token 224 | } 225 | getInitialNotification = async () => { 226 | //get the initial token (triggered when app opens from a closed state) 227 | const notification = await notifications.getInitialNotification() 228 | console.log("getInitialNotification", notification) 229 | return notification 230 | } 231 | 232 | onNotificationOpenedListener = () => { 233 | //remember to remove the listener on un mount 234 | //this gets triggered when the application is in the background 235 | this.removeOnNotificationOpened = notifications.onNotificationOpened( 236 | notification => { 237 | console.log("onNotificationOpened", notification) 238 | //do something with the notification 239 | } 240 | ) 241 | } 242 | 243 | onNotificationListener = () => { 244 | //remember to remove the listener on un mount 245 | //this gets triggered when the application is in the forground/runnning 246 | //for android make sure you manifest is setup - else this wont work 247 | //Android will not have any info set on the notification properties (title, subtitle, etc..), but _data will still contain information 248 | this.removeOnNotification = notifications.onNotification(notification => { 249 | //do something with the notification 250 | console.log("onNotification", notification) 251 | }) 252 | } 253 | 254 | onTokenRefreshListener = () => { 255 | //remember to remove the listener on un mount 256 | //this gets triggered when a new token is generated for the user 257 | this.removeonTokenRefresh = messages.onTokenRefresh(token => { 258 | //do something with the new token 259 | }) 260 | } 261 | setBadge = async number => { 262 | //only works on iOS and some Android Devices 263 | return await notifications.setBadge(number) 264 | } 265 | 266 | getBadge = async () => { 267 | //only works on iOS and some Android Devices 268 | return await notifications.getBadge() 269 | } 270 | 271 | hasPermission = async () => { 272 | //only works on iOS 273 | return await notifications.hasPermission() 274 | //or return await messages.hasPermission() 275 | } 276 | 277 | requestPermission = async () => { 278 | //only works on iOS 279 | return await notifications.requestPermission() 280 | //or return await messages.requestPermission() 281 | } 282 | 283 | localNotification = async () => { 284 | //required for Android 285 | const channel = new Android.Channel( 286 | "test-channel", 287 | "Test Channel", 288 | Android.Importance.Max 289 | ).setDescription("My apps test channel") 290 | 291 | // for android create the channel 292 | notifications.android().createChannel(channel) 293 | await notifications.displayNotification( 294 | new NotificationMessage() 295 | .setNotificationId("notification-id") 296 | .setTitle("Notification title") 297 | .setBody("Notification body") 298 | .setData({ 299 | key1: "key1", 300 | key2: "key2", 301 | }) 302 | .android.setChannelId("test-channel") //required for android 303 | ) 304 | } 305 | 306 | 307 | componentWillUnmount() { 308 | //remove the listener on unmount 309 | if (this.removeOnNotificationOpened) { 310 | this.removeOnNotificationOpened() 311 | } 312 | if (this.removeOnNotification) { 313 | this.removeOnNotification() 314 | } 315 | 316 | if (this.removeonTokenRefresh) { 317 | this.removeonTokenRefresh() 318 | } 319 | } 320 | 321 | ``` 322 | 323 | Check out the sample app 324 | -------------------------------------------------------------------------------- /android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 5 | 6 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 7 | 2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK 8 | ``` 9 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 10 | sdk.dir=/Users/{username}/Library/Android/sdk 11 | ``` 12 | 3. Delete the `maven` folder 13 | 4. Run `./gradlew installArchives` 14 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 15 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // android/build.gradle 2 | 3 | // based on: 4 | // 5 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 6 | // original location: 7 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 8 | // 9 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 10 | // original location: 11 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 12 | 13 | def DEFAULT_COMPILE_SDK_VERSION = 31 14 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 15 | def DEFAULT_MIN_SDK_VERSION = 21 16 | def DEFAULT_TARGET_SDK_VERSION = 28 17 | 18 | def safeExtGet(prop, fallback) { 19 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | apply plugin: 'maven-publish' 24 | 25 | buildscript { 26 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 27 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 28 | // module dependency in an application project. 29 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 30 | if (project == rootProject) { 31 | repositories { 32 | google() 33 | mavenCentral() 34 | } 35 | dependencies { 36 | classpath 'com.android.tools.build:gradle:3.5.3' 37 | classpath 'com.google.gms:google-services:4.3.10' 38 | } 39 | } 40 | } 41 | 42 | apply plugin: 'com.android.library' 43 | apply plugin: 'maven-publish' 44 | 45 | android { 46 | compileSdkVersion 33 47 | buildToolsVersion "30.0.3" 48 | namespace "com.afrihost.firebase.notifications" 49 | defaultConfig { 50 | minSdkVersion 21 51 | targetSdkVersion 33 52 | versionCode 1 53 | versionName "1.0" 54 | } 55 | lintOptions { 56 | abortOnError false 57 | } 58 | } 59 | 60 | repositories { 61 | // ref: https://www.baeldung.com/maven-local-repository 62 | mavenLocal() 63 | mavenCentral() 64 | maven { 65 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 66 | url "$rootDir/../node_modules/react-native/android" 67 | } 68 | maven { 69 | // Android JSC is installed from npm 70 | url "$rootDir/../node_modules/jsc-android/dist" 71 | } 72 | google() 73 | 74 | def found = false 75 | def parentDir = rootProject.projectDir 76 | def androidSourcesName = 'React Native sources' 77 | def androidPrebuiltBinaryName = 'React Native prebuilt binary' 78 | 79 | 1.upto(4, { 80 | if (found) return true 81 | parentDir = parentDir.parentFile 82 | 83 | // Running React Native from sources locally or for ExpoKit 84 | def androidSourcesDir = new File( 85 | parentDir, 86 | 'node_modules/react-native' 87 | ) 88 | 89 | // Official releases of React Native come with a prebuilt version of Android sources 90 | // in ./android, e.g. react-native/android/**/react-native-0.57.1.aar 91 | 92 | def androidPrebuiltBinaryDir = new File( 93 | parentDir, 94 | 'node_modules/react-native/android' 95 | ) 96 | 97 | if (androidPrebuiltBinaryDir.exists()) { 98 | maven { 99 | url androidPrebuiltBinaryDir.toString() 100 | name androidPrebuiltBinaryName 101 | } 102 | 103 | println "${project.name}: using ${androidPrebuiltBinaryName} from ${androidPrebuiltBinaryDir.toString()}" 104 | found = true 105 | } else if (androidSourcesDir.exists()) { 106 | maven { 107 | url androidSourcesDir.toString() 108 | name androidSourcesName 109 | } 110 | 111 | println "${project.name}: using ${androidSourcesName} from ${androidSourcesDir.toString()}" 112 | found = true 113 | } 114 | }) 115 | 116 | if (!found) { 117 | throw new FileNotFoundException( 118 | "${project.name}: unable to locate React Native android sources or prebuilt binary. " + 119 | "Ensure you have you installed React Native as a dependency in your project and try again." 120 | ) 121 | } 122 | } 123 | 124 | dependencies { 125 | implementation 'androidx.work:work-runtime-ktx:2.7.1' 126 | //noinspection GradleDynamicVersion 127 | implementation 'com.facebook.react:react-native:+' // From node_modules 128 | // Add the Firebase SDK for Google Analytics 129 | implementation platform('com.google.firebase:firebase-bom:29.1.0') 130 | implementation 'com.google.firebase:firebase-messaging' 131 | implementation "me.leolin:ShortcutBadger:1.1.22@aar" 132 | 133 | // Declare the dependency for the Firebase SDK for Google Analytics 134 | implementation 'com.google.firebase:firebase-analytics' 135 | 136 | 137 | // Add the SDKs for any other Firebase products you want to use in your app 138 | // For example, to use Firebase Authentication and Cloud Firestore 139 | // implementation 'com.google.firebase:firebase-auth:19.2.0' 140 | // implementation 'com.google.firebase:firebase-firestore:21.3.1' 141 | 142 | // Getting a "Could not find" error? Make sure that you've added 143 | // Google's Maven repository to your root-level build.gradle file 144 | //compileOnly 'me.leolin:ShortcutBadger:1.1.22@aar' 145 | } 146 | 147 | def configureReactNativePom(def pom) { 148 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 149 | 150 | pom.project { 151 | name packageJson.title 152 | artifactId packageJson.name 153 | version = packageJson.version 154 | group = "com.afrihost.firebase.notifications" 155 | description packageJson.description 156 | url packageJson.repository.baseUrl 157 | 158 | licenses { 159 | license { 160 | name packageJson.license 161 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 162 | distribution 'repo' 163 | } 164 | } 165 | 166 | developers { 167 | developer { 168 | id packageJson.author.username 169 | name packageJson.author.name 170 | } 171 | } 172 | } 173 | } 174 | 175 | afterEvaluate { project -> 176 | // some Gradle build hooks ref: 177 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 178 | task androidJavadoc(type: Javadoc) { 179 | source = android.sourceSets.main.java.srcDirs 180 | classpath += files(android.bootClasspath) 181 | project.getConfigurations().getByName('implementation').setCanBeResolved(true) 182 | include '**/*.java' 183 | } 184 | 185 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 186 | archiveClassifier = 'javadoc' 187 | from androidJavadoc.destinationDir 188 | } 189 | 190 | task androidSourcesJar(type: Jar) { 191 | archiveClassifier = 'javadoc' 192 | from android.sourceSets.main.java.srcDirs 193 | include '**/*.java' 194 | } 195 | 196 | android.libraryVariants.all { variant -> 197 | def name = variant.name.capitalize() 198 | def javaCompileTask = variant.javaCompileProvider.get() 199 | 200 | task "jar${name}"(type: Jar, dependsOn: javaCompileTask) { 201 | from javaCompileTask.destinationDir 202 | } 203 | } 204 | 205 | artifacts { 206 | archives androidSourcesJar 207 | archives androidJavadocJar 208 | } 209 | 210 | publishing { 211 | repositories { 212 | maven { 213 | url = uri("${rootProject.projectDir}/maven-repo") 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afrihost/react-native-firebase-push-notifications/7e3bcb8cd2a3bf7ee7003a5557f0847c68ebcb26/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jan 08 10:50:35 SAST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/BundleJSONConverter.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | // taken from https://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/main/java/com/facebook/internal/BundleJSONConverter.java 4 | 5 | /* 6 | * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. 7 | *

8 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, 9 | * copy, modify, and distribute this software in source code or binary form for use 10 | * in connection with the web services and APIs provided by Facebook. 11 | *

12 | * As with any software that integrates with the Facebook platform, your use of 13 | * this software is subject to the Facebook Developer Principles and Policies 14 | * [http://developers.facebook.com/policy/]. This copyright notice shall be 15 | * included in all copies or substantial portions of the software. 16 | *

17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | */ 24 | 25 | 26 | import android.os.Bundle; 27 | 28 | import org.json.JSONArray; 29 | import org.json.JSONException; 30 | import org.json.JSONObject; 31 | 32 | import java.util.ArrayList; 33 | import java.util.HashMap; 34 | import java.util.Iterator; 35 | import java.util.List; 36 | import java.util.Map; 37 | 38 | /** 39 | * com.facebook.internal is solely for the use of other packages within the Facebook SDK for 40 | * Android. Use of any of the classes in this package is unsupported, and they may be modified or 41 | * removed without warning at any time. 42 | *

43 | * A helper class that can round trip between JSON and Bundle objects that contains the types: 44 | * Boolean, Integer, Long, Double, String 45 | * If other types are found, an IllegalArgumentException is thrown. 46 | */ 47 | public class BundleJSONConverter { 48 | private static final Map, Setter> SETTERS = new HashMap<>(); 49 | 50 | static { 51 | SETTERS.put(Boolean.class, new Setter() { 52 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 53 | bundle.putBoolean(key, (Boolean) value); 54 | } 55 | 56 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 57 | json.put(key, value); 58 | } 59 | }); 60 | SETTERS.put(Integer.class, new Setter() { 61 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 62 | bundle.putInt(key, (Integer) value); 63 | } 64 | 65 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 66 | json.put(key, value); 67 | } 68 | }); 69 | SETTERS.put(Long.class, new Setter() { 70 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 71 | bundle.putLong(key, (Long) value); 72 | } 73 | 74 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 75 | json.put(key, value); 76 | } 77 | }); 78 | SETTERS.put(Double.class, new Setter() { 79 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 80 | bundle.putDouble(key, (Double) value); 81 | } 82 | 83 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 84 | json.put(key, value); 85 | } 86 | }); 87 | SETTERS.put(String.class, new Setter() { 88 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 89 | bundle.putString(key, (String) value); 90 | } 91 | 92 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 93 | json.put(key, value); 94 | } 95 | }); 96 | SETTERS.put(String[].class, new Setter() { 97 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 98 | throw new IllegalArgumentException("Unexpected type from JSON"); 99 | } 100 | 101 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 102 | JSONArray jsonArray = new JSONArray(); 103 | for (String stringValue : (String[]) value) { 104 | jsonArray.put(stringValue); 105 | } 106 | json.put(key, jsonArray); 107 | } 108 | }); 109 | 110 | SETTERS.put(JSONArray.class, new Setter() { 111 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 112 | JSONArray jsonArray = (JSONArray) value; 113 | // Assume an empty list is an ArrayList 114 | if (jsonArray.length() == 0 || jsonArray.get(0) instanceof String) { 115 | ArrayList stringArrayList = new ArrayList<>(); 116 | for (int i = 0; i < jsonArray.length(); i++) { 117 | stringArrayList.add((String) jsonArray.get(i)); 118 | } 119 | bundle.putStringArrayList(key, stringArrayList); 120 | } else if (jsonArray.get(0) instanceof Integer) { 121 | ArrayList integerArrayList = new ArrayList<>(); 122 | for (int i = 0; i < jsonArray.length(); i++) { 123 | integerArrayList.add((Integer) jsonArray.get(i)); 124 | } 125 | bundle.putIntegerArrayList(key, integerArrayList); 126 | } else if (jsonArray.get(0) instanceof Boolean) { 127 | boolean[] booleanArray = new boolean[jsonArray.length()]; 128 | for (int i = 0; i < jsonArray.length(); i++) { 129 | booleanArray[i] = (Boolean) jsonArray.get(i); 130 | } 131 | bundle.putBooleanArray(key, booleanArray); 132 | } else if (jsonArray.get(0) instanceof Double) { 133 | double[] doubleArray = new double[jsonArray.length()]; 134 | for (int i = 0; i < jsonArray.length(); i++) { 135 | doubleArray[i] = (Double) jsonArray.get(i); 136 | } 137 | bundle.putDoubleArray(key, doubleArray); 138 | } else if (jsonArray.get(0) instanceof Long) { 139 | long[] longArray = new long[jsonArray.length()]; 140 | for (int i = 0; i < jsonArray.length(); i++) { 141 | longArray[i] = (Long) jsonArray.get(i); 142 | } 143 | bundle.putLongArray(key, longArray); 144 | } else if (jsonArray.get(0) instanceof JSONObject) { 145 | ArrayList bundleArrayList = new ArrayList<>(); 146 | for (int i = 0; i < jsonArray.length(); i++) { 147 | bundleArrayList.add(convertToBundle((JSONObject) jsonArray.get(i))); 148 | } 149 | bundle.putSerializable(key, bundleArrayList); 150 | } else { 151 | throw new IllegalArgumentException("Unexpected type in an array: " + jsonArray 152 | .get(0) 153 | .getClass()); 154 | } 155 | } 156 | 157 | @Override 158 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 159 | throw new IllegalArgumentException("JSONArray's are not supported in bundles."); 160 | } 161 | }); 162 | } 163 | 164 | public static JSONObject convertToJSON(Bundle bundle) throws JSONException { 165 | JSONObject json = new JSONObject(); 166 | 167 | for (String key : bundle.keySet()) { 168 | Object value = bundle.get(key); 169 | if (value == null) { 170 | // Null is not supported. 171 | continue; 172 | } 173 | 174 | // Special case List as getClass would not work, since List is an interface 175 | if (value instanceof List) { 176 | JSONArray jsonArray = new JSONArray(); 177 | List listValue = (List) value; 178 | for (Object objValue : listValue) { 179 | if (objValue instanceof String 180 | || objValue instanceof Integer 181 | || objValue instanceof Double 182 | || objValue instanceof Long 183 | || objValue instanceof Boolean) { 184 | jsonArray.put(objValue); 185 | } else if (objValue instanceof Bundle) { 186 | jsonArray.put(convertToJSON((Bundle) objValue)); 187 | } else { 188 | throw new IllegalArgumentException("Unsupported type: " + objValue.getClass()); 189 | } 190 | } 191 | json.put(key, jsonArray); 192 | continue; 193 | } 194 | 195 | // Special case Bundle as it's one way, on the return it will be JSONObject 196 | if (value instanceof Bundle) { 197 | json.put(key, convertToJSON((Bundle) value)); 198 | continue; 199 | } 200 | 201 | Setter setter = SETTERS.get(value.getClass()); 202 | if (setter == null) { 203 | throw new IllegalArgumentException("Unsupported type: " + value.getClass()); 204 | } 205 | setter.setOnJSON(json, key, value); 206 | } 207 | 208 | return json; 209 | } 210 | 211 | public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException { 212 | Bundle bundle = new Bundle(); 213 | @SuppressWarnings("unchecked") 214 | Iterator jsonIterator = jsonObject.keys(); 215 | while (jsonIterator.hasNext()) { 216 | String key = jsonIterator.next(); 217 | Object value = jsonObject.get(key); 218 | if (value == null || value == JSONObject.NULL) { 219 | // Null is not supported. 220 | continue; 221 | } 222 | 223 | // Special case JSONObject as it's one way, on the return it would be Bundle. 224 | if (value instanceof JSONObject) { 225 | bundle.putBundle(key, convertToBundle((JSONObject) value)); 226 | continue; 227 | } 228 | 229 | Setter setter = SETTERS.get(value.getClass()); 230 | if (setter == null) { 231 | throw new IllegalArgumentException("Unsupported type: " + value.getClass()); 232 | } 233 | setter.setOnBundle(bundle, key, value); 234 | } 235 | 236 | return bundle; 237 | } 238 | 239 | public interface Setter { 240 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException; 241 | 242 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException; 243 | } 244 | } -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/DisplayNotificationTask.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationManager; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.graphics.Bitmap; 9 | import android.graphics.BitmapFactory; 10 | import android.graphics.Color; 11 | import android.net.Uri; 12 | import android.os.AsyncTask; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import androidx.core.app.NotificationCompat; 16 | import androidx.core.app.RemoteInput; 17 | import android.util.Log; 18 | 19 | import com.facebook.react.bridge.Arguments; 20 | import com.facebook.react.bridge.Promise; 21 | import com.facebook.react.bridge.ReactApplicationContext; 22 | 23 | import java.io.IOException; 24 | import java.lang.ref.WeakReference; 25 | import java.net.HttpURLConnection; 26 | import java.net.URL; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | 30 | import com.afrihost.firebase.notifications.Utils; 31 | 32 | public class DisplayNotificationTask extends AsyncTask { 33 | private static final String TAG = "DisplayNotificationTask"; 34 | private final WeakReference contextWeakReference; 35 | private final WeakReference reactContextWeakReference; 36 | 37 | private final Promise promise; 38 | private final Bundle notification; 39 | private final NotificationManager notificationManager; 40 | 41 | DisplayNotificationTask( 42 | Context context, ReactApplicationContext reactContext, 43 | NotificationManager notificationManager, 44 | Bundle notification, Promise promise 45 | ) { 46 | this.contextWeakReference = new WeakReference<>(context); 47 | this.reactContextWeakReference = new WeakReference<>(reactContext); 48 | 49 | this.promise = promise; 50 | this.notification = notification; 51 | this.notificationManager = notificationManager; 52 | } 53 | 54 | @Override 55 | protected void onPostExecute(Void result) { 56 | contextWeakReference.clear(); 57 | reactContextWeakReference.clear(); 58 | } 59 | 60 | @Override 61 | protected Void doInBackground(Void... voids) { 62 | Context context = contextWeakReference.get(); 63 | if (context == null) return null; 64 | 65 | try { 66 | Class intentClass = getMainActivityClass(context); 67 | 68 | if (intentClass == null) { 69 | if (promise != null) { 70 | promise.reject( 71 | "notification/display_notification_error", 72 | "Could not find main activity class" 73 | ); 74 | } 75 | return null; 76 | } 77 | 78 | Bundle android = notification.getBundle("android"); 79 | String notificationId = notification.getString("notificationId"); 80 | 81 | NotificationCompat.Builder nb; 82 | try { 83 | String channelId = android.getString("channelId"); 84 | nb = new NotificationCompat.Builder(context, channelId); 85 | } catch (Throwable t) { 86 | // thrown if v4 android support library < 26 87 | nb = new NotificationCompat.Builder(context); 88 | } 89 | 90 | if (notification.containsKey("body")) { 91 | nb = nb.setContentText(notification.getString("body")); 92 | } 93 | 94 | if (notification.containsKey("data")) { 95 | nb = nb.setExtras(notification.getBundle("data")); 96 | } 97 | 98 | if (notification.containsKey("sound")) { 99 | Uri sound = RNFirebaseNotificationManager.getSound( 100 | context, 101 | notification.getString("sound") 102 | ); 103 | nb = nb.setSound(sound); 104 | } 105 | 106 | if (notification.containsKey("subtitle")) { 107 | nb = nb.setSubText(notification.getString("subtitle")); 108 | } 109 | 110 | if (notification.containsKey("title")) { 111 | nb = nb.setContentTitle(notification.getString("title")); 112 | } 113 | 114 | if (android.containsKey("autoCancel")) { 115 | nb = nb.setAutoCancel(android.getBoolean("autoCancel")); 116 | } 117 | 118 | if (android.containsKey("badgeIconType") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 119 | Double badgeIconType = android.getDouble("badgeIconType"); 120 | try { 121 | nb = nb.setBadgeIconType(badgeIconType.intValue()); 122 | } catch (Throwable t) { 123 | // thrown if v4 android support library < 26 124 | // do nothing 125 | } 126 | } 127 | 128 | if (android.containsKey("bigPicture")) { 129 | Bundle bigPicture = android.getBundle("bigPicture"); 130 | NotificationCompat.BigPictureStyle bp = new NotificationCompat.BigPictureStyle(); 131 | Bitmap picture = getBitmap(bigPicture.getString("picture")); 132 | 133 | if (picture != null) { 134 | bp = bp.bigPicture(picture); 135 | } 136 | 137 | if (bigPicture.containsKey("largeIcon")) { 138 | String largeIconStr = bigPicture.getString("largeIcon"); 139 | if (largeIconStr == null) { 140 | bp = bp.bigLargeIcon(null); 141 | } else { 142 | Bitmap largeIconBitmap = getBitmap(largeIconStr); 143 | if (largeIconBitmap != null) { 144 | bp = bp.bigLargeIcon(largeIconBitmap); 145 | } 146 | } 147 | } 148 | 149 | if (bigPicture.containsKey("contentTitle")) { 150 | bp = bp.setBigContentTitle(bigPicture.getString("contentTitle")); 151 | } 152 | 153 | if (bigPicture.containsKey("summaryText")) { 154 | bp = bp.setSummaryText(bigPicture.getString("summaryText")); 155 | } 156 | 157 | nb = nb.setStyle(bp); 158 | } 159 | 160 | if (android.containsKey("bigText")) { 161 | Bundle bigText = android.getBundle("bigText"); 162 | 163 | NotificationCompat.BigTextStyle bt = new NotificationCompat.BigTextStyle(); 164 | bt.bigText(bigText.getString("text")); 165 | if (bigText.containsKey("contentTitle")) { 166 | bt = bt.setBigContentTitle(bigText.getString("contentTitle")); 167 | } 168 | if (bigText.containsKey("summaryText")) { 169 | bt = bt.setSummaryText(bigText.getString("summaryText")); 170 | } 171 | nb = nb.setStyle(bt); 172 | } 173 | 174 | if (android.containsKey("category")) { 175 | nb = nb.setCategory(android.getString("category")); 176 | } 177 | 178 | if (android.containsKey("color")) { 179 | String color = android.getString("color"); 180 | nb = nb.setColor(Color.parseColor(color)); 181 | } 182 | 183 | if (android.containsKey("colorized") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 184 | try { 185 | nb = nb.setColorized(android.getBoolean("colorized")); 186 | } catch (Throwable t) { 187 | // thrown if v4 android support library < 26 188 | // do nothing 189 | } 190 | } 191 | 192 | if (android.containsKey("contentInfo")) { 193 | nb = nb.setContentInfo(android.getString("contentInfo")); 194 | } 195 | 196 | if (android.containsKey("defaults")) { 197 | Double defaultValues = android.getDouble("defaults"); 198 | int defaults = defaultValues.intValue(); 199 | 200 | if (defaults == 0) { 201 | ArrayList defaultsArray = android.getIntegerArrayList("defaults"); 202 | if (defaultsArray != null) { 203 | for (Integer defaultValue : defaultsArray) { 204 | defaults |= defaultValue; 205 | } 206 | } 207 | } 208 | 209 | nb = nb.setDefaults(defaults); 210 | } 211 | 212 | if (android.containsKey("group")) { 213 | nb = nb.setGroup(android.getString("group")); 214 | } 215 | 216 | if (android.containsKey("groupAlertBehaviour") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 217 | Double groupAlertBehaviour = android.getDouble("groupAlertBehaviour"); 218 | try { 219 | nb = nb.setGroupAlertBehavior(groupAlertBehaviour.intValue()); 220 | } catch (Throwable t) { 221 | // thrown if v4 android support library < 26 222 | // do nothing 223 | } 224 | } 225 | 226 | if (android.containsKey("groupSummary")) { 227 | nb = nb.setGroupSummary(android.getBoolean("groupSummary")); 228 | } 229 | 230 | if (android.containsKey("largeIcon")) { 231 | Bitmap largeIcon = getBitmap(android.getString("largeIcon")); 232 | if (largeIcon != null) { 233 | nb = nb.setLargeIcon(largeIcon); 234 | } 235 | } 236 | 237 | if (android.containsKey("lights")) { 238 | Bundle lights = android.getBundle("lights"); 239 | Double argb = lights.getDouble("argb"); 240 | Double onMs = lights.getDouble("onMs"); 241 | Double offMs = lights.getDouble("offMs"); 242 | nb = nb.setLights(argb.intValue(), onMs.intValue(), offMs.intValue()); 243 | } 244 | 245 | if (android.containsKey("localOnly")) { 246 | nb = nb.setLocalOnly(android.getBoolean("localOnly")); 247 | } 248 | 249 | if (android.containsKey("number")) { 250 | Double number = android.getDouble("number"); 251 | nb = nb.setNumber(number.intValue()); 252 | } 253 | 254 | if (android.containsKey("ongoing")) { 255 | nb = nb.setOngoing(android.getBoolean("ongoing")); 256 | } 257 | 258 | if (android.containsKey("onlyAlertOnce")) { 259 | nb = nb.setOnlyAlertOnce(android.getBoolean("onlyAlertOnce")); 260 | } 261 | 262 | if (android.containsKey("people")) { 263 | List people = android.getStringArrayList("people"); 264 | if (people != null) { 265 | for (String person : people) { 266 | nb = nb.addPerson(person); 267 | } 268 | } 269 | } 270 | 271 | if (android.containsKey("priority")) { 272 | Double priority = android.getDouble("priority"); 273 | nb = nb.setPriority(priority.intValue()); 274 | } 275 | 276 | if (android.containsKey("progress")) { 277 | Bundle progress = android.getBundle("progress"); 278 | Double max = progress.getDouble("max"); 279 | Double progressI = progress.getDouble("progress"); 280 | nb = nb.setProgress( 281 | max.intValue(), 282 | progressI.intValue(), 283 | progress.getBoolean("indeterminate") 284 | ); 285 | } 286 | 287 | // TODO: Public version of notification 288 | /* if (android.containsKey("publicVersion")) { 289 | nb = nb.setPublicVersion(); 290 | } */ 291 | 292 | if (android.containsKey("remoteInputHistory")) { 293 | nb = nb.setRemoteInputHistory(android.getStringArray("remoteInputHistory")); 294 | } 295 | 296 | if (android.containsKey("shortcutId") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 297 | try { 298 | nb = nb.setShortcutId(android.getString("shortcutId")); 299 | } catch (Throwable t) { 300 | // thrown if v4 android support library < 26 301 | // do nothing 302 | } 303 | } 304 | 305 | if (android.containsKey("showWhen")) { 306 | nb = nb.setShowWhen(android.getBoolean("showWhen")); 307 | } 308 | 309 | if (android.containsKey("smallIcon")) { 310 | Bundle smallIcon = android.getBundle("smallIcon"); 311 | int smallIconResourceId = getIcon(smallIcon.getString("icon")); 312 | if (smallIconResourceId != 0) { 313 | if (smallIcon.containsKey("level")) { 314 | Double level = smallIcon.getDouble("level"); 315 | nb = nb.setSmallIcon(smallIconResourceId, level.intValue()); 316 | } else { 317 | nb = nb.setSmallIcon(smallIconResourceId); 318 | } 319 | } 320 | } 321 | 322 | if (android.containsKey("sortKey")) { 323 | nb = nb.setSortKey(android.getString("sortKey")); 324 | } 325 | 326 | if (android.containsKey("ticker")) { 327 | nb = nb.setTicker(android.getString("ticker")); 328 | } 329 | 330 | if (android.containsKey("timeoutAfter")) { 331 | Double timeoutAfter = android.getDouble("timeoutAfter"); 332 | nb = nb.setTimeoutAfter(timeoutAfter.longValue()); 333 | } 334 | 335 | if (android.containsKey("usesChronometer")) { 336 | nb = nb.setUsesChronometer(android.getBoolean("usesChronometer")); 337 | } 338 | 339 | if (android.containsKey("vibrate")) { 340 | ArrayList vibrate = android.getIntegerArrayList("vibrate"); 341 | if (vibrate != null) { 342 | long[] vibrateArray = new long[vibrate.size()]; 343 | for (int i = 0; i < vibrate.size(); i++) { 344 | vibrateArray[i] = vibrate 345 | .get(i) 346 | .longValue(); 347 | } 348 | nb = nb.setVibrate(vibrateArray); 349 | } 350 | } 351 | 352 | if (android.containsKey("visibility")) { 353 | Double visibility = android.getDouble("visibility"); 354 | nb = nb.setVisibility(visibility.intValue()); 355 | } 356 | 357 | if (android.containsKey("when")) { 358 | Double when = android.getDouble("when"); 359 | nb = nb.setWhen(when.longValue()); 360 | } 361 | 362 | // Build any actions 363 | if (android.containsKey("actions")) { 364 | List actions = (List) android.getSerializable("actions"); 365 | for (Bundle a : actions) { 366 | NotificationCompat.Action action = createAction(context, a, intentClass, notification); 367 | nb = nb.addAction(action); 368 | } 369 | } 370 | 371 | String tag = null; 372 | if (android.containsKey("tag")) { 373 | tag = android.getString("tag"); 374 | } 375 | 376 | // Create the notification intent 377 | PendingIntent contentIntent = createIntent( 378 | context, 379 | intentClass, 380 | notification, 381 | android.getString("clickAction") 382 | ); 383 | 384 | nb = nb.setContentIntent(contentIntent); 385 | 386 | // Build the notification and send it 387 | Notification builtNotification = nb.build(); 388 | notificationManager.notify(tag, notificationId.hashCode(), builtNotification); 389 | 390 | if (reactContextWeakReference.get() != null) { 391 | Utils.sendEvent( 392 | reactContextWeakReference.get(), 393 | "notifications_notification_displayed", 394 | Arguments.fromBundle(notification) 395 | ); 396 | } 397 | 398 | if (promise != null) { 399 | promise.resolve(null); 400 | } 401 | 402 | } catch (Exception e) { 403 | Log.e(TAG, "Failed to send notification", e); 404 | if (promise != null) { 405 | promise.reject("notification/display_notification_error", "Could not send notification", e); 406 | } 407 | } 408 | 409 | return null; 410 | } 411 | 412 | private NotificationCompat.Action createAction( 413 | Context context, 414 | Bundle action, 415 | Class intentClass, 416 | Bundle notification 417 | ) { 418 | String actionKey = action.getString("action"); 419 | boolean showUserInterface = action.containsKey("showUserInterface") && action.getBoolean( 420 | "showUserInterface"); 421 | 422 | PendingIntent actionIntent = showUserInterface ? 423 | createIntent(context, intentClass, notification, actionKey) : 424 | createBroadcastIntent(context, notification, actionKey); 425 | 426 | int icon = getIcon(action.getString("icon")); 427 | String title = action.getString("title"); 428 | 429 | NotificationCompat.Action.Builder ab = new NotificationCompat.Action.Builder( 430 | icon, 431 | title, 432 | actionIntent 433 | ); 434 | 435 | if (action.containsKey("allowGeneratedReplies")) { 436 | ab = ab.setAllowGeneratedReplies(action.getBoolean("allowGeneratedReplies")); 437 | } 438 | 439 | if (action.containsKey("remoteInputs")) { 440 | List remoteInputs = (List) action.getSerializable("remoteInputs"); 441 | for (Bundle ri : remoteInputs) { 442 | RemoteInput remoteInput = createRemoteInput(ri); 443 | ab = ab.addRemoteInput(remoteInput); 444 | } 445 | } 446 | 447 | // TODO: SemanticAction and ShowsUserInterface only available on v28? 448 | // if (action.containsKey("semanticAction")) { 449 | // Double semanticAction = action.getDouble("semanticAction"); 450 | // ab = ab.setSemanticAction(semanticAction.intValue()); 451 | // } 452 | // if (action.containsKey("showsUserInterface")) { 453 | // ab = ab.setShowsUserInterface(action.getBoolean("showsUserInterface")); 454 | // } 455 | 456 | return ab.build(); 457 | } 458 | 459 | private PendingIntent createIntent( 460 | Context context, 461 | Class intentClass, 462 | Bundle notification, 463 | String action 464 | ) { 465 | Intent intent = new Intent(context, intentClass); 466 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 467 | intent.putExtras(notification); 468 | 469 | if (action != null) { 470 | intent.setAction(action); 471 | } 472 | 473 | String notificationId = notification.getString("notificationId"); 474 | return PendingIntent.getActivity( 475 | context, 476 | notificationId.hashCode(), 477 | intent, 478 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? 479 | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : 480 | PendingIntent.FLAG_UPDATE_CURRENT 481 | ); 482 | } 483 | 484 | private PendingIntent createBroadcastIntent(Context context, Bundle notification, String action) { 485 | String notificationId = notification.getString("notificationId") + action; 486 | Intent intent = new Intent(context, RNFirebaseBackgroundNotificationActionReceiver.class); 487 | 488 | intent.putExtra("action", action); 489 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 490 | intent.putExtra("notification", notification); 491 | intent.setAction("io.invertase.firebase.notifications.BackgroundAction"); 492 | 493 | return PendingIntent.getBroadcast( 494 | context, 495 | notificationId.hashCode(), 496 | intent, 497 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? 498 | PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : 499 | PendingIntent.FLAG_UPDATE_CURRENT 500 | ); 501 | } 502 | 503 | private RemoteInput createRemoteInput(Bundle remoteInput) { 504 | String resultKey = remoteInput.getString("resultKey"); 505 | RemoteInput.Builder rb = new RemoteInput.Builder(resultKey); 506 | 507 | if (remoteInput.containsKey("allowedDataTypes")) { 508 | List allowedDataTypes = (List) remoteInput.getSerializable("allowedDataTypes"); 509 | for (Bundle adt : allowedDataTypes) { 510 | rb.setAllowDataType(adt.getString("mimeType"), adt.getBoolean("allow")); 511 | } 512 | } 513 | 514 | if (remoteInput.containsKey("allowFreeFormInput")) { 515 | rb.setAllowFreeFormInput(remoteInput.getBoolean("allowFreeFormInput")); 516 | } 517 | 518 | if (remoteInput.containsKey("choices")) { 519 | List choices = remoteInput.getStringArrayList("choices"); 520 | rb.setChoices(choices.toArray(new String[choices.size()])); 521 | } 522 | 523 | if (remoteInput.containsKey("label")) { 524 | rb.setLabel(remoteInput.getString("label")); 525 | } 526 | 527 | return rb.build(); 528 | } 529 | 530 | private Bitmap getBitmap(String image) { 531 | if (image.startsWith("http://") || image.startsWith("https://")) { 532 | return getBitmapFromUrl(image); 533 | } 534 | 535 | if (image.startsWith("file://")) { 536 | return BitmapFactory.decodeFile(image.replace("file://", "")); 537 | } 538 | 539 | int largeIconResId = getIcon(image); 540 | return BitmapFactory.decodeResource( 541 | contextWeakReference.get().getResources(), 542 | largeIconResId 543 | ); 544 | } 545 | 546 | private Bitmap getBitmapFromUrl(String imageUrl) { 547 | try { 548 | HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection(); 549 | connection.setDoInput(true); 550 | connection.connect(); 551 | return BitmapFactory.decodeStream(connection.getInputStream()); 552 | } catch (IOException e) { 553 | Log.e(TAG, "Failed to get bitmap for url: " + imageUrl, e); 554 | return null; 555 | } 556 | } 557 | 558 | private int getIcon(String icon) { 559 | int resourceId = RNFirebaseNotificationManager.getResourceId( 560 | contextWeakReference.get(), 561 | "mipmap", 562 | icon 563 | ); 564 | 565 | if (resourceId == 0) { 566 | resourceId = RNFirebaseNotificationManager.getResourceId( 567 | contextWeakReference.get(), 568 | "drawable", 569 | icon 570 | ); 571 | } 572 | 573 | return resourceId; 574 | } 575 | 576 | private Class getMainActivityClass(Context context) { 577 | String packageName = context.getPackageName(); 578 | Intent launchIntent = context 579 | .getPackageManager() 580 | .getLaunchIntentForPackage(packageName); 581 | 582 | try { 583 | return Class.forName(launchIntent.getComponent().getClassName()); 584 | } catch (ClassNotFoundException e) { 585 | Log.e(TAG, "Failed to get main activity class", e); 586 | return null; 587 | } 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/FirebasePushNotificationsPackage.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | 13 | public class FirebasePushNotificationsPackage implements ReactPackage { 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactContext) { 16 | return Arrays.asList(new FirebasePushNotificationsModule(reactContext)); 17 | } 18 | 19 | @Override 20 | public List createViewManagers(ReactApplicationContext reactContext) { 21 | return Collections.emptyList(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/MessagingSerializer.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import com.facebook.react.bridge.Arguments; 4 | import com.facebook.react.bridge.WritableMap; 5 | import com.google.firebase.messaging.RemoteMessage; 6 | 7 | import java.util.Map; 8 | 9 | public class MessagingSerializer { 10 | public static WritableMap parseRemoteMessage(RemoteMessage message) { 11 | WritableMap messageMap = Arguments.createMap(); 12 | WritableMap dataMap = Arguments.createMap(); 13 | 14 | if (message.getCollapseKey() != null) { 15 | messageMap.putString("collapseKey", message.getCollapseKey()); 16 | } 17 | 18 | if (message.getData() != null) { 19 | for (Map.Entry e : message 20 | .getData() 21 | .entrySet()) { 22 | dataMap.putString(e.getKey(), e.getValue()); 23 | } 24 | } 25 | messageMap.putMap("data", dataMap); 26 | 27 | if (message.getFrom() != null) { 28 | messageMap.putString("from", message.getFrom()); 29 | } 30 | if (message.getMessageId() != null) { 31 | messageMap.putString("messageId", message.getMessageId()); 32 | } 33 | if (message.getMessageType() != null) { 34 | messageMap.putString("messageType", message.getMessageType()); 35 | } 36 | messageMap.putDouble("sentTime", message.getSentTime()); 37 | if (message.getTo() != null) { 38 | messageMap.putString("to", message.getTo()); 39 | } 40 | messageMap.putDouble("ttl", message.getTtl()); 41 | 42 | return messageMap; 43 | } 44 | } -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/MyWorker.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.NonNull; 5 | import android.util.Log; 6 | 7 | import androidx.work.Worker; 8 | import androidx.work.WorkerParameters; 9 | 10 | public class MyWorker extends Worker { 11 | 12 | private static final String TAG = "MyWorker"; 13 | 14 | public MyWorker(@NonNull Context appContext, @NonNull WorkerParameters workerParams) { 15 | super(appContext, workerParams); 16 | } 17 | 18 | @NonNull 19 | @Override 20 | public Result doWork() { 21 | Log.d(TAG, "Performing long running task in scheduled job"); 22 | // TODO(developer): add long running task here. 23 | return Result.success(); 24 | } 25 | } -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseBackgroundMessagingService.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import com.facebook.react.HeadlessJsTaskService; 7 | import com.facebook.react.bridge.WritableMap; 8 | import com.facebook.react.jstasks.HeadlessJsTaskConfig; 9 | import com.google.firebase.messaging.RemoteMessage; 10 | 11 | import javax.annotation.Nullable; 12 | 13 | public class RNFirebaseBackgroundMessagingService extends HeadlessJsTaskService { 14 | @Override 15 | protected @Nullable 16 | HeadlessJsTaskConfig getTaskConfig(Intent intent) { 17 | Bundle extras = intent.getExtras(); 18 | if (extras != null) { 19 | RemoteMessage message = intent.getParcelableExtra("message"); 20 | WritableMap messageMap = MessagingSerializer.parseRemoteMessage(message); 21 | return new HeadlessJsTaskConfig( 22 | "RNFirebaseBackgroundMessage", 23 | messageMap, 24 | 60000, 25 | false 26 | ); 27 | } 28 | return null; 29 | } 30 | } -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseBackgroundNotificationActionReceiver.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.ComponentName; 7 | import android.os.Bundle; 8 | import androidx.core.app.RemoteInput; 9 | 10 | import com.facebook.react.HeadlessJsTaskService; 11 | import com.facebook.react.ReactApplication; 12 | import com.facebook.react.bridge.Arguments; 13 | import com.facebook.react.bridge.ReactContext; 14 | import com.facebook.react.bridge.WritableMap; 15 | 16 | import com.afrihost.firebase.notifications.Utils; 17 | 18 | public class RNFirebaseBackgroundNotificationActionReceiver extends BroadcastReceiver { 19 | static boolean isBackgroundNotficationIntent(Intent intent) { 20 | return intent.getExtras() != null && intent.hasExtra("action") && intent.hasExtra("notification"); 21 | } 22 | 23 | static WritableMap toNotificationOpenMap(Intent intent) { 24 | Bundle extras = intent.getExtras(); 25 | WritableMap notificationMap = Arguments.makeNativeMap(extras.getBundle("notification")); 26 | WritableMap notificationOpenMap = Arguments.createMap(); 27 | notificationOpenMap.putString("action", extras.getString("action")); 28 | notificationOpenMap.putMap("notification", notificationMap); 29 | 30 | Bundle extrasBundle = extras.getBundle("results"); 31 | if (extrasBundle != null) { 32 | WritableMap results = Arguments.makeNativeMap(extrasBundle); 33 | notificationOpenMap.putMap("results", results); 34 | } 35 | 36 | return notificationOpenMap; 37 | } 38 | 39 | @Override 40 | public void onReceive(Context context, Intent intent) { 41 | if (!isBackgroundNotficationIntent(intent)) { 42 | return; 43 | } 44 | 45 | if (Utils.isAppInForeground(context)) { 46 | WritableMap notificationOpenMap = toNotificationOpenMap(intent); 47 | 48 | ReactApplication reactApplication = (ReactApplication) context.getApplicationContext(); 49 | ReactContext reactContext = reactApplication 50 | .getReactNativeHost() 51 | .getReactInstanceManager() 52 | .getCurrentReactContext(); 53 | 54 | Utils.sendEvent(reactContext, "notifications_notification_opened", notificationOpenMap); 55 | } else { 56 | Intent serviceIntent = new Intent( 57 | context, 58 | RNFirebaseBackgroundNotificationActionsService.class 59 | ); 60 | serviceIntent.putExtras(intent.getExtras()); 61 | 62 | Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); 63 | if (remoteInput != null) { 64 | serviceIntent.putExtra("results", remoteInput); 65 | } 66 | ComponentName name = context.startService(serviceIntent); 67 | if (name != null) { 68 | HeadlessJsTaskService.acquireWakeLockNow(context); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseBackgroundNotificationActionsService.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.Intent; 4 | 5 | import com.facebook.react.HeadlessJsTaskService; 6 | import com.facebook.react.bridge.WritableMap; 7 | import com.facebook.react.jstasks.HeadlessJsTaskConfig; 8 | 9 | import javax.annotation.Nullable; 10 | 11 | import static com.afrihost.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver.isBackgroundNotficationIntent; 12 | import static com.afrihost.firebase.notifications.RNFirebaseBackgroundNotificationActionReceiver.toNotificationOpenMap; 13 | 14 | public class RNFirebaseBackgroundNotificationActionsService extends HeadlessJsTaskService { 15 | @Override 16 | protected @Nullable 17 | HeadlessJsTaskConfig getTaskConfig(Intent intent) { 18 | if (isBackgroundNotficationIntent(intent)) { 19 | WritableMap notificationOpenMap = toNotificationOpenMap(intent); 20 | 21 | return new HeadlessJsTaskConfig( 22 | "RNFirebaseBackgroundNotificationAction", 23 | notificationOpenMap, 24 | 60000, 25 | true 26 | ); 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseMessagingService.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.Intent; 4 | import android.content.ComponentName; 5 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 6 | import android.util.Log; 7 | 8 | import com.facebook.react.HeadlessJsTaskService; 9 | import com.google.firebase.messaging.FirebaseMessagingService; 10 | import com.google.firebase.messaging.RemoteMessage; 11 | 12 | public class RNFirebaseMessagingService extends FirebaseMessagingService { 13 | private static final String TAG = "RNFMessagingService"; 14 | 15 | public static final String MESSAGE_EVENT = "messaging-message"; 16 | public static final String NEW_TOKEN_EVENT = "messaging-token-refresh"; 17 | public static final String REMOTE_NOTIFICATION_EVENT = "notifications-remote-notification"; 18 | 19 | @Override 20 | public void onNewToken(String token) { 21 | Log.d(TAG, "onNewToken event received"); 22 | 23 | Intent newTokenEvent = new Intent(NEW_TOKEN_EVENT); 24 | LocalBroadcastManager 25 | .getInstance(this) 26 | .sendBroadcast(newTokenEvent); 27 | } 28 | 29 | @Override 30 | public void onMessageReceived(RemoteMessage message) { 31 | Log.d(TAG, "onMessageReceived event received"); 32 | 33 | if (message.getNotification() != null) { 34 | // It's a notification, pass to the Notifications module 35 | Intent notificationEvent = new Intent(REMOTE_NOTIFICATION_EVENT); 36 | notificationEvent.putExtra("notification", message); 37 | 38 | // Broadcast it to the (foreground) RN Application 39 | LocalBroadcastManager 40 | .getInstance(this) 41 | .sendBroadcast(notificationEvent); 42 | } else { 43 | // It's a data message 44 | // If the app is in the foreground we send it to the Messaging module 45 | if (Utils.isAppInForeground(this.getApplicationContext())) { 46 | Intent messagingEvent = new Intent(MESSAGE_EVENT); 47 | messagingEvent.putExtra("message", message); 48 | // Broadcast it so it is only available to the RN Application 49 | LocalBroadcastManager 50 | .getInstance(this) 51 | .sendBroadcast(messagingEvent); 52 | } else { 53 | try { 54 | // If the app is in the background we send it to the Headless JS Service 55 | Intent headlessIntent = new Intent( 56 | this.getApplicationContext(), 57 | RNFirebaseBackgroundMessagingService.class 58 | ); 59 | headlessIntent.putExtra("message", message); 60 | ComponentName name = this.getApplicationContext().startService(headlessIntent); 61 | if (name != null) { 62 | HeadlessJsTaskService.acquireWakeLockNow(this.getApplicationContext()); 63 | } 64 | } catch (IllegalStateException ex) { 65 | Log.e( 66 | TAG, 67 | "Background messages will only work if the message priority is set to 'high'", 68 | ex 69 | ); 70 | } 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseNotificationReceiver.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | /* 8 | * This is invoked by the Alarm Manager when it is time to display a scheduled notification. 9 | */ 10 | public class RNFirebaseNotificationReceiver extends BroadcastReceiver { 11 | @Override 12 | public void onReceive(Context context, Intent intent) { 13 | new RNFirebaseNotificationManager(context).displayScheduledNotification(intent.getExtras()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseNotifications.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.app.Activity; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.content.SharedPreferences; 9 | import android.os.Bundle; 10 | import androidx.core.app.RemoteInput; 11 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 12 | import android.util.Log; 13 | 14 | import com.facebook.react.bridge.ActivityEventListener; 15 | import com.facebook.react.bridge.Arguments; 16 | import com.facebook.react.bridge.Promise; 17 | import com.facebook.react.bridge.ReactApplicationContext; 18 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 19 | import com.facebook.react.bridge.ReactMethod; 20 | import com.facebook.react.bridge.ReadableArray; 21 | import com.facebook.react.bridge.ReadableMap; 22 | import com.facebook.react.bridge.WritableArray; 23 | import com.facebook.react.bridge.WritableMap; 24 | import com.google.firebase.messaging.RemoteMessage; 25 | 26 | import java.util.ArrayList; 27 | import java.util.Collections; 28 | import java.util.Map; 29 | 30 | import javax.annotation.Nullable; 31 | 32 | import com.afrihost.firebase.notifications.Utils; 33 | import com.afrihost.firebase.notifications.RNFirebaseMessagingService; 34 | import me.leolin.shortcutbadger.ShortcutBadger; 35 | 36 | import static com.afrihost.firebase.notifications.Utils.getResId; 37 | 38 | public class RNFirebaseNotifications extends ReactContextBaseJavaModule implements ActivityEventListener { 39 | private static final String BADGE_FILE = "BadgeCountFile"; 40 | private static final String BADGE_KEY = "BadgeCount"; 41 | private static final String TAG = "RNFirebaseNotifications"; 42 | 43 | private SharedPreferences sharedPreferences = null; 44 | 45 | private RNFirebaseNotificationManager notificationManager; 46 | 47 | RNFirebaseNotifications(ReactApplicationContext context) { 48 | super(context); 49 | context.addActivityEventListener(this); 50 | 51 | notificationManager = new RNFirebaseNotificationManager(context); 52 | sharedPreferences = context.getSharedPreferences(BADGE_FILE, Context.MODE_PRIVATE); 53 | 54 | LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); 55 | 56 | // Subscribe to remote notification events 57 | localBroadcastManager.registerReceiver( 58 | new RemoteNotificationReceiver(), 59 | new IntentFilter(RNFirebaseMessagingService.REMOTE_NOTIFICATION_EVENT) 60 | ); 61 | 62 | // Subscribe to scheduled notification events 63 | localBroadcastManager.registerReceiver( 64 | new ScheduledNotificationReceiver(), 65 | new IntentFilter(RNFirebaseNotificationManager.SCHEDULED_NOTIFICATION_EVENT) 66 | ); 67 | } 68 | 69 | @Override 70 | public String getName() { 71 | return "RNFirebaseNotifications"; 72 | } 73 | 74 | @ReactMethod 75 | public void cancelAllNotifications(Promise promise) { 76 | notificationManager.cancelAllNotifications(promise); 77 | } 78 | 79 | @ReactMethod 80 | public void cancelNotification(String notificationId, Promise promise) { 81 | notificationManager.cancelNotification(notificationId, promise); 82 | } 83 | 84 | @ReactMethod 85 | public void displayNotification(ReadableMap notification, Promise promise) { 86 | notificationManager.displayNotification(notification, promise); 87 | } 88 | 89 | @ReactMethod 90 | public void getBadge(Promise promise) { 91 | int badge = sharedPreferences.getInt(BADGE_KEY, 0); 92 | Log.d(TAG, "Got badge count: " + badge); 93 | promise.resolve(badge); 94 | } 95 | 96 | @ReactMethod 97 | public void getInitialNotification(Promise promise) { 98 | WritableMap notificationOpenMap = null; 99 | if (getCurrentActivity() != null) { 100 | notificationOpenMap = parseIntentForNotification(getCurrentActivity().getIntent()); 101 | } 102 | promise.resolve(notificationOpenMap); 103 | } 104 | 105 | @ReactMethod 106 | public void getScheduledNotifications(Promise promise) { 107 | ArrayList bundles = notificationManager.getScheduledNotifications(); 108 | WritableArray array = Arguments.createArray(); 109 | for (Bundle bundle : bundles) { 110 | array.pushMap(parseNotificationBundle(bundle)); 111 | } 112 | promise.resolve(array); 113 | } 114 | 115 | @ReactMethod 116 | public void removeAllDeliveredNotifications(Promise promise) { 117 | notificationManager.removeAllDeliveredNotifications(promise); 118 | } 119 | 120 | @ReactMethod 121 | public void removeDeliveredNotification(String notificationId, Promise promise) { 122 | notificationManager.removeDeliveredNotification(notificationId, promise); 123 | } 124 | 125 | @ReactMethod 126 | public void removeDeliveredNotificationsByTag(String tag, Promise promise) { 127 | notificationManager.removeDeliveredNotificationsByTag(tag, promise); 128 | } 129 | 130 | @ReactMethod 131 | public void setBadge(int badge, Promise promise) { 132 | // Store the badge count for later retrieval 133 | sharedPreferences 134 | .edit() 135 | .putInt(BADGE_KEY, badge) 136 | .apply(); 137 | if (badge == 0) { 138 | Log.d(TAG, "Remove badge count"); 139 | ShortcutBadger.removeCount(this.getReactApplicationContext()); 140 | } else { 141 | Log.d(TAG, "Apply badge count: " + badge); 142 | ShortcutBadger.applyCount(this.getReactApplicationContext(), badge); 143 | } 144 | promise.resolve(null); 145 | } 146 | 147 | @ReactMethod 148 | public void scheduleNotification(ReadableMap notification, Promise promise) { 149 | notificationManager.scheduleNotification(notification, promise); 150 | } 151 | 152 | ////////////////////////////////////////////////////////////////////// 153 | // Start Android specific methods 154 | ////////////////////////////////////////////////////////////////////// 155 | @ReactMethod 156 | public void createChannel(ReadableMap channelMap, Promise promise) { 157 | try { 158 | notificationManager.createChannel(channelMap); 159 | } catch (Throwable t) { 160 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 161 | } 162 | promise.resolve(null); 163 | } 164 | 165 | @ReactMethod 166 | public void createChannelGroup(ReadableMap channelGroupMap, Promise promise) { 167 | try { 168 | notificationManager.createChannelGroup(channelGroupMap); 169 | } catch (Throwable t) { 170 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 171 | } 172 | promise.resolve(null); 173 | } 174 | 175 | @ReactMethod 176 | public void createChannelGroups(ReadableArray channelGroupsArray, Promise promise) { 177 | try { 178 | notificationManager.createChannelGroups(channelGroupsArray); 179 | } catch (Throwable t) { 180 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 181 | } 182 | promise.resolve(null); 183 | } 184 | 185 | @ReactMethod 186 | public void createChannels(ReadableArray channelsArray, Promise promise) { 187 | try { 188 | notificationManager.createChannels(channelsArray); 189 | } catch (Throwable t) { 190 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 191 | } 192 | promise.resolve(null); 193 | } 194 | 195 | @ReactMethod 196 | public void deleteChannelGroup(String channelId, Promise promise) { 197 | try { 198 | notificationManager.deleteChannelGroup(channelId); 199 | promise.resolve(null); 200 | } catch (NullPointerException e) { 201 | promise.reject( 202 | "notifications/channel-group-not-found", 203 | "The requested NotificationChannelGroup does not exist, have you created it?" 204 | ); 205 | } 206 | } 207 | 208 | @ReactMethod 209 | public void deleteChannel(String channelId, Promise promise) { 210 | try { 211 | notificationManager.deleteChannel(channelId); 212 | } catch (Throwable t) { 213 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 214 | } 215 | promise.resolve(null); 216 | } 217 | 218 | @ReactMethod 219 | public void getChannel(String channelId, Promise promise) { 220 | try { 221 | promise.resolve(notificationManager.getChannel(channelId)); 222 | return; 223 | } catch (Throwable t) { 224 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 225 | } 226 | promise.resolve(null); 227 | } 228 | 229 | @ReactMethod 230 | public void getChannels(Promise promise) { 231 | try { 232 | promise.resolve(notificationManager.getChannels()); 233 | return; 234 | } catch (Throwable t) { 235 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 236 | } 237 | promise.resolve(Collections.emptyList()); 238 | } 239 | 240 | @ReactMethod 241 | public void getChannelGroup(String channelGroupId, Promise promise) { 242 | try { 243 | promise.resolve(notificationManager.getChannelGroup(channelGroupId)); 244 | return; 245 | } catch (Throwable t) { 246 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 247 | } 248 | promise.resolve(null); 249 | } 250 | 251 | @ReactMethod 252 | public void getChannelGroups(Promise promise) { 253 | try { 254 | promise.resolve(notificationManager.getChannelGroups()); 255 | return; 256 | } catch (Throwable t) { 257 | // do nothing - most likely a NoSuchMethodError for < v4 support lib 258 | } 259 | promise.resolve(Collections.emptyList()); 260 | } 261 | ////////////////////////////////////////////////////////////////////// 262 | // End Android specific methods 263 | ////////////////////////////////////////////////////////////////////// 264 | 265 | ////////////////////////////////////////////////////////////////////// 266 | // Start ActivityEventListener methods 267 | ////////////////////////////////////////////////////////////////////// 268 | @Override 269 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { 270 | // FCM functionality does not need this function 271 | } 272 | 273 | @Override 274 | public void onNewIntent(Intent intent) { 275 | WritableMap notificationOpenMap = parseIntentForNotification(intent); 276 | if (notificationOpenMap != null) { 277 | Utils.sendEvent( 278 | getReactApplicationContext(), 279 | "notifications_notification_opened", 280 | notificationOpenMap 281 | ); 282 | } 283 | } 284 | 285 | ////////////////////////////////////////////////////////////////////// 286 | // End ActivityEventListener methods 287 | ////////////////////////////////////////////////////////////////////// 288 | 289 | private WritableMap parseIntentForNotification(Intent intent) { 290 | WritableMap notificationOpenMap = parseIntentForRemoteNotification(intent); 291 | if (notificationOpenMap == null) { 292 | notificationOpenMap = parseIntentForLocalNotification(intent); 293 | } 294 | return notificationOpenMap; 295 | } 296 | 297 | private WritableMap parseIntentForLocalNotification(Intent intent) { 298 | if (intent.getExtras() == null || !intent.hasExtra("notificationId")) { 299 | return null; 300 | } 301 | 302 | WritableMap notificationMap = Arguments.makeNativeMap(intent.getExtras()); 303 | WritableMap notificationOpenMap = Arguments.createMap(); 304 | notificationOpenMap.putString("action", intent.getAction()); 305 | notificationOpenMap.putMap("notification", notificationMap); 306 | 307 | // Check for remote input results 308 | Bundle remoteInput = RemoteInput.getResultsFromIntent(intent); 309 | if (remoteInput != null) { 310 | notificationOpenMap.putMap("results", Arguments.makeNativeMap(remoteInput)); 311 | } 312 | 313 | return notificationOpenMap; 314 | } 315 | 316 | private WritableMap parseIntentForRemoteNotification(Intent intent) { 317 | // Check if FCM data exists 318 | if (intent.getExtras() == null || !intent.hasExtra("google.message_id")) { 319 | return null; 320 | } 321 | 322 | Bundle extras = intent.getExtras(); 323 | 324 | WritableMap notificationMap = Arguments.createMap(); 325 | WritableMap dataMap = Arguments.createMap(); 326 | 327 | for (String key : extras.keySet()) { 328 | if (key.equals("google.message_id")) { 329 | notificationMap.putString("notificationId", extras.getString(key)); 330 | } else if (key.equals("collapse_key") 331 | || key.equals("from") 332 | || key.equals("google.sent_time") 333 | || key.equals("google.ttl") 334 | || key.equals("_fbSourceApplicationHasBeenSet")) { 335 | // ignore known unneeded fields 336 | } else { 337 | dataMap.putString(key, extras.getString(key)); 338 | } 339 | } 340 | notificationMap.putMap("data", dataMap); 341 | 342 | WritableMap notificationOpenMap = Arguments.createMap(); 343 | notificationOpenMap.putString("action", intent.getAction()); 344 | notificationOpenMap.putMap("notification", notificationMap); 345 | 346 | return notificationOpenMap; 347 | } 348 | 349 | private WritableMap parseNotificationBundle(Bundle notification) { 350 | return Arguments.makeNativeMap(notification); 351 | } 352 | 353 | private WritableMap parseRemoteMessage(RemoteMessage message) { 354 | RemoteMessage.Notification notification = message.getNotification(); 355 | 356 | WritableMap notificationMap = Arguments.createMap(); 357 | WritableMap dataMap = Arguments.createMap(); 358 | 359 | // Cross platform notification properties 360 | String body = getNotificationBody(notification); 361 | if (body != null) { 362 | notificationMap.putString("body", body); 363 | } 364 | if (message.getData() != null) { 365 | for (Map.Entry e : message 366 | .getData() 367 | .entrySet()) { 368 | dataMap.putString(e.getKey(), e.getValue()); 369 | } 370 | } 371 | notificationMap.putMap("data", dataMap); 372 | if (message.getMessageId() != null) { 373 | notificationMap.putString("notificationId", message.getMessageId()); 374 | } 375 | if (notification.getSound() != null) { 376 | notificationMap.putString("sound", notification.getSound()); 377 | } 378 | String title = getNotificationTitle(notification); 379 | if (title != null) { 380 | notificationMap.putString("title", title); 381 | } 382 | 383 | // Android specific notification properties 384 | WritableMap androidMap = Arguments.createMap(); 385 | if (notification.getClickAction() != null) { 386 | androidMap.putString("clickAction", notification.getClickAction()); 387 | } 388 | if (notification.getColor() != null) { 389 | androidMap.putString("color", notification.getColor()); 390 | } 391 | if (notification.getIcon() != null) { 392 | WritableMap iconMap = Arguments.createMap(); 393 | iconMap.putString("icon", notification.getIcon()); 394 | androidMap.putMap("smallIcon", iconMap); 395 | } 396 | if (notification.getImageUrl() != null) { 397 | String imageUrl = notification.getImageUrl().toString(); 398 | WritableMap bigPictureMap = Arguments.createMap(); 399 | bigPictureMap.putString("picture", imageUrl); 400 | bigPictureMap.putNull("largeIcon"); 401 | androidMap.putMap("bigPicture", bigPictureMap); 402 | androidMap.putString("largeIcon", imageUrl); 403 | } 404 | if (notification.getTag() != null) { 405 | androidMap.putString("group", notification.getTag()); 406 | androidMap.putString("tag", notification.getTag()); 407 | } 408 | if (notification.getChannelId() != null) { 409 | androidMap.putString("channelId", notification.getChannelId()); 410 | } 411 | notificationMap.putMap("android", androidMap); 412 | 413 | return notificationMap; 414 | } 415 | 416 | private @Nullable 417 | String getNotificationBody(RemoteMessage.Notification notification) { 418 | String body = notification.getBody(); 419 | String bodyLocKey = notification.getBodyLocalizationKey(); 420 | if (bodyLocKey != null) { 421 | String[] bodyLocArgs = notification.getBodyLocalizationArgs(); 422 | Context ctx = getReactApplicationContext(); 423 | int resId = getResId(ctx, bodyLocKey); 424 | return ctx 425 | .getResources() 426 | .getString(resId, (Object[]) bodyLocArgs); 427 | } else { 428 | return body; 429 | } 430 | } 431 | 432 | private @Nullable 433 | String getNotificationTitle(RemoteMessage.Notification notification) { 434 | String title = notification.getTitle(); 435 | String titleLocKey = notification.getTitleLocalizationKey(); 436 | if (titleLocKey != null) { 437 | String[] titleLocArgs = notification.getTitleLocalizationArgs(); 438 | Context ctx = getReactApplicationContext(); 439 | int resId = getResId(ctx, titleLocKey); 440 | return ctx 441 | .getResources() 442 | .getString(resId, (Object[]) titleLocArgs); 443 | } else { 444 | return title; 445 | } 446 | } 447 | 448 | private class RemoteNotificationReceiver extends BroadcastReceiver { 449 | @Override 450 | public void onReceive(Context context, Intent intent) { 451 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 452 | Log.d(TAG, "Received new remote notification"); 453 | 454 | RemoteMessage message = intent.getParcelableExtra("notification"); 455 | WritableMap messageMap = parseRemoteMessage(message); 456 | 457 | Utils.sendEvent( 458 | getReactApplicationContext(), 459 | "notifications_notification_received", 460 | messageMap 461 | ); 462 | } 463 | } 464 | } 465 | 466 | private class ScheduledNotificationReceiver extends BroadcastReceiver { 467 | @Override 468 | public void onReceive(Context context, Intent intent) { 469 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 470 | Log.d(TAG, "Received new scheduled notification"); 471 | 472 | Bundle notification = intent.getBundleExtra("notification"); 473 | WritableMap messageMap = parseNotificationBundle(notification); 474 | 475 | Utils.sendEvent( 476 | getReactApplicationContext(), 477 | "notifications_notification_received", 478 | messageMap 479 | ); 480 | } 481 | } 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseNotificationsPackage.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.NativeModule; 5 | import com.facebook.react.bridge.ReactApplicationContext; 6 | import com.facebook.react.uimanager.UIManagerModule; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class RNFirebaseNotificationsPackage implements ReactPackage { 14 | public RNFirebaseNotificationsPackage() { 15 | } 16 | 17 | /** 18 | * @param reactContext react application context that can be used to create modules 19 | * @return list of native modules to register with the newly created catalyst instance 20 | */ 21 | @Override 22 | public List createNativeModules(ReactApplicationContext reactContext) { 23 | List modules = new ArrayList<>(); 24 | modules.add(new RNFirebaseNotifications(reactContext)); 25 | 26 | return modules; 27 | } 28 | 29 | /** 30 | * @param reactContext 31 | * @return a list of view managers that should be registered with {@link UIManagerModule} 32 | */ 33 | @Override 34 | public List createViewManagers(ReactApplicationContext reactContext) { 35 | return Collections.emptyList(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/RNFirebaseNotificationsRebootReceiver.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.util.Log; 7 | 8 | /* 9 | * This is invoked when the phone restarts to ensure that all notifications are rescheduled 10 | * correctly, as Android removes all scheduled alarms when the phone shuts down. 11 | */ 12 | public class RNFirebaseNotificationsRebootReceiver extends BroadcastReceiver { 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | Log.i("RNFNotifRebootReceiver", "Received reboot event"); 16 | new RNFirebaseNotificationManager(context).rescheduleNotifications(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/com/afrihost/firebase/notifications/Utils.java: -------------------------------------------------------------------------------- 1 | package com.afrihost.firebase.notifications; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.util.Log; 6 | 7 | import com.facebook.react.bridge.Arguments; 8 | import com.facebook.react.bridge.ReactContext; 9 | import com.facebook.react.bridge.ReadableArray; 10 | import com.facebook.react.bridge.ReadableMap; 11 | import com.facebook.react.bridge.WritableArray; 12 | import com.facebook.react.bridge.WritableMap; 13 | import com.facebook.react.common.LifecycleState; 14 | import com.facebook.react.modules.core.DeviceEventManagerModule; 15 | 16 | import org.json.JSONArray; 17 | import org.json.JSONException; 18 | import org.json.JSONObject; 19 | 20 | import java.text.SimpleDateFormat; 21 | import java.util.Calendar; 22 | import java.util.Date; 23 | import java.util.Iterator; 24 | import java.util.List; 25 | import java.util.Locale; 26 | import java.util.Map; 27 | import java.util.TimeZone; 28 | 29 | import javax.annotation.Nullable; 30 | 31 | 32 | @SuppressWarnings("WeakerAccess") 33 | public class Utils { 34 | private static final String TAG = "Utils"; 35 | 36 | public static String timestampToUTC(long timestamp) { 37 | Calendar calendar = Calendar.getInstance(); 38 | Date date = new Date((timestamp + calendar.getTimeZone().getOffset(timestamp)) * 1000); 39 | SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); 40 | format.setTimeZone(TimeZone.getTimeZone("UTC")); 41 | return format.format(date); 42 | } 43 | 44 | /** 45 | * send a JS event 46 | **/ 47 | public static void sendEvent(final ReactContext context, final String eventName, Object body) { 48 | if (context != null) { 49 | context 50 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 51 | .emit(eventName, body); 52 | } else { 53 | Log.d(TAG, "Missing context - cannot send event!"); 54 | } 55 | } 56 | 57 | public static WritableMap jsonObjectToWritableMap(JSONObject jsonObject) throws JSONException { 58 | Iterator iterator = jsonObject.keys(); 59 | WritableMap writableMap = Arguments.createMap(); 60 | 61 | while (iterator.hasNext()) { 62 | String key = iterator.next(); 63 | Object value = jsonObject.get(key); 64 | if (value instanceof Float || value instanceof Double) { 65 | writableMap.putDouble(key, jsonObject.getDouble(key)); 66 | } else if (value instanceof Number) { 67 | writableMap.putInt(key, jsonObject.getInt(key)); 68 | } else if (value instanceof String) { 69 | writableMap.putString(key, jsonObject.getString(key)); 70 | } else if (value instanceof JSONObject) { 71 | writableMap.putMap(key, jsonObjectToWritableMap(jsonObject.getJSONObject(key))); 72 | } else if (value instanceof JSONArray) { 73 | writableMap.putArray(key, jsonArrayToWritableArray(jsonObject.getJSONArray(key))); 74 | } else if (value == JSONObject.NULL) { 75 | writableMap.putNull(key); 76 | } 77 | } 78 | 79 | return writableMap; 80 | } 81 | 82 | public static WritableArray jsonArrayToWritableArray(JSONArray jsonArray) throws JSONException { 83 | WritableArray writableArray = Arguments.createArray(); 84 | 85 | for (int i = 0; i < jsonArray.length(); i++) { 86 | Object value = jsonArray.get(i); 87 | if (value instanceof Float || value instanceof Double) { 88 | writableArray.pushDouble(jsonArray.getDouble(i)); 89 | } else if (value instanceof Number) { 90 | writableArray.pushInt(jsonArray.getInt(i)); 91 | } else if (value instanceof String) { 92 | writableArray.pushString(jsonArray.getString(i)); 93 | } else if (value instanceof JSONObject) { 94 | writableArray.pushMap(jsonObjectToWritableMap(jsonArray.getJSONObject(i))); 95 | } else if (value instanceof JSONArray) { 96 | writableArray.pushArray(jsonArrayToWritableArray(jsonArray.getJSONArray(i))); 97 | } else if (value == JSONObject.NULL) { 98 | writableArray.pushNull(); 99 | } 100 | } 101 | return writableArray; 102 | } 103 | 104 | public static WritableMap mapToWritableMap(Map value) { 105 | WritableMap writableMap = Arguments.createMap(); 106 | 107 | for (Map.Entry entry : value.entrySet()) { 108 | mapPutValue(entry.getKey(), entry.getValue(), writableMap); 109 | } 110 | 111 | return writableMap; 112 | } 113 | 114 | private static WritableArray listToWritableArray(List objects) { 115 | WritableArray writableArray = Arguments.createArray(); 116 | for (Object object : objects) { 117 | arrayPushValue(object, writableArray); 118 | } 119 | return writableArray; 120 | } 121 | 122 | @SuppressWarnings("unchecked") 123 | public static void arrayPushValue(@Nullable Object value, WritableArray array) { 124 | if (value == null || value == JSONObject.NULL) { 125 | array.pushNull(); 126 | return; 127 | } 128 | 129 | String type = value.getClass().getName(); 130 | switch (type) { 131 | case "java.lang.Boolean": 132 | array.pushBoolean((Boolean) value); 133 | break; 134 | case "java.lang.Long": 135 | Long longVal = (Long) value; 136 | array.pushDouble((double) longVal); 137 | break; 138 | case "java.lang.Float": 139 | float floatVal = (float) value; 140 | array.pushDouble((double) floatVal); 141 | break; 142 | case "java.lang.Double": 143 | array.pushDouble((double) value); 144 | break; 145 | case "java.lang.Integer": 146 | array.pushInt((int) value); 147 | break; 148 | case "java.lang.String": 149 | array.pushString((String) value); 150 | break; 151 | case "org.json.JSONObject$1": 152 | try { 153 | array.pushMap(jsonObjectToWritableMap((JSONObject) value)); 154 | } catch (JSONException e) { 155 | array.pushNull(); 156 | } 157 | break; 158 | case "org.json.JSONArray$1": 159 | try { 160 | array.pushArray(jsonArrayToWritableArray((JSONArray) value)); 161 | } catch (JSONException e) { 162 | array.pushNull(); 163 | } 164 | break; 165 | default: 166 | if (List.class.isAssignableFrom(value.getClass())) { 167 | array.pushArray(listToWritableArray((List) value)); 168 | } else if (Map.class.isAssignableFrom(value.getClass())) { 169 | array.pushMap(mapToWritableMap((Map) value)); 170 | } else { 171 | Log.d(TAG, "utils:arrayPushValue:unknownType:" + type); 172 | array.pushNull(); 173 | } 174 | } 175 | } 176 | 177 | @SuppressWarnings("unchecked") 178 | public static void mapPutValue(String key, @Nullable Object value, WritableMap map) { 179 | if (value == null || value == JSONObject.NULL) { 180 | map.putNull(key); 181 | return; 182 | } 183 | 184 | String type = value.getClass().getName(); 185 | switch (type) { 186 | case "java.lang.Boolean": 187 | map.putBoolean(key, (Boolean) value); 188 | break; 189 | case "java.lang.Long": 190 | Long longVal = (Long) value; 191 | map.putDouble(key, (double) longVal); 192 | break; 193 | case "java.lang.Float": 194 | float floatVal = (float) value; 195 | map.putDouble(key, (double) floatVal); 196 | break; 197 | case "java.lang.Double": 198 | map.putDouble(key, (double) value); 199 | break; 200 | case "java.lang.Integer": 201 | map.putInt(key, (int) value); 202 | break; 203 | case "java.lang.String": 204 | map.putString(key, (String) value); 205 | break; 206 | case "org.json.JSONObject$1": 207 | try { 208 | map.putMap(key, jsonObjectToWritableMap((JSONObject) value)); 209 | } catch (JSONException e) { 210 | map.putNull(key); 211 | } 212 | break; 213 | case "org.json.JSONArray$1": 214 | try { 215 | map.putArray(key, jsonArrayToWritableArray((JSONArray) value)); 216 | } catch (JSONException e) { 217 | map.putNull(key); 218 | } 219 | break; 220 | default: 221 | if (List.class.isAssignableFrom(value.getClass())) { 222 | map.putArray(key, listToWritableArray((List) value)); 223 | } else if (Map.class.isAssignableFrom(value.getClass())) { 224 | map.putMap(key, mapToWritableMap((Map) value)); 225 | } else { 226 | Log.d(TAG, "utils:mapPutValue:unknownType:" + type); 227 | map.putNull(key); 228 | } 229 | } 230 | } 231 | 232 | /** 233 | * Convert a ReadableMap to a WritableMap for the purposes of re-sending back to JS 234 | * TODO This is now a legacy util - internally uses RN functionality 235 | * 236 | * @param map ReadableMap 237 | * @return WritableMap 238 | */ 239 | public static WritableMap readableMapToWritableMap(ReadableMap map) { 240 | WritableMap writableMap = Arguments.createMap(); 241 | // https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/WritableNativeMap.java#L54 242 | writableMap.merge(map); 243 | return writableMap; 244 | } 245 | 246 | /** 247 | * Convert a ReadableMap into a native Java Map 248 | * TODO This is now a legacy util - internally uses RN functionality 249 | * 250 | * @param readableMap ReadableMap 251 | * @return Map 252 | */ 253 | public static Map recursivelyDeconstructReadableMap(ReadableMap readableMap) { 254 | // https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeMap.java#L216 255 | return readableMap.toHashMap(); 256 | } 257 | 258 | /** 259 | * Convert a ReadableArray into a native Java Map 260 | * TODO This is now a legacy util - internally uses RN functionality 261 | * 262 | * @param readableArray ReadableArray 263 | * @return List 264 | */ 265 | public static List recursivelyDeconstructReadableArray(ReadableArray readableArray) { 266 | // https://github.com/facebook/react-native/blob/master/ReactAndroid/src/main/java/com/facebook/react/bridge/ReadableNativeArray.java#L175 267 | return readableArray.toArrayList(); 268 | } 269 | 270 | /** 271 | * We need to check if app is in foreground otherwise the app will crash. 272 | * http://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not 273 | * 274 | * @param context Context 275 | * @return boolean 276 | */ 277 | public static boolean isAppInForeground(Context context) { 278 | ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 279 | if (activityManager == null) return false; 280 | 281 | List appProcesses = activityManager.getRunningAppProcesses(); 282 | if (appProcesses == null) return false; 283 | 284 | final String packageName = context.getPackageName(); 285 | for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { 286 | if ( 287 | appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND 288 | && appProcess.processName.equals(packageName) 289 | ) { 290 | ReactContext reactContext; 291 | 292 | try { 293 | reactContext = (ReactContext) context; 294 | } catch (ClassCastException exception) { 295 | // Not react context so default to true 296 | return true; 297 | } 298 | 299 | return reactContext.getLifecycleState() == LifecycleState.RESUMED; 300 | } 301 | } 302 | 303 | return false; 304 | } 305 | 306 | public static int getResId(Context ctx, String resName) { 307 | int resourceId = ctx 308 | .getResources() 309 | .getIdentifier(resName, "string", ctx.getPackageName()); 310 | if (resourceId == 0) { 311 | Log.e(TAG, "resource " + resName + " could not be found"); 312 | } 313 | return resourceId; 314 | } 315 | } -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Firebase Cloud Messaging 4 | Click the SUBSCRIBE TO WEATHER button below to subscribe to the 5 | weather topic. Messages sent to the weather topic will be received. The LOG TOKEN button logs the 6 | InstanceID token to logcat. 7 | Subscribe To Weather 8 | Log Token 9 | 10 | Subscribed to weather topic 11 | InstanceID Token: %s 12 | 13 | fcm_default_channel 14 | 18 | 19 | Weather 20 | 21 | Failed to subscribe to weather topic 22 | FCM Message 23 | -------------------------------------------------------------------------------- /ios/FirebasePushNotifications.h: -------------------------------------------------------------------------------- 1 | #ifndef FirebasePushNotifications_h 2 | #define FirebasePushNotifications_h 3 | #import 4 | 5 | #if __has_include() 6 | #import 7 | #import 8 | 9 | @interface FirebasePushNotifications : RCTEventEmitter 10 | 11 | + (void)configure; 12 | + (_Nonnull instancetype)instance; 13 | 14 | #if !TARGET_OS_TV 15 | - (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; 16 | - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(void (^_Nonnull)(UIBackgroundFetchResult))completionHandler; 17 | #endif 18 | 19 | @end 20 | 21 | #else 22 | @interface FirebasePushNotifications : NSObject 23 | @end 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /ios/FirebasePushNotifications.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | package = JSON.parse(File.read('../package.json')) 3 | 4 | Pod::Spec.new do |s| 5 | s.name = "FirebasePushNotifications" 6 | s.version = package["version"] 7 | s.description = package["description"] 8 | s.summary = <<-DESC 9 | Firebase push notications supporting iOS & Android. 10 | DESC 11 | s.homepage = "https://www.afrihost.com" 12 | s.license = package['license'] 13 | s.authors = "N/A" 14 | s.source = { :git => "https://github.com/afrihost/react-native-firebase-push-notifications.git", :tag => "v#{s.version}" } 15 | s.social_media_url = 'http://twitter.com/afrihost' 16 | s.platform = :ios, "9.0" 17 | s.source_files = './**/*.{h,m}' 18 | s.dependency 'React' 19 | s.dependency 'Firebase/Core' 20 | s.subspec 'Crashlytics' do |cs| 21 | cs.dependency 'Fabric' 22 | cs.dependency 'Crashlytics' 23 | end 24 | # allow this package to be used with use_frameworks! 25 | s.static_framework = true 26 | end 27 | -------------------------------------------------------------------------------- /ios/FirebasePushNotifications.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 921BC0F623DB0BD8000FA892 /* RNFirebaseUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 921BC0F123DB0BD7000FA892 /* RNFirebaseUtil.m */; }; 11 | 921BC0F723DB0BD8000FA892 /* RNFirebase.m in Sources */ = {isa = PBXBuildFile; fileRef = 921BC0F423DB0BD8000FA892 /* RNFirebase.m */; }; 12 | 9241BB4A23DF064E00AE72EA /* RNFirebaseMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 9241BB4823DF064E00AE72EA /* RNFirebaseMessaging.m */; }; 13 | 9241BB4D23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.m in Sources */ = {isa = PBXBuildFile; fileRef = 9241BB4B23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.m */; }; 14 | B3E7B58A1CC2AC0600A0062D /* FirebasePushNotifications.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* FirebasePushNotifications.m */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXCopyFilesBuildPhase section */ 18 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 19 | isa = PBXCopyFilesBuildPhase; 20 | buildActionMask = 2147483647; 21 | dstPath = "include/$(PRODUCT_NAME)"; 22 | dstSubfolderSpec = 16; 23 | files = ( 24 | ); 25 | runOnlyForDeploymentPostprocessing = 0; 26 | }; 27 | /* End PBXCopyFilesBuildPhase section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 134814201AA4EA6300B7C361 /* libFirebasePushNotifications.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFirebasePushNotifications.a; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 921BC0F123DB0BD7000FA892 /* RNFirebaseUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseUtil.m; sourceTree = ""; }; 32 | 921BC0F223DB0BD7000FA892 /* RNFirebaseUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseUtil.h; sourceTree = ""; }; 33 | 921BC0F323DB0BD8000FA892 /* RNFirebase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebase.h; sourceTree = ""; }; 34 | 921BC0F423DB0BD8000FA892 /* RNFirebase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFirebase.m; sourceTree = ""; }; 35 | 921BC0F523DB0BD8000FA892 /* RNFirebaseEvents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFirebaseEvents.h; sourceTree = ""; }; 36 | 9241BB4823DF064E00AE72EA /* RNFirebaseMessaging.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNFirebaseMessaging.m; sourceTree = ""; }; 37 | 9241BB4923DF064E00AE72EA /* RNFirebaseMessaging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNFirebaseMessaging.h; sourceTree = ""; }; 38 | 9241BB4B23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+UIBackgroundFetchResult.m"; sourceTree = ""; }; 39 | 9241BB4C23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+UIBackgroundFetchResult.h"; sourceTree = ""; }; 40 | B3E7B5881CC2AC0600A0062D /* FirebasePushNotifications.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FirebasePushNotifications.h; sourceTree = ""; }; 41 | B3E7B5891CC2AC0600A0062D /* FirebasePushNotifications.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FirebasePushNotifications.m; sourceTree = ""; }; 42 | /* End PBXFileReference section */ 43 | 44 | /* Begin PBXFrameworksBuildPhase section */ 45 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 46 | isa = PBXFrameworksBuildPhase; 47 | buildActionMask = 2147483647; 48 | files = ( 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 134814211AA4EA7D00B7C361 /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 134814201AA4EA6300B7C361 /* libFirebasePushNotifications.a */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 58B511D21A9E6C8500147676 = { 64 | isa = PBXGroup; 65 | children = ( 66 | B3E7B5881CC2AC0600A0062D /* FirebasePushNotifications.h */, 67 | 9241BB4C23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.h */, 68 | 9241BB4B23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.m */, 69 | B3E7B5891CC2AC0600A0062D /* FirebasePushNotifications.m */, 70 | 9241BB4923DF064E00AE72EA /* RNFirebaseMessaging.h */, 71 | 9241BB4823DF064E00AE72EA /* RNFirebaseMessaging.m */, 72 | 921BC0F323DB0BD8000FA892 /* RNFirebase.h */, 73 | 921BC0F423DB0BD8000FA892 /* RNFirebase.m */, 74 | 921BC0F523DB0BD8000FA892 /* RNFirebaseEvents.h */, 75 | 921BC0F223DB0BD7000FA892 /* RNFirebaseUtil.h */, 76 | 921BC0F123DB0BD7000FA892 /* RNFirebaseUtil.m */, 77 | 134814211AA4EA7D00B7C361 /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | /* End PBXGroup section */ 82 | 83 | /* Begin PBXNativeTarget section */ 84 | 58B511DA1A9E6C8500147676 /* FirebasePushNotifications */ = { 85 | isa = PBXNativeTarget; 86 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "FirebasePushNotifications" */; 87 | buildPhases = ( 88 | 58B511D71A9E6C8500147676 /* Sources */, 89 | 58B511D81A9E6C8500147676 /* Frameworks */, 90 | 58B511D91A9E6C8500147676 /* CopyFiles */, 91 | ); 92 | buildRules = ( 93 | ); 94 | dependencies = ( 95 | ); 96 | name = FirebasePushNotifications; 97 | productName = RCTDataManager; 98 | productReference = 134814201AA4EA6300B7C361 /* libFirebasePushNotifications.a */; 99 | productType = "com.apple.product-type.library.static"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | 58B511D31A9E6C8500147676 /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastUpgradeCheck = 0920; 108 | ORGANIZATIONNAME = Facebook; 109 | TargetAttributes = { 110 | 58B511DA1A9E6C8500147676 = { 111 | CreatedOnToolsVersion = 6.1.1; 112 | }; 113 | }; 114 | }; 115 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "FirebasePushNotifications" */; 116 | compatibilityVersion = "Xcode 3.2"; 117 | developmentRegion = English; 118 | hasScannedForEncodings = 0; 119 | knownRegions = ( 120 | English, 121 | en, 122 | ); 123 | mainGroup = 58B511D21A9E6C8500147676; 124 | productRefGroup = 58B511D21A9E6C8500147676; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | 58B511DA1A9E6C8500147676 /* FirebasePushNotifications */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | 58B511D71A9E6C8500147676 /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 921BC0F623DB0BD8000FA892 /* RNFirebaseUtil.m in Sources */, 139 | 9241BB4D23DF076D00AE72EA /* RCTConvert+UIBackgroundFetchResult.m in Sources */, 140 | 921BC0F723DB0BD8000FA892 /* RNFirebase.m in Sources */, 141 | B3E7B58A1CC2AC0600A0062D /* FirebasePushNotifications.m in Sources */, 142 | 9241BB4A23DF064E00AE72EA /* RNFirebaseMessaging.m in Sources */, 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXSourcesBuildPhase section */ 147 | 148 | /* Begin XCBuildConfiguration section */ 149 | 58B511ED1A9E6C8500147676 /* Debug */ = { 150 | isa = XCBuildConfiguration; 151 | buildSettings = { 152 | ALWAYS_SEARCH_USER_PATHS = NO; 153 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 154 | CLANG_CXX_LIBRARY = "libc++"; 155 | CLANG_ENABLE_MODULES = YES; 156 | CLANG_ENABLE_OBJC_ARC = YES; 157 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 158 | CLANG_WARN_BOOL_CONVERSION = YES; 159 | CLANG_WARN_COMMA = YES; 160 | CLANG_WARN_CONSTANT_CONVERSION = YES; 161 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 162 | CLANG_WARN_EMPTY_BODY = YES; 163 | CLANG_WARN_ENUM_CONVERSION = YES; 164 | CLANG_WARN_INFINITE_RECURSION = YES; 165 | CLANG_WARN_INT_CONVERSION = YES; 166 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 167 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 168 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 169 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 170 | CLANG_WARN_STRICT_PROTOTYPES = YES; 171 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 172 | CLANG_WARN_UNREACHABLE_CODE = YES; 173 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 174 | COPY_PHASE_STRIP = NO; 175 | ENABLE_STRICT_OBJC_MSGSEND = YES; 176 | ENABLE_TESTABILITY = YES; 177 | GCC_C_LANGUAGE_STANDARD = gnu99; 178 | GCC_DYNAMIC_NO_PIC = NO; 179 | GCC_NO_COMMON_BLOCKS = YES; 180 | GCC_OPTIMIZATION_LEVEL = 0; 181 | GCC_PREPROCESSOR_DEFINITIONS = ( 182 | "DEBUG=1", 183 | "$(inherited)", 184 | ); 185 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 186 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 187 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 188 | GCC_WARN_UNDECLARED_SELECTOR = YES; 189 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 190 | GCC_WARN_UNUSED_FUNCTION = YES; 191 | GCC_WARN_UNUSED_VARIABLE = YES; 192 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 193 | MTL_ENABLE_DEBUG_INFO = YES; 194 | ONLY_ACTIVE_ARCH = YES; 195 | SDKROOT = iphoneos; 196 | }; 197 | name = Debug; 198 | }; 199 | 58B511EE1A9E6C8500147676 /* Release */ = { 200 | isa = XCBuildConfiguration; 201 | buildSettings = { 202 | ALWAYS_SEARCH_USER_PATHS = NO; 203 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 204 | CLANG_CXX_LIBRARY = "libc++"; 205 | CLANG_ENABLE_MODULES = YES; 206 | CLANG_ENABLE_OBJC_ARC = YES; 207 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 208 | CLANG_WARN_BOOL_CONVERSION = YES; 209 | CLANG_WARN_COMMA = YES; 210 | CLANG_WARN_CONSTANT_CONVERSION = YES; 211 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 212 | CLANG_WARN_EMPTY_BODY = YES; 213 | CLANG_WARN_ENUM_CONVERSION = YES; 214 | CLANG_WARN_INFINITE_RECURSION = YES; 215 | CLANG_WARN_INT_CONVERSION = YES; 216 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 217 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 218 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 219 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 220 | CLANG_WARN_STRICT_PROTOTYPES = YES; 221 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 222 | CLANG_WARN_UNREACHABLE_CODE = YES; 223 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 224 | COPY_PHASE_STRIP = YES; 225 | ENABLE_NS_ASSERTIONS = NO; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 236 | MTL_ENABLE_DEBUG_INFO = NO; 237 | SDKROOT = iphoneos; 238 | VALIDATE_PRODUCT = YES; 239 | }; 240 | name = Release; 241 | }; 242 | 58B511F01A9E6C8500147676 /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | HEADER_SEARCH_PATHS = ( 246 | "$(inherited)", 247 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 248 | "$(SRCROOT)/../../../React/**", 249 | "$(SRCROOT)/../../react-native/React/**", 250 | ); 251 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 252 | OTHER_LDFLAGS = "-ObjC"; 253 | PRODUCT_NAME = FirebasePushNotifications; 254 | SKIP_INSTALL = YES; 255 | }; 256 | name = Debug; 257 | }; 258 | 58B511F11A9E6C8500147676 /* Release */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | HEADER_SEARCH_PATHS = ( 262 | "$(inherited)", 263 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 264 | "$(SRCROOT)/../../../React/**", 265 | "$(SRCROOT)/../../react-native/React/**", 266 | ); 267 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 268 | OTHER_LDFLAGS = "-ObjC"; 269 | PRODUCT_NAME = FirebasePushNotifications; 270 | SKIP_INSTALL = YES; 271 | }; 272 | name = Release; 273 | }; 274 | /* End XCBuildConfiguration section */ 275 | 276 | /* Begin XCConfigurationList section */ 277 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "FirebasePushNotifications" */ = { 278 | isa = XCConfigurationList; 279 | buildConfigurations = ( 280 | 58B511ED1A9E6C8500147676 /* Debug */, 281 | 58B511EE1A9E6C8500147676 /* Release */, 282 | ); 283 | defaultConfigurationIsVisible = 0; 284 | defaultConfigurationName = Release; 285 | }; 286 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "FirebasePushNotifications" */ = { 287 | isa = XCConfigurationList; 288 | buildConfigurations = ( 289 | 58B511F01A9E6C8500147676 /* Debug */, 290 | 58B511F11A9E6C8500147676 /* Release */, 291 | ); 292 | defaultConfigurationIsVisible = 0; 293 | defaultConfigurationName = Release; 294 | }; 295 | /* End XCConfigurationList section */ 296 | }; 297 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 298 | } 299 | -------------------------------------------------------------------------------- /ios/FirebasePushNotifications.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/FirebasePushNotifications.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RCTConvert+UIBackgroundFetchResult.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RCTConvert (UIBackgroundFetchResult) 4 | 5 | @end 6 | -------------------------------------------------------------------------------- /ios/RCTConvert+UIBackgroundFetchResult.m: -------------------------------------------------------------------------------- 1 | #import "RCTConvert+UIBackgroundFetchResult.h" 2 | 3 | @implementation RCTConvert (UIBackgroundFetchResult) 4 | 5 | RCT_ENUM_CONVERTER( 6 | UIBackgroundFetchResult, 7 | (@{ 8 | @"backgroundFetchResultNoData" : @(UIBackgroundFetchResultNoData), 9 | @"backgroundFetchResultNewData" : @(UIBackgroundFetchResultNewData), 10 | @"backgroundFetchResultFailed" : @(UIBackgroundFetchResultFailed)} 11 | ), 12 | UIBackgroundFetchResultNoData, 13 | integerValue 14 | ) 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/RNFirebase.h: -------------------------------------------------------------------------------- 1 | #ifndef RNFirebase_h 2 | #define RNFirebase_h 3 | #import 4 | 5 | #import 6 | #import 7 | 8 | @interface RNFirebase : RCTEventEmitter { 9 | } 10 | 11 | @end 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ios/RNFirebase.m: -------------------------------------------------------------------------------- 1 | #import "RNFirebase.h" 2 | #import "RNFirebaseUtil.h" 3 | #import 4 | #import 5 | 6 | 7 | #if __has_include() 8 | #import 9 | #define REGISTER_LIB 10 | #endif 11 | 12 | @implementation RNFirebase 13 | RCT_EXPORT_MODULE(RNFirebase); 14 | 15 | - (id)init { 16 | self = [super init]; 17 | if (self != nil) { 18 | DLog(@"Setting up RNFirebase instance"); 19 | #ifdef REGISTER_LIB 20 | [FIRApp registerLibrary:@"react-native-firebase" withVersion:@"5.6.0"]; 21 | #endif 22 | } 23 | return self; 24 | } 25 | 26 | - (NSArray *)supportedEvents { 27 | return @[]; 28 | } 29 | 30 | /** 31 | * Initialize a new firebase app instance or ignore if currently exists. 32 | * @return 33 | */ 34 | RCT_EXPORT_METHOD(initializeApp: 35 | (NSString *) appDisplayName 36 | options: 37 | (NSDictionary *) options 38 | callback: 39 | (RCTResponseSenderBlock) callback) { 40 | 41 | RCTUnsafeExecuteOnMainQueueSync(^{ 42 | FIRApp *existingApp = [RNFirebaseUtil getApp:appDisplayName]; 43 | 44 | if (!existingApp) { 45 | FIROptions *firOptions = [[FIROptions alloc] initWithGoogleAppID:[options valueForKey:@"appId"] GCMSenderID:[options valueForKey:@"messagingSenderId"]]; 46 | 47 | firOptions.APIKey = [options valueForKey:@"apiKey"]; 48 | firOptions.projectID = [options valueForKey:@"projectId"]; 49 | firOptions.clientID = [options valueForKey:@"clientId"]; 50 | firOptions.trackingID = [options valueForKey:@"trackingId"]; 51 | firOptions.databaseURL = [options valueForKey:@"databaseURL"]; 52 | firOptions.storageBucket = [options valueForKey:@"storageBucket"]; 53 | firOptions.androidClientID = [options valueForKey:@"androidClientId"]; 54 | firOptions.deepLinkURLScheme = [options valueForKey:@"deepLinkURLScheme"]; 55 | firOptions.bundleID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; 56 | 57 | NSString *appName = [RNFirebaseUtil getAppName:appDisplayName]; 58 | [FIRApp configureWithName:appName options:firOptions]; 59 | } 60 | 61 | callback(@[[NSNull null], @{@"result": @"success"}]); 62 | }); 63 | } 64 | 65 | /** 66 | * Delete a firebase app 67 | * @return 68 | */ 69 | RCT_EXPORT_METHOD(deleteApp: 70 | (NSString *) appDisplayName 71 | resolver: 72 | (RCTPromiseResolveBlock) resolve 73 | rejecter: 74 | (RCTPromiseRejectBlock) reject) { 75 | 76 | FIRApp *existingApp = [RNFirebaseUtil getApp:appDisplayName]; 77 | 78 | if (!existingApp) { 79 | return resolve([NSNull null]); 80 | } 81 | 82 | [existingApp deleteApp:^(BOOL success) { 83 | if (success) { 84 | resolve([NSNull null]); 85 | } else { 86 | reject(@"app/delete-app-failed", @"Failed to delete the specified app.", nil); 87 | } 88 | }]; 89 | 90 | } 91 | 92 | /** 93 | * React native constant exports - exports native firebase apps mainly 94 | * @return NSDictionary 95 | */ 96 | - (NSDictionary *)constantsToExport { 97 | NSMutableDictionary *constants = [NSMutableDictionary new]; 98 | NSDictionary *firApps = [FIRApp allApps]; 99 | NSMutableArray *appsArray = [NSMutableArray new]; 100 | 101 | for (id key in firApps) { 102 | NSMutableDictionary *appOptions = [NSMutableDictionary new]; 103 | FIRApp *firApp = firApps[key]; 104 | FIROptions *firOptions = [firApp options]; 105 | appOptions[@"name"] = [RNFirebaseUtil getAppDisplayName:firApp.name]; 106 | appOptions[@"apiKey"] = firOptions.APIKey; 107 | appOptions[@"appId"] = firOptions.googleAppID; 108 | appOptions[@"databaseURL"] = firOptions.databaseURL; 109 | appOptions[@"messagingSenderId"] = firOptions.GCMSenderID; 110 | appOptions[@"projectId"] = firOptions.projectID; 111 | appOptions[@"storageBucket"] = firOptions.storageBucket; 112 | 113 | // missing from android sdk / ios only: 114 | appOptions[@"clientId"] = firOptions.clientID; 115 | appOptions[@"trackingId"] = firOptions.trackingID; 116 | appOptions[@"androidClientID"] = firOptions.androidClientID; 117 | appOptions[@"deepLinkUrlScheme"] = firOptions.deepLinkURLScheme; 118 | 119 | [appsArray addObject:appOptions]; 120 | } 121 | 122 | constants[@"apps"] = appsArray; 123 | return constants; 124 | } 125 | 126 | + (BOOL)requiresMainQueueSetup 127 | { 128 | return YES; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /ios/RNFirebaseEvents.h: -------------------------------------------------------------------------------- 1 | #ifndef RNFirebaseEvents_h 2 | #define RNFirebaseEvents_h 3 | 4 | #import 5 | 6 | static NSString *const AUTH_STATE_CHANGED_EVENT = @"auth_state_changed"; 7 | static NSString *const AUTH_ID_TOKEN_CHANGED_EVENT = @"auth_id_token_changed"; 8 | static NSString *const PHONE_AUTH_STATE_CHANGED_EVENT = @"phone_auth_state_changed"; 9 | 10 | // Database 11 | static NSString *const DATABASE_SYNC_EVENT = @"database_sync_event"; 12 | static NSString *const DATABASE_TRANSACTION_EVENT = @"database_transaction_event"; 13 | 14 | static NSString *const DATABASE_VALUE_EVENT = @"value"; 15 | static NSString *const DATABASE_CHILD_ADDED_EVENT = @"child_added"; 16 | static NSString *const DATABASE_CHILD_MODIFIED_EVENT = @"child_changed"; 17 | static NSString *const DATABASE_CHILD_REMOVED_EVENT = @"child_removed"; 18 | static NSString *const DATABASE_CHILD_MOVED_EVENT = @"child_moved"; 19 | 20 | // Firestore 21 | static NSString *const FIRESTORE_TRANSACTION_EVENT = @"firestore_transaction_event"; 22 | static NSString *const FIRESTORE_COLLECTION_SYNC_EVENT = @"firestore_collection_sync_event"; 23 | static NSString *const FIRESTORE_DOCUMENT_SYNC_EVENT = @"firestore_document_sync_event"; 24 | 25 | // Storage 26 | static NSString *const STORAGE_EVENT = @"storage_event"; 27 | static NSString *const STORAGE_ERROR = @"storage_error"; 28 | 29 | static NSString *const STORAGE_STATE_CHANGED = @"state_changed"; 30 | static NSString *const STORAGE_UPLOAD_SUCCESS = @"upload_success"; 31 | static NSString *const STORAGE_UPLOAD_FAILURE = @"upload_failure"; 32 | static NSString *const STORAGE_DOWNLOAD_SUCCESS = @"download_success"; 33 | static NSString *const STORAGE_DOWNLOAD_FAILURE = @"download_failure"; 34 | 35 | // Messaging 36 | static NSString *const MESSAGING_MESSAGE_RECEIVED = @"messaging_message_received"; 37 | static NSString *const MESSAGING_TOKEN_REFRESHED = @"messaging_token_refreshed"; 38 | 39 | // Notifications 40 | static NSString *const NOTIFICATIONS_NOTIFICATION_DISPLAYED = @"notifications_notification_displayed"; 41 | static NSString *const NOTIFICATIONS_NOTIFICATION_OPENED = @"notifications_notification_opened"; 42 | static NSString *const NOTIFICATIONS_NOTIFICATION_RECEIVED = @"notifications_notification_received"; 43 | 44 | // AdMob 45 | static NSString *const ADMOB_INTERSTITIAL_EVENT = @"interstitial_event"; 46 | static NSString *const ADMOB_REWARDED_VIDEO_EVENT = @"rewarded_video_event"; 47 | 48 | // Links 49 | static NSString *const LINKS_LINK_RECEIVED = @"links_link_received"; 50 | 51 | // Invites 52 | static NSString *const INVITES_INVITATION_RECEIVED = @"invites_invitation_received"; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /ios/RNFirebaseMessaging.h: -------------------------------------------------------------------------------- 1 | #ifndef RNFirebaseMessaging_h 2 | #define RNFirebaseMessaging_h 3 | #import 4 | 5 | #if __has_include() 6 | #import 7 | #import 8 | #import 9 | 10 | @interface RNFirebaseMessaging : RCTEventEmitter 11 | 12 | + (_Nonnull instancetype)instance; 13 | 14 | @property _Nullable RCTPromiseRejectBlock permissionRejecter; 15 | @property _Nullable RCTPromiseResolveBlock permissionResolver; 16 | 17 | #if !TARGET_OS_TV 18 | - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo; 19 | - (void)didRegisterUserNotificationSettings:(nonnull UIUserNotificationSettings *)notificationSettings; 20 | #endif 21 | 22 | @end 23 | 24 | #else 25 | @interface RNFirebaseMessaging : NSObject 26 | @end 27 | #endif 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /ios/RNFirebaseMessaging.m: -------------------------------------------------------------------------------- 1 | #import "RNFirebaseMessaging.h" 2 | 3 | #if __has_include() 4 | @import UserNotifications; 5 | #import "RNFirebaseEvents.h" 6 | #import "RNFirebaseUtil.h" 7 | #import 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 14 | @import UserNotifications; 15 | #endif 16 | 17 | @implementation RNFirebaseMessaging 18 | 19 | static RNFirebaseMessaging *theRNFirebaseMessaging = nil; 20 | static bool jsReady = FALSE; 21 | static NSString* initialToken = nil; 22 | static NSMutableArray* pendingMessages = nil; 23 | 24 | + (nonnull instancetype)instance { 25 | return theRNFirebaseMessaging; 26 | } 27 | 28 | RCT_EXPORT_MODULE() 29 | 30 | - (id)init { 31 | self = [super init]; 32 | if (self != nil) { 33 | DLog(@"Setting up RNFirebaseMessaging instance"); 34 | [self configure]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)configure { 40 | // Set as delegate for FIRMessaging 41 | [FIRMessaging messaging].delegate = self; 42 | 43 | // Establish Firebase managed data channel 44 | // [FIRMessaging messaging].shouldEstablishDirectChannel = YES; 45 | 46 | // Set static instance for use from AppDelegate 47 | theRNFirebaseMessaging = self; 48 | } 49 | 50 | // ******************************************************* 51 | // ** Start AppDelegate methods 52 | // ** iOS 8/9 Only 53 | // ******************************************************* 54 | 55 | // Listen for permission response 56 | - (void) didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { 57 | if (notificationSettings.types == UIUserNotificationTypeNone) { 58 | if (_permissionRejecter) { 59 | _permissionRejecter(@"messaging/permission_error", @"Failed to grant permission", nil); 60 | } 61 | } else if (_permissionResolver) { 62 | _permissionResolver(nil); 63 | } 64 | _permissionRejecter = nil; 65 | _permissionResolver = nil; 66 | } 67 | 68 | // Listen for FCM data messages that arrive as a remote notification 69 | - (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo { 70 | NSDictionary *message = [self parseUserInfo:userInfo]; 71 | [self sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; 72 | } 73 | 74 | // ******************************************************* 75 | // ** Finish AppDelegate methods 76 | // ******************************************************* 77 | 78 | 79 | // ******************************************************* 80 | // ** Start FIRMessagingDelegate methods 81 | // ** iOS 8+ 82 | // ******************************************************* 83 | 84 | // Listen for FCM tokens 85 | - (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { 86 | DLog(@"Received new FCM token: %@", fcmToken); 87 | [self sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:fcmToken]; 88 | } 89 | 90 | // ******************************************************* 91 | // ** Finish FIRMessagingDelegate methods 92 | // ******************************************************* 93 | 94 | // ** Start React Module methods ** 95 | RCT_EXPORT_METHOD(getToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 96 | if (initialToken) { 97 | resolve(initialToken); 98 | initialToken = nil; 99 | } else if ([[FIRMessaging messaging] FCMToken]) { 100 | resolve([[FIRMessaging messaging] FCMToken]); 101 | } else { 102 | NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID; 103 | [[FIRMessaging messaging] retrieveFCMTokenForSenderID:senderId completion:^(NSString * _Nullable FCMToken, NSError * _Nullable error) { 104 | if (error) { 105 | reject(@"messaging/fcm-token-error", @"Failed to retrieve FCM token.", error); 106 | } else if (FCMToken) { 107 | resolve(FCMToken); 108 | } else { 109 | resolve([NSNull null]); 110 | } 111 | }]; 112 | } 113 | } 114 | 115 | RCT_EXPORT_METHOD(deleteToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 116 | NSString * senderId = [[FIRApp defaultApp] options].GCMSenderID; 117 | [[FIRMessaging messaging] deleteFCMTokenForSenderID:senderId completion:^(NSError * _Nullable error) { 118 | if (error) { 119 | reject(@"messaging/fcm-token-error", @"Failed to delete FCM token.", error); 120 | } else { 121 | resolve([NSNull null]); 122 | } 123 | }]; 124 | } 125 | 126 | 127 | RCT_EXPORT_METHOD(getAPNSToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 128 | NSData *apnsToken = [FIRMessaging messaging].APNSToken; 129 | if (apnsToken) { 130 | const char *data = [apnsToken bytes]; 131 | NSMutableString *token = [NSMutableString string]; 132 | for (NSInteger i = 0; i < apnsToken.length; i++) { 133 | [token appendFormat:@"%02.2hhX", data[i]]; 134 | } 135 | resolve([token copy]); 136 | } else { 137 | resolve([NSNull null]); 138 | } 139 | } 140 | 141 | RCT_EXPORT_METHOD(requestPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 142 | if (RCTRunningInAppExtension()) { 143 | reject(@"messaging/request-permission-unavailable", @"requestPermission is not supported in App Extensions", nil); 144 | return; 145 | } 146 | 147 | if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { 148 | UIUserNotificationType types = (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); 149 | dispatch_async(dispatch_get_main_queue(), ^{ 150 | [RCTSharedApplication() registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:types categories:nil]]; 151 | // We set the promise for usage by the AppDelegate callback which listens 152 | // for the result of the permission request 153 | self.permissionRejecter = reject; 154 | self.permissionResolver = resolve; 155 | }); 156 | } else { 157 | if (@available(iOS 10.0, *)) { 158 | // For iOS 10 display notification (sent via APNS) 159 | UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; 160 | [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) { 161 | if (granted) { 162 | resolve(nil); 163 | } else { 164 | reject(@"messaging/permission_error", @"Failed to grant permission", error); 165 | } 166 | }]; 167 | } 168 | } 169 | 170 | dispatch_async(dispatch_get_main_queue(), ^{ 171 | [RCTSharedApplication() registerForRemoteNotifications]; 172 | }); 173 | } 174 | 175 | RCT_EXPORT_METHOD(registerForRemoteNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 176 | [RCTSharedApplication() registerForRemoteNotifications]; 177 | resolve(nil); 178 | } 179 | 180 | // Non Web SDK methods 181 | RCT_EXPORT_METHOD(hasPermission:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 182 | if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { 183 | dispatch_async(dispatch_get_main_queue(), ^{ 184 | BOOL hasPermission = [RCTConvert BOOL:@([RCTSharedApplication() currentUserNotificationSettings].types != UIUserNotificationTypeNone)]; 185 | resolve(@(hasPermission)); 186 | }); 187 | } else { 188 | if (@available(iOS 10.0, *)) { 189 | [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) { 190 | BOOL hasPermission = [RCTConvert BOOL:@(settings.alertSetting == UNNotificationSettingEnabled)]; 191 | resolve(@(hasPermission)); 192 | }]; 193 | } 194 | } 195 | } 196 | 197 | 198 | RCT_EXPORT_METHOD(subscribeToTopic:(NSString*) topic 199 | resolve:(RCTPromiseResolveBlock) resolve 200 | reject:(RCTPromiseRejectBlock) reject) { 201 | [[FIRMessaging messaging] subscribeToTopic:topic]; 202 | resolve(nil); 203 | } 204 | 205 | RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic 206 | resolve:(RCTPromiseResolveBlock) resolve 207 | reject:(RCTPromiseRejectBlock) reject) { 208 | [[FIRMessaging messaging] unsubscribeFromTopic:topic]; 209 | resolve(nil); 210 | } 211 | 212 | RCT_EXPORT_METHOD(jsInitialised:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { 213 | jsReady = TRUE; 214 | resolve(nil); 215 | if (initialToken) { 216 | [self sendJSEvent:self name:MESSAGING_TOKEN_REFRESHED body:initialToken]; 217 | } 218 | if (pendingMessages) { 219 | for (id message in pendingMessages) { 220 | [RNFirebaseUtil sendJSEvent:self name:MESSAGING_MESSAGE_RECEIVED body:message]; 221 | } 222 | pendingMessages = nil; 223 | } 224 | } 225 | 226 | // ** Start internals ** 227 | 228 | // Because of the time delay between the app starting and the bridge being initialised 229 | // we catch any events that are received before the JS is ready to receive them 230 | - (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { 231 | if (emitter.bridge && jsReady) { 232 | [RNFirebaseUtil sendJSEvent:emitter name:name body:body]; 233 | } else { 234 | if ([name isEqualToString:MESSAGING_TOKEN_REFRESHED]) { 235 | initialToken = body; 236 | } else if ([name isEqualToString:MESSAGING_MESSAGE_RECEIVED]) { 237 | if (!pendingMessages) { 238 | pendingMessages = [[NSMutableArray alloc] init]; 239 | } 240 | [pendingMessages addObject:body]; 241 | } else { 242 | DLog(@"Received unexpected message type"); 243 | } 244 | } 245 | } 246 | 247 | //- (NSDictionary*)parseFIRMessagingRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { 248 | // NSDictionary *appData = remoteMessage.appData; 249 | // 250 | // NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; 251 | // NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; 252 | // for (id k1 in appData) { 253 | // if ([k1 isEqualToString:@"collapse_key"]) { 254 | // message[@"collapseKey"] = appData[@"collapse_key"]; 255 | // } else if ([k1 isEqualToString:@"from"]) { 256 | // message[@"from"] = appData[k1]; 257 | // } else if ([k1 isEqualToString:@"notification"]) { 258 | // // Ignore for messages 259 | // } else { 260 | // // Assume custom data key 261 | // data[k1] = appData[k1]; 262 | // } 263 | // } 264 | // message[@"data"] = data; 265 | // 266 | // return message; 267 | //} 268 | 269 | - (NSDictionary*)parseUserInfo:(NSDictionary *)userInfo { 270 | NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; 271 | NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; 272 | 273 | for (id k1 in userInfo) { 274 | if ([k1 isEqualToString:@"aps"]) { 275 | // Ignore notification section 276 | } else if ([k1 isEqualToString:@"gcm.message_id"]) { 277 | message[@"messageId"] = userInfo[k1]; 278 | } else if ([k1 isEqualToString:@"google.c.a.ts"]) { 279 | message[@"sentTime"] = userInfo[k1]; 280 | } else if ([k1 isEqualToString:@"gcm.n.e"] 281 | || [k1 isEqualToString:@"gcm.notification.sound2"] 282 | || [k1 isEqualToString:@"google.c.a.c_id"] 283 | || [k1 isEqualToString:@"google.c.a.c_l"] 284 | || [k1 isEqualToString:@"google.c.a.e"] 285 | || [k1 isEqualToString:@"google.c.a.udt"]) { 286 | // Ignore known keys 287 | } else { 288 | // Assume custom data 289 | data[k1] = userInfo[k1]; 290 | } 291 | } 292 | 293 | message[@"data"] = data; 294 | 295 | return message; 296 | } 297 | 298 | - (NSArray *)supportedEvents { 299 | return @[MESSAGING_MESSAGE_RECEIVED, MESSAGING_TOKEN_REFRESHED]; 300 | } 301 | 302 | + (BOOL)requiresMainQueueSetup 303 | { 304 | return YES; 305 | } 306 | 307 | @end 308 | 309 | #else 310 | @implementation RNFirebaseMessaging 311 | @end 312 | #endif 313 | 314 | -------------------------------------------------------------------------------- /ios/RNFirebaseUtil.h: -------------------------------------------------------------------------------- 1 | #ifndef RNFirebaseUtil_h 2 | #define RNFirebaseUtil_h 3 | 4 | #import 5 | #import 6 | #import "Firebase.h" 7 | 8 | #ifdef DEBUG 9 | #define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__); 10 | #else 11 | #define DLog(...) 12 | #endif 13 | 14 | @interface RNFirebaseUtil : NSObject 15 | 16 | + (NSString *)getISO8601String:(NSDate *)date; 17 | + (FIRApp *)getApp:(NSString *)appDisplayName; 18 | + (NSString *)getAppName:(NSString *)appDisplayName; 19 | + (NSString *)getAppDisplayName:(NSString *)appName; 20 | + (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body; 21 | + (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body; 22 | 23 | @end 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /ios/RNFirebaseUtil.m: -------------------------------------------------------------------------------- 1 | #import "RNFirebaseUtil.h" 2 | 3 | @implementation RNFirebaseUtil 4 | 5 | static NSString *const DEFAULT_APP_DISPLAY_NAME = @"[DEFAULT]"; 6 | static NSString *const DEFAULT_APP_NAME = @"__FIRAPP_DEFAULT"; 7 | 8 | + (NSString *)getISO8601String:(NSDate *)date { 9 | static NSDateFormatter *formatter = nil; 10 | 11 | if (!formatter) { 12 | formatter = [[NSDateFormatter alloc] init]; 13 | [formatter setLocale:[NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]]; 14 | formatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"UTC"]; 15 | [formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"]; 16 | } 17 | 18 | NSString *iso8601String = [formatter stringFromDate:date]; 19 | 20 | return [iso8601String stringByAppendingString:@"Z"]; 21 | } 22 | 23 | + (FIRApp *)getApp:(NSString *)appDisplayName { 24 | NSString *appName = [RNFirebaseUtil getAppName:appDisplayName]; 25 | return [FIRApp appNamed:appName]; 26 | } 27 | 28 | + (NSString *)getAppName:(NSString *)appDisplayName { 29 | if ([appDisplayName isEqualToString:DEFAULT_APP_DISPLAY_NAME]) { 30 | return DEFAULT_APP_NAME; 31 | } 32 | return appDisplayName; 33 | } 34 | 35 | + (NSString *)getAppDisplayName:(NSString *)appName { 36 | if ([appName isEqualToString:DEFAULT_APP_NAME]) { 37 | return DEFAULT_APP_DISPLAY_NAME; 38 | } 39 | return appName; 40 | } 41 | 42 | + (void)sendJSEvent:(RCTEventEmitter *)emitter name:(NSString *)name body:(id)body { 43 | @try { 44 | // TODO: Temporary fix for https://github.com/invertase/react-native-firebase/issues/233 45 | // until a better solution comes around 46 | if (emitter.bridge) { 47 | [emitter sendEventWithName:name body:body]; 48 | } 49 | } @catch (NSException *error) { 50 | DLog(@"An error occurred in sendJSEvent: %@", [error debugDescription]); 51 | } 52 | } 53 | 54 | + (void)sendJSEventWithAppName:(RCTEventEmitter *)emitter app:(FIRApp *)app name:(NSString *)name body:(id)body { 55 | // Add the appName to the body 56 | NSMutableDictionary *newBody = [body mutableCopy]; 57 | newBody[@"appName"] = [RNFirebaseUtil getAppDisplayName:app.name]; 58 | 59 | [RNFirebaseUtil sendJSEvent:emitter name:name body:newBody]; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-firebase-push-notifications", 3 | "title": "React Native Firebase Push Notifications", 4 | "version": "2.0.7", 5 | "description": "A React native firebase push notification implementation for Android and iOS", 6 | "main": "./src/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "sdkVersions": { 11 | "ios": { 12 | "firebase": ">= 10.24.0" 13 | } 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/afrihost/react-native-firebase-push-notifications.git", 18 | "baseUrl": "https://github.com/afrihost/react-native-firebase-push-notifications" 19 | }, 20 | "keywords": [ 21 | "react-native", 22 | "firebase", 23 | "firebase-push-notifications", 24 | "ios", 25 | "android", 26 | "fcm", 27 | "gcm", 28 | "google-cloud-messaging" 29 | ], 30 | "author": { 31 | "name": "Sacheen Dhanjie", 32 | "email": "open-source@afrihost.com", 33 | "username": "open-source@afrihost.com" 34 | }, 35 | "license": "MIT", 36 | "licenseFilename": "LICENSE", 37 | "peerDependencies": { 38 | "react": "^17.0.2", 39 | "react-native": ">=0.65.0" 40 | }, 41 | "dependencies": { 42 | "lodash": "^4.17.11" 43 | }, 44 | "devDependencies": { 45 | "react": "^17.0.2", 46 | "react-native": "^0.65.0" 47 | }, 48 | "bugs": { 49 | "url": "https://github.com/afrihost/react-native-firebase-push-notifications/issues" 50 | }, 51 | "homepage": "https://github.com/afrihost/react-native-firebase-push-notifications#readme" 52 | } 53 | -------------------------------------------------------------------------------- /react-native-firebase-push-notifications.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | firebase_sdk_version = package['sdkVersions']['ios']['firebase'] || '>= 9.6.0' 5 | 6 | Pod::Spec.new do |s| 7 | s.name = "react-native-firebase-push-notifications" 8 | s.version = package["version"] 9 | s.summary = package["description"] 10 | s.description = <<-DESC 11 | react-native-firebase-push-notifications 12 | DESC 13 | s.homepage = "https://github.com/afrihost/react-native-firebase-push-notifications" 14 | s.license = "MIT" 15 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 16 | s.authors = { "Your Name" => "open-source@afrihost.com" } 17 | s.platforms = { :ios => "9.0" } 18 | s.source = { :git => "https://github.com/afrihost/react-native-firebase-push-notifications.git", :tag => "#{s.version}" } 19 | 20 | s.source_files = "ios/**/*.{h,m,swift}" 21 | s.requires_arc = true 22 | 23 | # React Native dependencies 24 | s.dependency 'React-Core' 25 | 26 | if defined?($FirebaseSDKVersion) 27 | Pod::UI.puts "#{s.name}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'" 28 | firebase_sdk_version = $FirebaseSDKVersion 29 | end 30 | 31 | # Firebase dependencies 32 | s.dependency 'Firebase/CoreOnly', firebase_sdk_version 33 | s.dependency 'Firebase/Messaging', firebase_sdk_version 34 | 35 | if defined?($RNFirebaseAsStaticFramework) 36 | Pod::UI.puts "#{s.name}: Using overridden static_framework value of '#{$RNFirebaseAsStaticFramework}'" 37 | s.static_framework = $RNFirebaseAsStaticFramework 38 | else 39 | s.static_framework = false 40 | end 41 | end 42 | 43 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import { NativeEventEmitter, NativeModules, Platform } from "react-native" 3 | import EventEmitter from "react-native/Libraries/vendor/emitter/EventEmitter" 4 | import AndroidAction from "./notifications/AndroidAction" 5 | import AndroidChannel from "./notifications/AndroidChannel" 6 | import AndroidChannelGroup from "./notifications/AndroidChannelGroup" 7 | import AndroidNotifications from "./notifications/AndroidNotifications" 8 | import AndroidRemoteInput from "./notifications/AndroidRemoteInput" 9 | import IOSNotifications from "./notifications/IOSNotifications" 10 | import Notification from "./notifications/Notification" 11 | import { 12 | BadgeIconType, 13 | Category, 14 | Defaults, 15 | GroupAlert, 16 | Importance, 17 | Priority, 18 | SemanticAction, 19 | Visibility 20 | } from "./notifications/types" 21 | 22 | const { FirebaseNotifications } = NativeModules 23 | const { RNFirebaseMessaging } = NativeModules 24 | 25 | const NATIVE_EVENTS = [ 26 | "notifications_notification_displayed", 27 | "notifications_notification_opened", 28 | "notifications_notification_received" 29 | ] 30 | 31 | class Notifications extends NativeEventEmitter { 32 | constructor() { 33 | super(FirebaseNotifications) 34 | this.AndroidNotifications = new AndroidNotifications() 35 | this.IOSNotifications = new IOSNotifications() 36 | this.localEventEmitter = new EventEmitter() 37 | this.removeOnNotificationOpened = this.addListener( 38 | "notifications_notification_opened", 39 | event => { 40 | this.localEventEmitter.emit( 41 | "onNotificationOpened", 42 | new Notification(event.notification, this) 43 | ) 44 | } 45 | ) 46 | 47 | this.removeOnNotificationReceived = this.addListener( 48 | "notifications_notification_received", 49 | event => { 50 | this.localEventEmitter.emit( 51 | "onNotification", 52 | new Notification(event, this) 53 | ) 54 | } 55 | ) 56 | 57 | if (Platform.OS === "ios") { 58 | FirebaseNotifications.jsInitialised() 59 | } 60 | } 61 | 62 | android() { 63 | return this.AndroidNotifications 64 | } 65 | 66 | ios() { 67 | return this.IOSNotifications 68 | } 69 | 70 | displayNotification = async notification => { 71 | return await FirebaseNotifications.displayNotification(notification.build()) 72 | } 73 | 74 | onNotificationOpened = nextOrObserver => { 75 | let listener 76 | if (_.isFunction(nextOrObserver)) { 77 | listener = nextOrObserver 78 | } else if (isObject(nextOrObserver) && _.isFunction(nextOrObserver.next)) { 79 | listener = nextOrObserver.next 80 | } else { 81 | throw new Error( 82 | "Notifications.onNotificationOpened failed: First argument must be a function or observer object with a `next` function." 83 | ) 84 | } 85 | 86 | this.localEventEmitter.addListener("onNotificationOpened", listener) 87 | 88 | return () => { 89 | this.localEventEmitter.removeAllListeners("onNotificationOpened") 90 | } 91 | } 92 | 93 | onNotification = nextOrObserver => { 94 | let listener 95 | if (_.isFunction(nextOrObserver)) { 96 | listener = nextOrObserver 97 | } else if (isObject(nextOrObserver) && _.isFunction(nextOrObserver.next)) { 98 | listener = nextOrObserver.next 99 | } else { 100 | throw new Error( 101 | "Notifications.onNotification failed: First argument must be a function or observer object with a `next` function." 102 | ) 103 | } 104 | this.localEventEmitter.addListener("onNotification", listener) 105 | 106 | return () => { 107 | this.localEventEmitter.removeListener("onNotification", listener) 108 | } 109 | } 110 | 111 | getToken = () => { 112 | return FirebaseNotifications.getToken() 113 | } 114 | 115 | getInitialNotification = async () => { 116 | const initialNotification = 117 | await FirebaseNotifications.getInitialNotification() 118 | if (_.has(initialNotification, "notification")) { 119 | return { 120 | action: initialNotification.action, 121 | notification: new Notification(initialNotification.notification, this), 122 | results: initialNotification.results 123 | } 124 | } 125 | return null 126 | } 127 | 128 | getBadge = () => { 129 | return FirebaseNotifications.getBadge() 130 | } 131 | 132 | setBadge = async num => { 133 | return await FirebaseNotifications.setBadge(num) 134 | } 135 | 136 | requestPermission = async () => { 137 | if (Platform.OS === "ios") { 138 | return await RNFirebaseMessaging.requestPermission() 139 | } 140 | return null 141 | } 142 | 143 | hasPermission = async () => { 144 | if (Platform.OS === "ios") { 145 | return await RNFirebaseMessaging.hasPermission() 146 | } 147 | 148 | return null 149 | } 150 | } 151 | 152 | class Messaging extends NativeEventEmitter { 153 | constructor() { 154 | super(RNFirebaseMessaging) 155 | this.localEventEmitter = new EventEmitter() 156 | 157 | removeMessageTokenRefreshed = this.addListener( 158 | "messaging_token_refreshed", 159 | event => { 160 | this.localEventEmitter.emit("onTokenRefresh", event) 161 | } 162 | ) 163 | } 164 | 165 | onTokenRefresh = nextOrObserver => { 166 | let listener 167 | if (_.isFunction(nextOrObserver)) { 168 | listener = nextOrObserver 169 | } else if (isObject(nextOrObserver) && _.isFunction(nextOrObserver.next)) { 170 | listener = nextOrObserver.next 171 | } else { 172 | throw new Error( 173 | "Notifications.onTokenRefresh failed: First argument must be a function or observer object with a `next` function." 174 | ) 175 | } 176 | this.localEventEmitter.addListener("onTokenRefresh", listener) 177 | 178 | return () => { 179 | this.localEventEmitter.removeAllListeners("onTokenRefresh") 180 | } 181 | } 182 | 183 | getToken = () => { 184 | return RNFirebaseMessaging.getToken() 185 | } 186 | 187 | requestPermission = async () => { 188 | return await RNFirebaseMessaging.requestPermission() 189 | } 190 | 191 | hasPermission = async () => { 192 | return await RNFirebaseMessaging.hasPermission() 193 | } 194 | } 195 | 196 | export const notifications = new Notifications() 197 | export const messages = new Messaging() 198 | export const NotificationMessage = Notification 199 | export const Android = { 200 | Action: AndroidAction, 201 | BadgeIconType: BadgeIconType, 202 | Category: Category, 203 | Channel: AndroidChannel, 204 | ChannelGroup: AndroidChannelGroup, 205 | Defaults: Defaults, 206 | GroupAlert: GroupAlert, 207 | Importance: Importance, 208 | Priority: Priority, 209 | RemoteInput: AndroidRemoteInput, 210 | SemanticAction: SemanticAction, 211 | Visibility: Visibility 212 | } 213 | //export default FirebaseNotifications 214 | -------------------------------------------------------------------------------- /src/notifications/AndroidAction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidAction representation wrapper 4 | */ 5 | import RemoteInput, { 6 | fromNativeAndroidRemoteInput, 7 | } from './AndroidRemoteInput'; 8 | import { SemanticAction } from './types'; 9 | import type { NativeAndroidAction, SemanticActionType } from './types'; 10 | 11 | export default class AndroidAction { 12 | _action: string; 13 | 14 | _allowGeneratedReplies: boolean | void; 15 | 16 | _icon: string; 17 | 18 | _remoteInputs: RemoteInput[]; 19 | 20 | _semanticAction: SemanticActionType | void; 21 | 22 | _showUserInterface: boolean | void; 23 | 24 | _title: string; 25 | 26 | constructor(action: string, icon: string, title: string) { 27 | this._action = action; 28 | this._icon = icon; 29 | this._remoteInputs = []; 30 | this._showUserInterface = true; 31 | this._title = title; 32 | } 33 | 34 | get action(): string { 35 | return this._action; 36 | } 37 | 38 | get allowGeneratedReplies(): ?boolean { 39 | return this._allowGeneratedReplies; 40 | } 41 | 42 | get icon(): string { 43 | return this._icon; 44 | } 45 | 46 | get remoteInputs(): RemoteInput[] { 47 | return this._remoteInputs; 48 | } 49 | 50 | get semanticAction(): ?SemanticActionType { 51 | return this._semanticAction; 52 | } 53 | 54 | get showUserInterface(): ?boolean { 55 | return this._showUserInterface; 56 | } 57 | 58 | get title(): string { 59 | return this._title; 60 | } 61 | 62 | /** 63 | * 64 | * @param remoteInput 65 | * @returns {AndroidAction} 66 | */ 67 | addRemoteInput(remoteInput: RemoteInput): AndroidAction { 68 | if (!(remoteInput instanceof RemoteInput)) { 69 | throw new Error( 70 | `AndroidAction:addRemoteInput expects an 'RemoteInput' but got type ${typeof remoteInput}` 71 | ); 72 | } 73 | this._remoteInputs.push(remoteInput); 74 | return this; 75 | } 76 | 77 | /** 78 | * 79 | * @param allowGeneratedReplies 80 | * @returns {AndroidAction} 81 | */ 82 | setAllowGenerateReplies(allowGeneratedReplies: boolean): AndroidAction { 83 | this._allowGeneratedReplies = allowGeneratedReplies; 84 | return this; 85 | } 86 | 87 | /** 88 | * 89 | * @param semanticAction 90 | * @returns {AndroidAction} 91 | */ 92 | setSemanticAction(semanticAction: SemanticActionType): AndroidAction { 93 | if (!Object.values(SemanticAction).includes(semanticAction)) { 94 | throw new Error( 95 | `AndroidAction:setSemanticAction Invalid Semantic Action: ${semanticAction}` 96 | ); 97 | } 98 | this._semanticAction = semanticAction; 99 | return this; 100 | } 101 | 102 | /** 103 | * 104 | * @param showUserInterface 105 | * @returns {AndroidAction} 106 | */ 107 | setShowUserInterface(showUserInterface: boolean): AndroidAction { 108 | this._showUserInterface = showUserInterface; 109 | return this; 110 | } 111 | 112 | build(): NativeAndroidAction { 113 | if (!this._action) { 114 | throw new Error('AndroidAction: Missing required `action` property'); 115 | } else if (!this._icon) { 116 | throw new Error('AndroidAction: Missing required `icon` property'); 117 | } else if (!this._title) { 118 | throw new Error('AndroidAction: Missing required `title` property'); 119 | } 120 | 121 | return { 122 | action: this._action, 123 | allowGeneratedReplies: this._allowGeneratedReplies, 124 | icon: this._icon, 125 | remoteInputs: this._remoteInputs.map(remoteInput => remoteInput.build()), 126 | semanticAction: this._semanticAction, 127 | showUserInterface: this._showUserInterface, 128 | title: this._title, 129 | }; 130 | } 131 | } 132 | 133 | export const fromNativeAndroidAction = ( 134 | nativeAction: NativeAndroidAction 135 | ): AndroidAction => { 136 | const action = new AndroidAction( 137 | nativeAction.action, 138 | nativeAction.icon, 139 | nativeAction.title 140 | ); 141 | if (nativeAction.allowGeneratedReplies) { 142 | action.setAllowGenerateReplies(nativeAction.allowGeneratedReplies); 143 | } 144 | if (nativeAction.remoteInputs) { 145 | nativeAction.remoteInputs.forEach(remoteInput => { 146 | action.addRemoteInput(fromNativeAndroidRemoteInput(remoteInput)); 147 | }); 148 | } 149 | if (nativeAction.semanticAction) { 150 | action.setSemanticAction(nativeAction.semanticAction); 151 | } 152 | if (nativeAction.showUserInterface) { 153 | action.setShowUserInterface(nativeAction.showUserInterface); 154 | } 155 | 156 | return action; 157 | }; 158 | -------------------------------------------------------------------------------- /src/notifications/AndroidChannel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidChannel representation wrapper 4 | */ 5 | import { Importance, Visibility } from './types'; 6 | import type { ImportanceType, VisibilityType } from './types'; 7 | 8 | export type NativeAndroidChannel = {| 9 | bypassDnd?: boolean, 10 | channelId: string, 11 | description?: string, 12 | group?: string, 13 | importance: ImportanceType, 14 | lightColor?: string, 15 | lightsEnabled?: boolean, 16 | lockScreenVisibility?: VisibilityType, 17 | name: string, 18 | showBadge?: boolean, 19 | sound?: string, 20 | vibrationEnabled?: boolean, 21 | vibrationPattern?: number[], 22 | |}; 23 | 24 | export default class AndroidChannel { 25 | _bypassDnd: boolean | void; 26 | 27 | _channelId: string; 28 | 29 | _description: string | void; 30 | 31 | _group: string | void; 32 | 33 | _importance: ImportanceType; 34 | 35 | _lightColor: string | void; 36 | 37 | _lightsEnabled: boolean | void; 38 | 39 | _lockScreenVisibility: VisibilityType; 40 | 41 | _name: string; 42 | 43 | _showBadge: boolean | void; 44 | 45 | _sound: string | void; 46 | 47 | _vibrationEnabled: boolean | void; 48 | 49 | _vibrationPattern: number[] | void; 50 | 51 | constructor(channelId: string, name: string, importance: ImportanceType) { 52 | if (!Object.values(Importance).includes(importance)) { 53 | throw new Error(`AndroidChannel() Invalid Importance: ${importance}`); 54 | } 55 | this._channelId = channelId; 56 | this._name = name; 57 | this._importance = importance; 58 | } 59 | 60 | get bypassDnd(): ?boolean { 61 | return this._bypassDnd; 62 | } 63 | 64 | get channelId(): string { 65 | return this._channelId; 66 | } 67 | 68 | get description(): ?string { 69 | return this._description; 70 | } 71 | 72 | get group(): ?string { 73 | return this._group; 74 | } 75 | 76 | get importance(): ImportanceType { 77 | return this._importance; 78 | } 79 | 80 | get lightColor(): ?string { 81 | return this._lightColor; 82 | } 83 | 84 | get lightsEnabled(): ?boolean { 85 | return this._lightsEnabled; 86 | } 87 | 88 | get lockScreenVisibility(): ?VisibilityType { 89 | return this._lockScreenVisibility; 90 | } 91 | 92 | get name(): string { 93 | return this._name; 94 | } 95 | 96 | get showBadge(): ?boolean { 97 | return this._showBadge; 98 | } 99 | 100 | get sound(): ?string { 101 | return this._sound; 102 | } 103 | 104 | get vibrationEnabled(): ?boolean { 105 | return this._vibrationEnabled; 106 | } 107 | 108 | get vibrationPattern(): ?(number[]) { 109 | return this._vibrationPattern; 110 | } 111 | 112 | /** 113 | * 114 | * @param lightsEnabled 115 | * @returns {AndroidChannel} 116 | */ 117 | enableLights(lightsEnabled: boolean): AndroidChannel { 118 | this._lightsEnabled = lightsEnabled; 119 | return this; 120 | } 121 | 122 | /** 123 | * 124 | * @param vibrationEnabled 125 | * @returns {AndroidChannel} 126 | */ 127 | enableVibration(vibrationEnabled: boolean): AndroidChannel { 128 | this._vibrationEnabled = vibrationEnabled; 129 | return this; 130 | } 131 | 132 | /** 133 | * 134 | * @param bypassDnd 135 | * @returns {AndroidChannel} 136 | */ 137 | setBypassDnd(bypassDnd: boolean): AndroidChannel { 138 | this._bypassDnd = bypassDnd; 139 | return this; 140 | } 141 | 142 | /** 143 | * 144 | * @param description 145 | * @returns {AndroidChannel} 146 | */ 147 | setDescription(description: string): AndroidChannel { 148 | this._description = description; 149 | return this; 150 | } 151 | 152 | /** 153 | * 154 | * @param group 155 | * @returns {AndroidChannel} 156 | */ 157 | setGroup(groupId: string): AndroidChannel { 158 | this._group = groupId; 159 | return this; 160 | } 161 | 162 | /** 163 | * 164 | * @param lightColor 165 | * @returns {AndroidChannel} 166 | */ 167 | setLightColor(lightColor: string): AndroidChannel { 168 | this._lightColor = lightColor; 169 | return this; 170 | } 171 | 172 | /** 173 | * 174 | * @param lockScreenVisibility 175 | * @returns {AndroidChannel} 176 | */ 177 | setLockScreenVisibility( 178 | lockScreenVisibility: VisibilityType 179 | ): AndroidChannel { 180 | if (!Object.values(Visibility).includes(lockScreenVisibility)) { 181 | throw new Error( 182 | `AndroidChannel:setLockScreenVisibility Invalid Visibility: ${lockScreenVisibility}` 183 | ); 184 | } 185 | this._lockScreenVisibility = lockScreenVisibility; 186 | return this; 187 | } 188 | 189 | /** 190 | * 191 | * @param showBadge 192 | * @returns {AndroidChannel} 193 | */ 194 | setShowBadge(showBadge: boolean): AndroidChannel { 195 | this._showBadge = showBadge; 196 | return this; 197 | } 198 | 199 | /** 200 | * 201 | * @param sound 202 | * @returns {AndroidChannel} 203 | */ 204 | setSound(sound: string): AndroidChannel { 205 | this._sound = sound; 206 | return this; 207 | } 208 | 209 | /** 210 | * 211 | * @param vibrationPattern 212 | * @returns {AndroidChannel} 213 | */ 214 | setVibrationPattern(vibrationPattern: number[]): AndroidChannel { 215 | this._vibrationPattern = vibrationPattern; 216 | return this; 217 | } 218 | 219 | build(): NativeAndroidChannel { 220 | if (!this._channelId) { 221 | throw new Error('AndroidChannel: Missing required `channelId` property'); 222 | } else if (!this._importance) { 223 | throw new Error('AndroidChannel: Missing required `importance` property'); 224 | } else if (!this._name) { 225 | throw new Error('AndroidChannel: Missing required `name` property'); 226 | } 227 | 228 | return { 229 | bypassDnd: this._bypassDnd, 230 | channelId: this._channelId, 231 | description: this._description, 232 | group: this._group, 233 | importance: this._importance, 234 | lightColor: this._lightColor, 235 | lightsEnabled: this._lightsEnabled, 236 | lockScreenVisibility: this._lockScreenVisibility, 237 | name: this._name, 238 | showBadge: this._showBadge, 239 | sound: this._sound, 240 | vibrationEnabled: this._vibrationEnabled, 241 | vibrationPattern: this._vibrationPattern, 242 | }; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/notifications/AndroidChannelGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidChannelGroup representation wrapper 4 | */ 5 | 6 | import type { NativeAndroidChannel } from './AndroidChannel'; 7 | 8 | export type NativeAndroidChannelGroup = {| 9 | name: string, 10 | groupId: string, 11 | // Android API >= 28 12 | description: string | void, 13 | // Android API >= 28 14 | channels: void | NativeAndroidChannel[], 15 | |}; 16 | 17 | export default class AndroidChannelGroup { 18 | constructor(groupId: string, name: string, description?: string) { 19 | this._name = name; 20 | this._groupId = groupId; 21 | this._description = description; 22 | } 23 | 24 | _groupId: string; 25 | 26 | get groupId(): string { 27 | return this._groupId; 28 | } 29 | 30 | _name: string; 31 | 32 | get name(): string { 33 | return this._name; 34 | } 35 | 36 | _description: string | void; 37 | 38 | get description(): string | void { 39 | return this._description; 40 | } 41 | 42 | build(): NativeAndroidChannelGroup { 43 | if (!this._groupId) { 44 | throw new Error( 45 | 'AndroidChannelGroup: Missing required `groupId` property' 46 | ); 47 | } else if (!this._name) { 48 | throw new Error('AndroidChannelGroup: Missing required `name` property'); 49 | } 50 | 51 | return { 52 | name: this._name, 53 | groupId: this._groupId, 54 | description: this._description, 55 | channels: [], 56 | }; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/notifications/AndroidNotification.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidNotification representation wrapper 4 | */ 5 | import AndroidAction, { fromNativeAndroidAction } from './AndroidAction'; 6 | import { BadgeIconType, Category, GroupAlert, Priority } from './types'; 7 | import type Notification from './Notification'; 8 | import type { 9 | BadgeIconTypeType, 10 | BigPicture, 11 | BigText, 12 | CategoryType, 13 | DefaultsType, 14 | GroupAlertType, 15 | Lights, 16 | NativeAndroidNotification, 17 | PriorityType, 18 | Progress, 19 | SmallIcon, 20 | VisibilityType, 21 | } from './types'; 22 | 23 | export default class AndroidNotification { 24 | _actions: AndroidAction[]; 25 | 26 | _autoCancel: boolean | void; 27 | 28 | _badgeIconType: BadgeIconTypeType | void; 29 | 30 | _bigPicture: BigPicture | void; 31 | 32 | _bigText: BigText | void; 33 | 34 | _category: CategoryType | void; 35 | 36 | _channelId: string; 37 | 38 | _clickAction: string | void; 39 | 40 | _color: string | void; 41 | 42 | _colorized: boolean | void; 43 | 44 | _contentInfo: string | void; 45 | 46 | _defaults: DefaultsType[] | void; 47 | 48 | _group: string | void; 49 | 50 | _groupAlertBehaviour: GroupAlertType | void; 51 | 52 | _groupSummary: boolean | void; 53 | 54 | _largeIcon: string | void; 55 | 56 | _lights: Lights | void; 57 | 58 | _localOnly: boolean | void; 59 | 60 | _notification: Notification; 61 | 62 | _number: number | void; 63 | 64 | _ongoing: boolean | void; 65 | 66 | _onlyAlertOnce: boolean | void; 67 | 68 | _people: string[]; 69 | 70 | _priority: PriorityType | void; 71 | 72 | _progress: Progress | void; 73 | 74 | // _publicVersion: Notification; 75 | _remoteInputHistory: string[] | void; 76 | 77 | _shortcutId: string | void; 78 | 79 | _showWhen: boolean | void; 80 | 81 | _smallIcon: SmallIcon; 82 | 83 | _sortKey: string | void; 84 | 85 | // TODO: style: Style; // Need to figure out if this can work 86 | _tag: string | void; 87 | 88 | _ticker: string | void; 89 | 90 | _timeoutAfter: number | void; 91 | 92 | _usesChronometer: boolean | void; 93 | 94 | _vibrate: number[] | void; 95 | 96 | _visibility: VisibilityType | void; 97 | 98 | _when: number | void; 99 | 100 | // android unsupported 101 | // content: RemoteViews 102 | // contentIntent: PendingIntent - need to look at what this is 103 | // customBigContentView: RemoteViews 104 | // customContentView: RemoteViews 105 | // customHeadsUpContentView: RemoteViews 106 | // deleteIntent: PendingIntent 107 | // fullScreenIntent: PendingIntent 108 | // sound.streamType 109 | 110 | constructor(notification, data) { 111 | this._notification = notification; 112 | 113 | if (data) { 114 | this._actions = data.actions 115 | ? data.actions.map(action => fromNativeAndroidAction(action)) 116 | : []; 117 | this._autoCancel = data.autoCancel; 118 | this._badgeIconType = data.badgeIconType; 119 | this._bigPicture = data.bigPicture; 120 | this._bigText = data.bigText; 121 | this._category = data.category; 122 | this._channelId = data.channelId; 123 | this._clickAction = data.clickAction; 124 | this._color = data.color; 125 | this._colorized = data.colorized; 126 | this._contentInfo = data.contentInfo; 127 | this._defaults = data.defaults; 128 | this._group = data.group; 129 | this._groupAlertBehaviour = data.groupAlertBehaviour; 130 | this._groupSummary = data.groupSummary; 131 | this._largeIcon = data.largeIcon; 132 | this._lights = data.lights; 133 | this._localOnly = data.localOnly; 134 | this._number = data.number; 135 | this._ongoing = data.ongoing; 136 | this._onlyAlertOnce = data.onlyAlertOnce; 137 | this._people = data.people; 138 | this._priority = data.priority; 139 | this._progress = data.progress; 140 | // _publicVersion: Notification; 141 | this._remoteInputHistory = data.remoteInputHistory; 142 | this._shortcutId = data.shortcutId; 143 | this._showWhen = data.showWhen; 144 | this._smallIcon = data.smallIcon; 145 | this._sortKey = data.sortKey; 146 | this._tag = data.tag; 147 | this._ticker = data.ticker; 148 | this._timeoutAfter = data.timeoutAfter; 149 | this._usesChronometer = data.usesChronometer; 150 | this._vibrate = data.vibrate; 151 | this._visibility = data.visibility; 152 | this._when = data.when; 153 | } 154 | 155 | // Defaults 156 | this._actions = this._actions || []; 157 | this._people = this._people || []; 158 | this._smallIcon = this._smallIcon || { 159 | icon: 'ic_launcher', 160 | }; 161 | } 162 | 163 | get actions(): AndroidAction[] { 164 | return this._actions; 165 | } 166 | 167 | get autoCancel(): ?boolean { 168 | return this._autoCancel; 169 | } 170 | 171 | get badgeIconType(): ?BadgeIconTypeType { 172 | return this._badgeIconType; 173 | } 174 | 175 | get bigPicture(): ?BigPicture { 176 | return this._bigPicture; 177 | } 178 | 179 | get bigText(): ?BigText { 180 | return this._bigText; 181 | } 182 | 183 | get category(): ?CategoryType { 184 | return this._category; 185 | } 186 | 187 | get channelId(): string { 188 | return this._channelId; 189 | } 190 | 191 | get clickAction(): ?string { 192 | return this._clickAction; 193 | } 194 | 195 | get color(): ?string { 196 | return this._color; 197 | } 198 | 199 | get colorized(): ?boolean { 200 | return this._colorized; 201 | } 202 | 203 | get contentInfo(): ?string { 204 | return this._contentInfo; 205 | } 206 | 207 | get defaults(): ?(DefaultsType[]) { 208 | return this._defaults; 209 | } 210 | 211 | get group(): ?string { 212 | return this._group; 213 | } 214 | 215 | get groupAlertBehaviour(): ?GroupAlertType { 216 | return this._groupAlertBehaviour; 217 | } 218 | 219 | get groupSummary(): ?boolean { 220 | return this._groupSummary; 221 | } 222 | 223 | get largeIcon(): ?string { 224 | return this._largeIcon; 225 | } 226 | 227 | get lights(): ?Lights { 228 | return this._lights; 229 | } 230 | 231 | get localOnly(): ?boolean { 232 | return this._localOnly; 233 | } 234 | 235 | get number(): ?number { 236 | return this._number; 237 | } 238 | 239 | get ongoing(): ?boolean { 240 | return this._ongoing; 241 | } 242 | 243 | get onlyAlertOnce(): ?boolean { 244 | return this._onlyAlertOnce; 245 | } 246 | 247 | get people(): string[] { 248 | return this._people; 249 | } 250 | 251 | get priority(): ?PriorityType { 252 | return this._priority; 253 | } 254 | 255 | get progress(): ?Progress { 256 | return this._progress; 257 | } 258 | 259 | get remoteInputHistory(): ?(string[]) { 260 | return this._remoteInputHistory; 261 | } 262 | 263 | get shortcutId(): ?string { 264 | return this._shortcutId; 265 | } 266 | 267 | get showWhen(): ?boolean { 268 | return this._showWhen; 269 | } 270 | 271 | get smallIcon(): SmallIcon { 272 | return this._smallIcon; 273 | } 274 | 275 | get sortKey(): ?string { 276 | return this._sortKey; 277 | } 278 | 279 | get tag(): ?string { 280 | return this._tag; 281 | } 282 | 283 | get ticker(): ?string { 284 | return this._ticker; 285 | } 286 | 287 | get timeoutAfter(): ?number { 288 | return this._timeoutAfter; 289 | } 290 | 291 | get usesChronometer(): ?boolean { 292 | return this._usesChronometer; 293 | } 294 | 295 | get vibrate(): ?(number[]) { 296 | return this._vibrate; 297 | } 298 | 299 | get visibility(): ?VisibilityType { 300 | return this._visibility; 301 | } 302 | 303 | get when(): ?number { 304 | return this._when; 305 | } 306 | 307 | /** 308 | * 309 | * @param action 310 | * @returns {Notification} 311 | */ 312 | addAction(action: AndroidAction): Notification { 313 | if (!(action instanceof AndroidAction)) { 314 | throw new Error( 315 | `AndroidNotification:addAction expects an 'AndroidAction' but got type ${typeof action}` 316 | ); 317 | } 318 | this._actions.push(action); 319 | return this._notification; 320 | } 321 | 322 | /** 323 | * 324 | * @param person 325 | * @returns {Notification} 326 | */ 327 | addPerson(person: string): Notification { 328 | this._people.push(person); 329 | return this._notification; 330 | } 331 | 332 | /** 333 | * 334 | * @param autoCancel 335 | * @returns {Notification} 336 | */ 337 | setAutoCancel(autoCancel: boolean): Notification { 338 | this._autoCancel = autoCancel; 339 | return this._notification; 340 | } 341 | 342 | /** 343 | * 344 | * @param badgeIconType 345 | * @returns {Notification} 346 | */ 347 | setBadgeIconType(badgeIconType: BadgeIconTypeType): Notification { 348 | if (!Object.values(BadgeIconType).includes(badgeIconType)) { 349 | throw new Error( 350 | `AndroidNotification:setBadgeIconType Invalid BadgeIconType: ${badgeIconType}` 351 | ); 352 | } 353 | this._badgeIconType = badgeIconType; 354 | return this._notification; 355 | } 356 | 357 | setBigPicture( 358 | picture: string, 359 | largeIcon?: string, 360 | contentTitle?: string, 361 | summaryText?: string 362 | ): Notification { 363 | this._bigPicture = { 364 | contentTitle, 365 | largeIcon, 366 | picture, 367 | summaryText, 368 | }; 369 | return this._notification; 370 | } 371 | 372 | setBigText( 373 | text: string, 374 | contentTitle?: string, 375 | summaryText?: string 376 | ): Notification { 377 | this._bigText = { 378 | contentTitle, 379 | summaryText, 380 | text, 381 | }; 382 | return this._notification; 383 | } 384 | 385 | /** 386 | * 387 | * @param category 388 | * @returns {Notification} 389 | */ 390 | setCategory(category: CategoryType): Notification { 391 | if (!Object.values(Category).includes(category)) { 392 | throw new Error( 393 | `AndroidNotification:setCategory Invalid Category: ${category}` 394 | ); 395 | } 396 | this._category = category; 397 | return this._notification; 398 | } 399 | 400 | /** 401 | * 402 | * @param channelId 403 | * @returns {Notification} 404 | */ 405 | setChannelId(channelId: string): Notification { 406 | this._channelId = channelId; 407 | return this._notification; 408 | } 409 | 410 | /** 411 | * 412 | * @param clickAction 413 | * @returns {Notification} 414 | */ 415 | setClickAction(clickAction: string): Notification { 416 | this._clickAction = clickAction; 417 | return this._notification; 418 | } 419 | 420 | /** 421 | * 422 | * @param color 423 | * @returns {Notification} 424 | */ 425 | setColor(color: string): Notification { 426 | this._color = color; 427 | return this._notification; 428 | } 429 | 430 | /** 431 | * 432 | * @param colorized 433 | * @returns {Notification} 434 | */ 435 | setColorized(colorized: boolean): Notification { 436 | this._colorized = colorized; 437 | return this._notification; 438 | } 439 | 440 | /** 441 | * 442 | * @param contentInfo 443 | * @returns {Notification} 444 | */ 445 | setContentInfo(contentInfo: string): Notification { 446 | this._contentInfo = contentInfo; 447 | return this._notification; 448 | } 449 | 450 | /** 451 | * 452 | * @param defaults 453 | * @returns {Notification} 454 | */ 455 | setDefaults(defaults: DefaultsType[]): Notification { 456 | this._defaults = defaults; 457 | return this._notification; 458 | } 459 | 460 | /** 461 | * 462 | * @param group 463 | * @returns {Notification} 464 | */ 465 | setGroup(group: string): Notification { 466 | this._group = group; 467 | return this._notification; 468 | } 469 | 470 | /** 471 | * 472 | * @param groupAlertBehaviour 473 | * @returns {Notification} 474 | */ 475 | setGroupAlertBehaviour(groupAlertBehaviour: GroupAlertType): Notification { 476 | if (!Object.values(GroupAlert).includes(groupAlertBehaviour)) { 477 | throw new Error( 478 | `AndroidNotification:setGroupAlertBehaviour Invalid GroupAlert: ${groupAlertBehaviour}` 479 | ); 480 | } 481 | this._groupAlertBehaviour = groupAlertBehaviour; 482 | return this._notification; 483 | } 484 | 485 | /** 486 | * 487 | * @param groupSummary 488 | * @returns {Notification} 489 | */ 490 | setGroupSummary(groupSummary: boolean): Notification { 491 | this._groupSummary = groupSummary; 492 | return this._notification; 493 | } 494 | 495 | /** 496 | * 497 | * @param largeIcon 498 | * @returns {Notification} 499 | */ 500 | setLargeIcon(largeIcon: string): Notification { 501 | this._largeIcon = largeIcon; 502 | return this._notification; 503 | } 504 | 505 | /** 506 | * 507 | * @param argb 508 | * @param onMs 509 | * @param offMs 510 | * @returns {Notification} 511 | */ 512 | setLights(argb: number, onMs: number, offMs: number): Notification { 513 | this._lights = { 514 | argb, 515 | onMs, 516 | offMs, 517 | }; 518 | return this._notification; 519 | } 520 | 521 | /** 522 | * 523 | * @param localOnly 524 | * @returns {Notification} 525 | */ 526 | setLocalOnly(localOnly: boolean): Notification { 527 | this._localOnly = localOnly; 528 | return this._notification; 529 | } 530 | 531 | /** 532 | * 533 | * @param number 534 | * @returns {Notification} 535 | */ 536 | setNumber(number: number): Notification { 537 | this._number = number; 538 | return this._notification; 539 | } 540 | 541 | /** 542 | * 543 | * @param ongoing 544 | * @returns {Notification} 545 | */ 546 | setOngoing(ongoing: boolean): Notification { 547 | this._ongoing = ongoing; 548 | return this._notification; 549 | } 550 | 551 | /** 552 | * 553 | * @param onlyAlertOnce 554 | * @returns {Notification} 555 | */ 556 | setOnlyAlertOnce(onlyAlertOnce: boolean): Notification { 557 | this._onlyAlertOnce = onlyAlertOnce; 558 | return this._notification; 559 | } 560 | 561 | /** 562 | * 563 | * @param priority 564 | * @returns {Notification} 565 | */ 566 | setPriority(priority: PriorityType): Notification { 567 | if (!Object.values(Priority).includes(priority)) { 568 | throw new Error( 569 | `AndroidNotification:setPriority Invalid Priority: ${priority}` 570 | ); 571 | } 572 | this._priority = priority; 573 | return this._notification; 574 | } 575 | 576 | /** 577 | * 578 | * @param max 579 | * @param progress 580 | * @param indeterminate 581 | * @returns {Notification} 582 | */ 583 | setProgress( 584 | max: number, 585 | progress: number, 586 | indeterminate: boolean 587 | ): Notification { 588 | this._progress = { 589 | max, 590 | progress, 591 | indeterminate, 592 | }; 593 | return this._notification; 594 | } 595 | 596 | /** 597 | * 598 | * @param publicVersion 599 | * @returns {Notification} 600 | */ 601 | /* setPublicVersion(publicVersion: Notification): Notification { 602 | this._publicVersion = publicVersion; 603 | return this._notification; 604 | } */ 605 | 606 | /** 607 | * 608 | * @param remoteInputHistory 609 | * @returns {Notification} 610 | */ 611 | setRemoteInputHistory(remoteInputHistory: string[]): Notification { 612 | this._remoteInputHistory = remoteInputHistory; 613 | return this._notification; 614 | } 615 | 616 | /** 617 | * 618 | * @param shortcutId 619 | * @returns {Notification} 620 | */ 621 | setShortcutId(shortcutId: string): Notification { 622 | this._shortcutId = shortcutId; 623 | return this._notification; 624 | } 625 | 626 | /** 627 | * 628 | * @param showWhen 629 | * @returns {Notification} 630 | */ 631 | setShowWhen(showWhen: boolean): Notification { 632 | this._showWhen = showWhen; 633 | return this._notification; 634 | } 635 | 636 | /** 637 | * 638 | * @param icon 639 | * @param level 640 | * @returns {Notification} 641 | */ 642 | setSmallIcon(icon: string, level?: number): Notification { 643 | this._smallIcon = { 644 | icon, 645 | level, 646 | }; 647 | return this._notification; 648 | } 649 | 650 | /** 651 | * 652 | * @param sortKey 653 | * @returns {Notification} 654 | */ 655 | setSortKey(sortKey: string): Notification { 656 | this._sortKey = sortKey; 657 | return this._notification; 658 | } 659 | 660 | /** 661 | * 662 | * @param tag 663 | * @returns {Notification} 664 | */ 665 | setTag(tag: string): Notification { 666 | this._tag = tag; 667 | return this._notification; 668 | } 669 | 670 | /** 671 | * 672 | * @param ticker 673 | * @returns {Notification} 674 | */ 675 | setTicker(ticker: string): Notification { 676 | this._ticker = ticker; 677 | return this._notification; 678 | } 679 | 680 | /** 681 | * 682 | * @param timeoutAfter 683 | * @returns {Notification} 684 | */ 685 | setTimeoutAfter(timeoutAfter: number): Notification { 686 | this._timeoutAfter = timeoutAfter; 687 | return this._notification; 688 | } 689 | 690 | /** 691 | * 692 | * @param usesChronometer 693 | * @returns {Notification} 694 | */ 695 | setUsesChronometer(usesChronometer: boolean): Notification { 696 | this._usesChronometer = usesChronometer; 697 | return this._notification; 698 | } 699 | 700 | /** 701 | * 702 | * @param vibrate 703 | * @returns {Notification} 704 | */ 705 | setVibrate(vibrate: number[]): Notification { 706 | this._vibrate = vibrate; 707 | return this._notification; 708 | } 709 | 710 | /** 711 | * 712 | * @param visibility 713 | * @returns {Notification} 714 | */ 715 | setVisibility(visibility: VisibilityType): Notification { 716 | this._visibility = visibility; 717 | return this._notification; 718 | } 719 | 720 | /** 721 | * 722 | * @param when 723 | * @returns {Notification} 724 | */ 725 | setWhen(when: number): Notification { 726 | this._when = when; 727 | return this._notification; 728 | } 729 | 730 | build(): NativeAndroidNotification { 731 | // TODO: Validation of required fields 732 | if (!this._channelId) { 733 | throw new Error( 734 | 'AndroidNotification: Missing required `channelId` property' 735 | ); 736 | } else if (!this._smallIcon) { 737 | throw new Error( 738 | 'AndroidNotification: Missing required `smallIcon` property' 739 | ); 740 | } 741 | 742 | return { 743 | actions: this._actions.map(action => action.build()), 744 | autoCancel: this._autoCancel, 745 | badgeIconType: this._badgeIconType, 746 | bigPicture: this._bigPicture, 747 | bigText: this._bigText, 748 | category: this._category, 749 | channelId: this._channelId, 750 | clickAction: this._clickAction, 751 | color: this._color, 752 | colorized: this._colorized, 753 | contentInfo: this._contentInfo, 754 | defaults: this._defaults, 755 | group: this._group, 756 | groupAlertBehaviour: this._groupAlertBehaviour, 757 | groupSummary: this._groupSummary, 758 | largeIcon: this._largeIcon, 759 | lights: this._lights, 760 | localOnly: this._localOnly, 761 | number: this._number, 762 | ongoing: this._ongoing, 763 | onlyAlertOnce: this._onlyAlertOnce, 764 | people: this._people, 765 | priority: this._priority, 766 | progress: this._progress, 767 | // publicVersion: this._publicVersion, 768 | remoteInputHistory: this._remoteInputHistory, 769 | shortcutId: this._shortcutId, 770 | showWhen: this._showWhen, 771 | smallIcon: this._smallIcon, 772 | sortKey: this._sortKey, 773 | // TODO: style: Style, 774 | tag: this._tag, 775 | ticker: this._ticker, 776 | timeoutAfter: this._timeoutAfter, 777 | usesChronometer: this._usesChronometer, 778 | vibrate: this._vibrate, 779 | visibility: this._visibility, 780 | when: this._when, 781 | }; 782 | } 783 | } 784 | -------------------------------------------------------------------------------- /src/notifications/AndroidNotifications.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidNotifications representation wrapper 4 | */ 5 | import { Platform, NativeModules } from "react-native" 6 | import AndroidChannel from "./AndroidChannel" 7 | import AndroidChannelGroup from "./AndroidChannelGroup" 8 | import type Notifications from "./" 9 | import type { NativeAndroidChannel } from "./AndroidChannel" 10 | import type { NativeAndroidChannelGroup } from "./AndroidChannelGroup" 11 | 12 | const { FirebaseNotifications } = NativeModules 13 | 14 | export default class AndroidNotifications { 15 | _notifications: Notifications 16 | 17 | constructor(notifications: Notifications) { 18 | this._notifications = notifications 19 | } 20 | 21 | createChannel(channel: AndroidChannel): Promise { 22 | if (Platform.OS === "android") { 23 | if (!(channel instanceof AndroidChannel)) { 24 | throw new Error( 25 | `AndroidNotifications:createChannel expects an 'AndroidChannel' but got type ${typeof channel}` 26 | ) 27 | } 28 | return FirebaseNotifications.createChannel(channel.build()) 29 | } 30 | return Promise.resolve() 31 | } 32 | 33 | createChannelGroup(channelGroup: AndroidChannelGroup): Promise { 34 | if (Platform.OS === "android") { 35 | if (!(channelGroup instanceof AndroidChannelGroup)) { 36 | throw new Error( 37 | `AndroidNotifications:createChannelGroup expects an 'AndroidChannelGroup' but got type ${typeof channelGroup}` 38 | ) 39 | } 40 | return FirebaseNotifications.createChannelGroup(channelGroup.build()) 41 | } 42 | return Promise.resolve() 43 | } 44 | 45 | createChannelGroups(channelGroups: AndroidChannelGroup[]): Promise { 46 | if (Platform.OS === "android") { 47 | if (!Array.isArray(channelGroups)) { 48 | throw new Error( 49 | `AndroidNotifications:createChannelGroups expects an 'Array' but got type ${typeof channelGroups}` 50 | ) 51 | } 52 | const nativeChannelGroups = [] 53 | for (let i = 0; i < channelGroups.length; i++) { 54 | const channelGroup = channelGroups[i] 55 | if (!(channelGroup instanceof AndroidChannelGroup)) { 56 | throw new Error( 57 | `AndroidNotifications:createChannelGroups expects array items of type 'AndroidChannelGroup' but got type ${typeof channelGroup}` 58 | ) 59 | } 60 | nativeChannelGroups.push(channelGroup.build()) 61 | } 62 | return FirebaseNotifications.createChannelGroups(nativeChannelGroups) 63 | } 64 | return Promise.resolve() 65 | } 66 | 67 | createChannels(channels: AndroidChannel[]): Promise { 68 | if (Platform.OS === "android") { 69 | if (!Array.isArray(channels)) { 70 | throw new Error( 71 | `AndroidNotifications:createChannels expects an 'Array' but got type ${typeof channels}` 72 | ) 73 | } 74 | const nativeChannels = [] 75 | for (let i = 0; i < channels.length; i++) { 76 | const channel = channels[i] 77 | if (!(channel instanceof AndroidChannel)) { 78 | throw new Error( 79 | `AndroidNotifications:createChannels expects array items of type 'AndroidChannel' but got type ${typeof channel}` 80 | ) 81 | } 82 | nativeChannels.push(channel.build()) 83 | } 84 | return FirebaseNotifications.createChannels(nativeChannels) 85 | } 86 | return Promise.resolve() 87 | } 88 | 89 | removeDeliveredNotificationsByTag(tag: string): Promise { 90 | if (Platform.OS === "android") { 91 | if (typeof tag !== "string") { 92 | throw new Error( 93 | `AndroidNotifications:removeDeliveredNotificationsByTag expects an 'string' but got type ${typeof tag}` 94 | ) 95 | } 96 | return FirebaseNotifications.removeDeliveredNotificationsByTag(tag) 97 | } 98 | return Promise.resolve() 99 | } 100 | 101 | deleteChannelGroup(groupId: string): Promise { 102 | if (Platform.OS === "android") { 103 | if (typeof groupId !== "string") { 104 | throw new Error( 105 | `AndroidNotifications:deleteChannelGroup expects an 'string' but got type ${typeof groupId}` 106 | ) 107 | } 108 | return FirebaseNotifications.deleteChannelGroup(groupId) 109 | } 110 | return Promise.resolve() 111 | } 112 | 113 | deleteChannel(channelId: string): Promise { 114 | if (Platform.OS === "android") { 115 | if (typeof channelId !== "string") { 116 | throw new Error( 117 | `AndroidNotifications:deleteChannel expects an 'string' but got type ${typeof channelId}` 118 | ) 119 | } 120 | return FirebaseNotifications.deleteChannel(channelId) 121 | } 122 | return Promise.resolve() 123 | } 124 | 125 | getChannel(channelId: string): Promise { 126 | if (Platform.OS === "android") { 127 | if (typeof channelId !== "string") { 128 | throw new Error( 129 | `AndroidNotifications:getChannel expects an 'string' but got type ${typeof channelId}` 130 | ) 131 | } 132 | return Promise.resolve(FirebaseNotifications.getChannel(channelId)) 133 | } 134 | return Promise.resolve(null) 135 | } 136 | 137 | getChannels(): Promise { 138 | if (Platform.OS === "android") { 139 | return Promise.resolve(FirebaseNotifications.getChannels()) 140 | } 141 | return Promise.resolve([]) 142 | } 143 | 144 | getChannelGroup(channelGroupId: string): Promise { 145 | if (Platform.OS === "android") { 146 | if (typeof channelGroupId !== "string") { 147 | throw new Error( 148 | `AndroidNotifications:getChannel expects an 'string' but got type ${typeof channelGroupId}` 149 | ) 150 | } 151 | return Promise.resolve( 152 | FirebaseNotifications.getChannelGroup(channelGroupId) 153 | ) 154 | } 155 | return Promise.resolve(null) 156 | } 157 | 158 | getChannelGroups(): Promise { 159 | if (Platform.OS === "android") { 160 | return Promise.resolve(FirebaseNotifications.getChannelGroups()) 161 | } 162 | return Promise.resolve([]) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/notifications/AndroidRemoteInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * AndroidRemoteInput representation wrapper 4 | */ 5 | 6 | import type { AndroidAllowDataType, NativeAndroidRemoteInput } from './types'; 7 | 8 | export default class AndroidRemoteInput { 9 | _allowedDataTypes: AndroidAllowDataType[]; 10 | 11 | _allowFreeFormInput: boolean | void; 12 | 13 | _choices: string[]; 14 | 15 | _label: string | void; 16 | 17 | _resultKey: string; 18 | 19 | constructor(resultKey: string) { 20 | this._allowedDataTypes = []; 21 | this._choices = []; 22 | this._resultKey = resultKey; 23 | } 24 | 25 | get allowedDataTypes(): AndroidAllowDataType[] { 26 | return this._allowedDataTypes; 27 | } 28 | 29 | get allowFreeFormInput(): ?boolean { 30 | return this._allowFreeFormInput; 31 | } 32 | 33 | get choices(): string[] { 34 | return this._choices; 35 | } 36 | 37 | get label(): ?string { 38 | return this._label; 39 | } 40 | 41 | get resultKey(): string { 42 | return this._resultKey; 43 | } 44 | 45 | /** 46 | * 47 | * @param mimeType 48 | * @param allow 49 | * @returns {AndroidRemoteInput} 50 | */ 51 | setAllowDataType(mimeType: string, allow: boolean): AndroidRemoteInput { 52 | this._allowedDataTypes.push({ 53 | allow, 54 | mimeType, 55 | }); 56 | return this; 57 | } 58 | 59 | /** 60 | * 61 | * @param allowFreeFormInput 62 | * @returns {AndroidRemoteInput} 63 | */ 64 | setAllowFreeFormInput(allowFreeFormInput: boolean): AndroidRemoteInput { 65 | this._allowFreeFormInput = allowFreeFormInput; 66 | return this; 67 | } 68 | 69 | /** 70 | * 71 | * @param choices 72 | * @returns {AndroidRemoteInput} 73 | */ 74 | setChoices(choices: string[]): AndroidRemoteInput { 75 | this._choices = choices; 76 | return this; 77 | } 78 | 79 | /** 80 | * 81 | * @param label 82 | * @returns {AndroidRemoteInput} 83 | */ 84 | setLabel(label: string): AndroidRemoteInput { 85 | this._label = label; 86 | return this; 87 | } 88 | 89 | build(): NativeAndroidRemoteInput { 90 | if (!this._resultKey) { 91 | throw new Error( 92 | 'AndroidRemoteInput: Missing required `resultKey` property' 93 | ); 94 | } 95 | 96 | return { 97 | allowedDataTypes: this._allowedDataTypes, 98 | allowFreeFormInput: this._allowFreeFormInput, 99 | choices: this._choices, 100 | label: this._label, 101 | resultKey: this._resultKey, 102 | }; 103 | } 104 | } 105 | 106 | export const fromNativeAndroidRemoteInput = ( 107 | nativeRemoteInput: NativeAndroidRemoteInput 108 | ): AndroidRemoteInput => { 109 | const remoteInput = new AndroidRemoteInput(nativeRemoteInput.resultKey); 110 | if (nativeRemoteInput.allowedDataTypes) { 111 | for (let i = 0; i < nativeRemoteInput.allowedDataTypes.length; i++) { 112 | const allowDataType = nativeRemoteInput.allowedDataTypes[i]; 113 | remoteInput.setAllowDataType(allowDataType.mimeType, allowDataType.allow); 114 | } 115 | } 116 | if (nativeRemoteInput.allowFreeFormInput) { 117 | remoteInput.setAllowFreeFormInput(nativeRemoteInput.allowFreeFormInput); 118 | } 119 | if (nativeRemoteInput.choices) { 120 | remoteInput.setChoices(nativeRemoteInput.choices); 121 | } 122 | if (nativeRemoteInput.label) { 123 | remoteInput.setLabel(nativeRemoteInput.label); 124 | } 125 | 126 | return remoteInput; 127 | }; 128 | -------------------------------------------------------------------------------- /src/notifications/IOSNotification.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * IOSNotification representation wrapper 4 | */ 5 | import { Platform, NativeModules } from "react-native" 6 | import type Notification from "./Notification" 7 | import type Notifications from "./" 8 | import { type BackgroundFetchResultValue } from "./IOSNotifications" 9 | import type { 10 | IOSAttachment, 11 | IOSAttachmentOptions, 12 | NativeIOSNotification, 13 | } from "./types" 14 | const { FirebaseNotifications } = NativeModules 15 | 16 | type CompletionHandler = (BackgroundFetchResultValue) => void 17 | 18 | export default class IOSNotification { 19 | _alertAction: string | void 20 | 21 | _attachments: IOSAttachment[] 22 | 23 | _badge: number | void 24 | 25 | _category: string | void 26 | 27 | _hasAction: boolean | void 28 | 29 | _launchImage: string | void 30 | 31 | _notification: Notification 32 | 33 | _threadIdentifier: string | void 34 | 35 | _complete: CompletionHandler 36 | 37 | constructor( 38 | notification: Notification, 39 | notifications?: Notifications, 40 | data?: NativeIOSNotification 41 | ) { 42 | this._notification = notification 43 | 44 | if (data) { 45 | this._alertAction = data.alertAction 46 | this._attachments = data.attachments || [] 47 | this._badge = data.badge 48 | this._category = data.category 49 | this._hasAction = data.hasAction 50 | this._launchImage = data.launchImage 51 | this._threadIdentifier = data.threadIdentifier 52 | } else { 53 | this._attachments = [] 54 | } 55 | 56 | if (!(Platform.OS === "ios") || !notifications || !notifications.ios) { 57 | return this 58 | } 59 | 60 | // IOS + Native Notification Only 61 | const complete = (fetchResult: BackgroundFetchResultValue) => { 62 | const { notificationId } = notification 63 | // && notifications check for Flow 64 | if (notificationId && notifications) { 65 | FirebaseNotifications.complete(notificationId, fetchResult) 66 | } 67 | } 68 | 69 | if (notifications.ios.shouldAutoComplete) { 70 | complete(notifications.ios.backgroundFetchResult.noData) 71 | } else { 72 | this._complete = complete 73 | } 74 | } 75 | 76 | get alertAction(): ?string { 77 | return this._alertAction 78 | } 79 | 80 | get attachments(): IOSAttachment[] { 81 | return this._attachments 82 | } 83 | 84 | get badge(): ?number { 85 | return this._badge 86 | } 87 | 88 | get category(): ?string { 89 | return this._category 90 | } 91 | 92 | get hasAction(): ?boolean { 93 | return this._hasAction 94 | } 95 | 96 | get launchImage(): ?string { 97 | return this._launchImage 98 | } 99 | 100 | get threadIdentifier(): ?string { 101 | return this._threadIdentifier 102 | } 103 | 104 | get complete(): CompletionHandler { 105 | return this._complete 106 | } 107 | 108 | /** 109 | * 110 | * @param identifier 111 | * @param url 112 | * @param options 113 | * @returns {Notification} 114 | */ 115 | addAttachment( 116 | identifier: string, 117 | url: string, 118 | options?: IOSAttachmentOptions 119 | ): Notification { 120 | this._attachments.push({ 121 | identifier, 122 | options, 123 | url, 124 | }) 125 | return this._notification 126 | } 127 | 128 | /** 129 | * 130 | * @param alertAction 131 | * @returns {Notification} 132 | */ 133 | setAlertAction(alertAction: string): Notification { 134 | this._alertAction = alertAction 135 | return this._notification 136 | } 137 | 138 | /** 139 | * 140 | * @param badge 141 | * @returns {Notification} 142 | */ 143 | setBadge(badge: number): Notification { 144 | this._badge = badge 145 | return this._notification 146 | } 147 | 148 | /** 149 | * 150 | * @param category 151 | * @returns {Notification} 152 | */ 153 | setCategory(category: string): Notification { 154 | this._category = category 155 | return this._notification 156 | } 157 | 158 | /** 159 | * 160 | * @param hasAction 161 | * @returns {Notification} 162 | */ 163 | setHasAction(hasAction: boolean): Notification { 164 | this._hasAction = hasAction 165 | return this._notification 166 | } 167 | 168 | /** 169 | * 170 | * @param launchImage 171 | * @returns {Notification} 172 | */ 173 | setLaunchImage(launchImage: string): Notification { 174 | this._launchImage = launchImage 175 | return this._notification 176 | } 177 | 178 | /** 179 | * 180 | * @param threadIdentifier 181 | * @returns {Notification} 182 | */ 183 | setThreadIdentifier(threadIdentifier: string): Notification { 184 | this._threadIdentifier = threadIdentifier 185 | return this._notification 186 | } 187 | 188 | build(): NativeIOSNotification { 189 | // TODO: Validation of required fields 190 | 191 | return { 192 | alertAction: this._alertAction, 193 | attachments: this._attachments, 194 | badge: this._badge, 195 | category: this._category, 196 | hasAction: this._hasAction, 197 | launchImage: this._launchImage, 198 | threadIdentifier: this._threadIdentifier, 199 | } 200 | } 201 | } -------------------------------------------------------------------------------- /src/notifications/IOSNotifications.js: -------------------------------------------------------------------------------- 1 | import { NativeModules } from 'react-native' 2 | const { FirebaseNotifications } = NativeModules 3 | import type Notifications from './'; 4 | 5 | export type BackgroundFetchResultValue = string; 6 | type BackgroundFetchResult = { 7 | noData: BackgroundFetchResultValue, 8 | newData: BackgroundFetchResultValue, 9 | failure: BackgroundFetchResultValue, 10 | }; 11 | 12 | export default class IOSNotifications { 13 | _backgroundFetchResult: BackgroundFetchResult; 14 | 15 | shouldAutoComplete: boolean; 16 | 17 | constructor(notifications: Notifications) { 18 | this.shouldAutoComplete = true; 19 | 20 | const nativeModule = FirebaseNotifications; 21 | this._backgroundFetchResult = { 22 | noData: nativeModule.backgroundFetchResultNoData, 23 | newData: nativeModule.backgroundFetchResultNewData, 24 | failure: nativeModule.backgroundFetchResultFailed, 25 | }; 26 | } 27 | 28 | get backgroundFetchResult(): BackgroundFetchResult { 29 | return { ...this._backgroundFetchResult }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/notifications/Notification.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | * Notification representation wrapper 4 | */ 5 | import { Platform } from "react-native" 6 | import AndroidNotification from "./AndroidNotification" 7 | import IOSNotification from "./IOSNotification" 8 | import _ from "lodash" 9 | 10 | import type { NativeNotification } from "./types" 11 | import type Notifications from "./" 12 | 13 | export type NotificationOpen = {| 14 | action: string, 15 | notification: Notification, 16 | results?: { [string]: string } 17 | |} 18 | 19 | export default class Notification { 20 | // iOS 8/9 | 10+ | Android 21 | _android: AndroidNotification 22 | 23 | _body: string 24 | 25 | // alertBody | body | contentText 26 | _data: { [string]: string } 27 | 28 | // userInfo | userInfo | extras 29 | _ios: IOSNotification 30 | 31 | _notificationId: string 32 | 33 | _sound: string | void 34 | 35 | // soundName | sound | sound 36 | _subtitle: string | void 37 | 38 | // N/A | subtitle | subText 39 | _title: string // alertTitle | title | contentTitle 40 | 41 | constructor( 42 | nativeNotification?: NativeNotification, 43 | notifications?: Notifications 44 | ) { 45 | if (nativeNotification) { 46 | this._body = nativeNotification.body 47 | this._data = nativeNotification.data 48 | this._notificationId = nativeNotification.notificationId 49 | this._sound = nativeNotification.sound 50 | this._subtitle = nativeNotification.subtitle 51 | this._title = nativeNotification.title 52 | } 53 | 54 | this._android = new AndroidNotification( 55 | this, 56 | nativeNotification && nativeNotification.android 57 | ) 58 | 59 | this._ios = new IOSNotification( 60 | this, 61 | notifications, 62 | nativeNotification && nativeNotification.ios 63 | ) 64 | 65 | // Defaults 66 | this._data = this._data || {} 67 | // TODO: Is this the best way to generate an ID? 68 | this._notificationId = this._notificationId || _.uniqueId() 69 | } 70 | 71 | get android(): AndroidNotification { 72 | return this._android 73 | } 74 | 75 | get body(): string { 76 | return this._body 77 | } 78 | 79 | get data(): { [string]: string } { 80 | return this._data 81 | } 82 | 83 | get ios(): IOSNotification { 84 | return this._ios 85 | } 86 | 87 | get notificationId(): string { 88 | return this._notificationId 89 | } 90 | 91 | get sound(): ?string { 92 | return this._sound 93 | } 94 | 95 | get subtitle(): ?string { 96 | return this._subtitle 97 | } 98 | 99 | get title(): string { 100 | return this._title 101 | } 102 | 103 | /** 104 | * 105 | * @param body 106 | * @returns {Notification} 107 | */ 108 | setBody(body: string): Notification { 109 | this._body = body 110 | return this 111 | } 112 | 113 | /** 114 | * 115 | * @param data 116 | * @returns {Notification} 117 | */ 118 | setData(data: Object = {}): Notification { 119 | if (!_.isObject(data)) { 120 | throw new Error( 121 | `Notification:withData expects an object but got type '${typeof data}'.` 122 | ) 123 | } 124 | this._data = data 125 | return this 126 | } 127 | 128 | /** 129 | * 130 | * @param notificationId 131 | * @returns {Notification} 132 | */ 133 | setNotificationId(notificationId: string): Notification { 134 | this._notificationId = notificationId 135 | return this 136 | } 137 | 138 | /** 139 | * 140 | * @param sound 141 | * @returns {Notification} 142 | */ 143 | setSound(sound: string): Notification { 144 | this._sound = sound 145 | return this 146 | } 147 | 148 | /** 149 | * 150 | * @param subtitle 151 | * @returns {Notification} 152 | */ 153 | setSubtitle(subtitle: string): Notification { 154 | this._subtitle = subtitle 155 | return this 156 | } 157 | 158 | /** 159 | * 160 | * @param title 161 | * @returns {Notification} 162 | */ 163 | setTitle(title: string): Notification { 164 | this._title = title 165 | return this 166 | } 167 | 168 | build(): NativeNotification { 169 | if (!this._notificationId) { 170 | throw new Error( 171 | "Notification: Missing required `notificationId` property" 172 | ) 173 | } 174 | 175 | return { 176 | android: Platform.OS === "android" ? this._android.build() : undefined, 177 | body: this._body, 178 | data: this._data, 179 | ios: Platform.OS === "ios" ? this._ios.build() : undefined, 180 | notificationId: this._notificationId, 181 | sound: this._sound, 182 | subtitle: this._subtitle, 183 | title: this._title 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/notifications/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @flow 3 | */ 4 | export const BadgeIconType = { 5 | Large: 2, 6 | None: 0, 7 | Small: 1, 8 | }; 9 | 10 | export const Category = { 11 | Alarm: 'alarm', 12 | Call: 'call', 13 | Email: 'email', 14 | Error: 'err', 15 | Event: 'event', 16 | Message: 'msg', 17 | Progress: 'progress', 18 | Promo: 'promo', 19 | Recommendation: 'recommendation', 20 | Reminder: 'reminder', 21 | Service: 'service', 22 | Social: 'social', 23 | Status: 'status', 24 | System: 'system', 25 | Transport: 'transport', 26 | }; 27 | 28 | export const Defaults = { 29 | All: -1, 30 | Lights: 4, 31 | Sound: 1, 32 | Vibrate: 2, 33 | }; 34 | 35 | export const GroupAlert = { 36 | All: 0, 37 | Children: 2, 38 | Summary: 1, 39 | }; 40 | 41 | export const Importance = { 42 | Default: 3, 43 | High: 4, 44 | Low: 2, 45 | Max: 5, 46 | Min: 1, 47 | None: 0, 48 | Unspecified: -1000, 49 | }; 50 | 51 | export const Priority = { 52 | Default: 0, 53 | High: 1, 54 | Low: -1, 55 | Max: 2, 56 | Min: -2, 57 | }; 58 | 59 | export const SemanticAction = { 60 | Archive: 5, 61 | Call: 10, 62 | Delete: 4, 63 | MarkAsRead: 2, 64 | MarkAsUnread: 3, 65 | Mute: 6, 66 | None: 0, 67 | Reply: 1, 68 | ThumbsDown: 9, 69 | ThumbsUp: 8, 70 | Unmute: 7, 71 | }; 72 | 73 | export const Visibility = { 74 | Private: 0, 75 | Public: 1, 76 | Secret: -1, 77 | }; 78 | 79 | export type BadgeIconTypeType = $Values; 80 | export type CategoryType = $Values; 81 | export type DefaultsType = $Values; 82 | export type GroupAlertType = $Values; 83 | export type ImportanceType = $Values; 84 | export type PriorityType = $Values; 85 | export type SemanticActionType = $Values; 86 | export type VisibilityType = $Values; 87 | 88 | export type BigPicture = {| 89 | contentTitle?: string, 90 | largeIcon?: string, 91 | picture: string, 92 | summaryText?: string, 93 | |}; 94 | 95 | export type BigText = {| 96 | contentTitle?: string, 97 | summaryText?: string, 98 | text: string, 99 | |}; 100 | 101 | export type Lights = {| 102 | argb: number, 103 | onMs: number, 104 | offMs: number, 105 | |}; 106 | 107 | export type Progress = {| 108 | max: number, 109 | progress: number, 110 | indeterminate: boolean, 111 | |}; 112 | 113 | export type SmallIcon = {| 114 | icon: string, 115 | level?: number, 116 | |}; 117 | 118 | export type AndroidAllowDataType = { 119 | allow: boolean, 120 | mimeType: string, 121 | }; 122 | 123 | export type NativeAndroidRemoteInput = {| 124 | allowedDataTypes: AndroidAllowDataType[], 125 | allowFreeFormInput?: boolean, 126 | choices: string[], 127 | label?: string, 128 | resultKey: string, 129 | |}; 130 | 131 | export type NativeAndroidAction = {| 132 | action: string, 133 | allowGeneratedReplies?: boolean, 134 | icon: string, 135 | remoteInputs: NativeAndroidRemoteInput[], 136 | semanticAction?: SemanticActionType, 137 | showUserInterface?: boolean, 138 | title: string, 139 | |}; 140 | 141 | export type NativeAndroidNotification = {| 142 | actions?: NativeAndroidAction[], 143 | autoCancel?: boolean, 144 | badgeIconType?: BadgeIconTypeType, 145 | bigPicture?: BigPicture, 146 | bigText?: BigText, 147 | category?: CategoryType, 148 | channelId: string, 149 | clickAction?: string, 150 | color?: string, 151 | colorized?: boolean, 152 | contentInfo?: string, 153 | defaults?: DefaultsType[], 154 | group?: string, 155 | groupAlertBehaviour?: GroupAlertType, 156 | groupSummary?: boolean, 157 | largeIcon?: string, 158 | lights?: Lights, 159 | localOnly?: boolean, 160 | number?: number, 161 | ongoing?: boolean, 162 | onlyAlertOnce?: boolean, 163 | people: string[], 164 | priority?: PriorityType, 165 | progress?: Progress, 166 | // publicVersion: Notification, 167 | remoteInputHistory?: string[], 168 | shortcutId?: string, 169 | showWhen?: boolean, 170 | smallIcon: SmallIcon, 171 | sortKey?: string, 172 | // TODO: style: Style, 173 | tag?: string, 174 | ticker?: string, 175 | timeoutAfter?: number, 176 | usesChronometer?: boolean, 177 | vibrate?: number[], 178 | visibility?: VisibilityType, 179 | when?: number, 180 | |}; 181 | 182 | export type IOSAttachmentOptions = {| 183 | typeHint: string, 184 | thumbnailHidden: boolean, 185 | thumbnailClippingRect: { 186 | height: number, 187 | width: number, 188 | x: number, 189 | y: number, 190 | }, 191 | thumbnailTime: number, 192 | |}; 193 | 194 | export type IOSAttachment = {| 195 | identifier: string, 196 | options?: IOSAttachmentOptions, 197 | url: string, 198 | |}; 199 | 200 | export type NativeIOSNotification = {| 201 | alertAction?: string, 202 | attachments: IOSAttachment[], 203 | badge?: number, 204 | category?: string, 205 | hasAction?: boolean, 206 | launchImage?: string, 207 | threadIdentifier?: string, 208 | |}; 209 | 210 | export type Schedule = {| 211 | exact?: boolean, 212 | fireDate: number, 213 | repeatInterval?: 'minute' | 'hour' | 'day' | 'week', 214 | |}; 215 | 216 | export type NativeNotification = {| 217 | android?: NativeAndroidNotification, 218 | body: string, 219 | data: { [string]: string }, 220 | ios?: NativeIOSNotification, 221 | notificationId: string, 222 | schedule?: Schedule, 223 | sound?: string, 224 | subtitle?: string, 225 | title: string, 226 | |}; 227 | 228 | export type NativeNotificationOpen = {| 229 | action: string, 230 | notification: NativeNotification, 231 | results?: { [string]: string }, 232 | |}; 233 | --------------------------------------------------------------------------------