├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── Examples └── simple-fcm-client │ ├── .babelrc │ ├── .buckconfig │ ├── .flowconfig │ ├── .gitignore │ ├── .watchmanconfig │ ├── README.md │ ├── __tests__ │ ├── index.android.js │ └── index.ios.js │ ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── google │ │ │ │ └── firebase │ │ │ │ └── quickstart │ │ │ │ └── fcm │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_notif.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_notif.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_notif.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_notif.png │ │ │ ├── raw │ │ │ └── bell.mp3 │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle │ ├── app │ ├── App.js │ ├── FirebaseClient.js │ ├── FirebaseConstants.js │ └── Listeners.js │ ├── index.android.js │ ├── index.ios.js │ ├── ios │ ├── Podfile │ ├── Podfile.lock │ ├── SimpleFcmClient.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── SimpleFcmClient.xcscheme │ ├── SimpleFcmClient.xcworkspace │ │ └── contents.xcworkspacedata │ ├── SimpleFcmClient │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── SimpleFcmClient.entitlements │ │ └── main.m │ ├── SimpleFcmClientTests │ │ ├── Info.plist │ │ └── SimpleFcmClientTests.m │ └── bell.mp3 │ ├── package.json │ └── yarn.lock ├── ISSUE_TEMPLATE ├── LICENSE ├── README.md ├── android ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── evollu │ └── react │ └── fcm │ ├── BadgeHelper.java │ ├── BundleJSONConverter.java │ ├── FIRLocalMessagingHelper.java │ ├── FIRLocalMessagingPublisher.java │ ├── FIRMessagingModule.java │ ├── FIRMessagingPackage.java │ ├── FIRSystemBootEventReceiver.java │ ├── InstanceIdService.java │ ├── MessagingService.java │ ├── ReactNativeJson.java │ └── SendNotificationTask.java ├── index.d.ts ├── index.js ├── ios ├── RNFIRMessaging.h ├── RNFIRMessaging.m └── RNFIRMessaging.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── libinlu.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ ├── LLu.xcuserdatad │ └── xcschemes │ │ ├── RNFIRMessaging.xcscheme │ │ └── xcschememanagement.plist │ └── libinlu.xcuserdatad │ └── xcschemes │ ├── RCTPushNotification.xcscheme │ └── xcschememanagement.plist ├── package.json └── react-native-fcm.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | xcuserdata 4 | .idea/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | Examples -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | RELEASE NOTE MOVED TO [Release Section](https://github.com/evollu/react-native-fcm/releases) 2 | 3 | ### 1.0.13 BREAKING CHANGES 4 | - get initial intent inside module, support rn 0.29.0 (for people upgrading from older version, change `new FIRMessagingPackage(getIntent())` back to `new FIRMessagingPackage()`) 5 | - remove initAction as it is just duplication of initData 6 | 7 | ### 1.0.12 8 | DON'T USE 9 | 10 | ### 1.0.11 11 | - change android library version to use 9.+ instead of 9.0.1 so it lives well with other libraries 12 | 13 | ### 1.0.10 14 | - added support for projects not using cocoapods 15 | 16 | ### 1.0.9 17 | - added FCM.on support 18 | - returns string token instead of object for FCM.on('refreshToken') event 19 | - added support for subscribe/unsubscribe topic feature 20 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } -------------------------------------------------------------------------------- /Examples/simple-fcm-client/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | # We fork some components by platform. 4 | .*/*[.]android.js 5 | 6 | # Ignore templates with `@flow` in header 7 | .*/local-cli/generator.* 8 | 9 | # Ignore malformed json 10 | .*/node_modules/y18n/test/.*\.json 11 | 12 | # Ignore the website subdir 13 | /website/.* 14 | 15 | # Ignore BUCK generated dirs 16 | /\.buckd/ 17 | 18 | # Ignore unexpected extra @providesModule 19 | .*/node_modules/commoner/test/source/widget/share.js 20 | 21 | # Ignore duplicate module providers 22 | # For RN Apps installed via npm, "Libraries" folder is inside node_modules/react-native but in the source repo it is in the root 23 | .*/Libraries/react-native/React.js 24 | .*/Libraries/react-native/ReactNative.js 25 | .*/node_modules/jest-runtime/build/__tests__/.* 26 | 27 | [include] 28 | 29 | [libs] 30 | node_modules/react-native/Libraries/react-native/react-native-interface.js 31 | node_modules/react-native/flow 32 | flow/ 33 | 34 | [options] 35 | module.system=haste 36 | 37 | esproposal.class_static_fields=enable 38 | esproposal.class_instance_fields=enable 39 | 40 | experimental.strict_type_args=true 41 | 42 | munge_underscores=true 43 | 44 | module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' 45 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 46 | 47 | suppress_type=$FlowIssue 48 | suppress_type=$FlowFixMe 49 | suppress_type=$FixMe 50 | 51 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-2]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 52 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-2]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 53 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 54 | 55 | unsafe.enable_getters_and_setters=true 56 | 57 | [version] 58 | ^0.32.0 59 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | android/keystores/debug.keystore 42 | 43 | # FCM 44 | ios/GoogleService-Info.plist 45 | android/app/google-services.json 46 | app/FirebaseConstants.js 47 | 48 | #Pods 49 | ios/Pods 50 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Examples/simple-fcm-client/README.md: -------------------------------------------------------------------------------- 1 | # simple-fcm-client 2 | 3 | ## iOS 4 | 5 | ### Installation 6 | 7 | - `yarn install` 8 | - Overwrite `ios/GoogleService-info.plist` with your file 9 | - Change the _Bundle Identifier_ in Xcode project settings 10 | - Select your _Team_ for both targets (`SimpleFcmClient` and `SimpleFcmClientTests`) 11 | - Update your API_KEY [here](https://github.com/evollu/react-native-fcm/blob/master/Examples/simple-fcm-client/app/FirebaseConstants.js#L3) 12 | - run `pod install` under `ios` folder 13 | 14 | ## Android 15 | 16 | TBA - PRs welcome! 17 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.google.firebase.quickstart.fcm', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.google.firebase.quickstart.fcm', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation 19 | * entryFile: "index.android.js", 20 | * 21 | * // whether to bundle JS and assets in debug mode 22 | * bundleInDebug: false, 23 | * 24 | * // whether to bundle JS and assets in release mode 25 | * bundleInRelease: true, 26 | * 27 | * // whether to bundle JS and assets in another build variant (if configured). 28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 29 | * // The configuration property can be in the following formats 30 | * // 'bundleIn${productFlavor}${buildType}' 31 | * // 'bundleIn${buildType}' 32 | * // bundleInFreeDebug: true, 33 | * // bundleInPaidRelease: true, 34 | * // bundleInBeta: true, 35 | * 36 | * // the root of your project, i.e. where "package.json" lives 37 | * root: "../../", 38 | * 39 | * // where to put the JS bundle asset in debug mode 40 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 41 | * 42 | * // where to put the JS bundle asset in release mode 43 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 44 | * 45 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 46 | * // require('./image.png')), in debug mode 47 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 48 | * 49 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 50 | * // require('./image.png')), in release mode 51 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 52 | * 53 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 54 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 55 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 56 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 57 | * // for example, you might want to remove it from here. 58 | * inputExcludes: ["android/**", "ios/**"], 59 | * 60 | * // override which node gets called and with what additional arguments 61 | * nodeExecutableAndArgs: ["node"] 62 | * 63 | * // supply additional arguments to the packager 64 | * extraPackagerArgs: [] 65 | * ] 66 | */ 67 | 68 | apply from: "../../node_modules/react-native/react.gradle" 69 | 70 | /** 71 | * Set this to true to create two separate APKs instead of one: 72 | * - An APK that only works on ARM devices 73 | * - An APK that only works on x86 devices 74 | * The advantage is the size of the APK is reduced by about 4MB. 75 | * Upload all the APKs to the Play Store and people will download 76 | * the correct one based on the CPU architecture of their device. 77 | */ 78 | def enableSeparateBuildPerCPUArchitecture = false 79 | 80 | /** 81 | * Run Proguard to shrink the Java bytecode in release builds. 82 | */ 83 | def enableProguardInReleaseBuilds = false 84 | 85 | android { 86 | compileSdkVersion 27 87 | buildToolsVersion "27.0.3" 88 | 89 | defaultConfig { 90 | applicationId "com.google.firebase.quickstart.fcm" 91 | minSdkVersion 16 92 | targetSdkVersion 27 93 | versionCode 1 94 | versionName "1.0" 95 | ndk { 96 | abiFilters "armeabi-v7a", "x86" 97 | } 98 | } 99 | splits { 100 | abi { 101 | reset() 102 | enable enableSeparateBuildPerCPUArchitecture 103 | universalApk false // If true, also generate a universal APK 104 | include "armeabi-v7a", "x86" 105 | } 106 | } 107 | buildTypes { 108 | release { 109 | minifyEnabled enableProguardInReleaseBuilds 110 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 111 | } 112 | } 113 | // applicationVariants are e.g. debug, release 114 | applicationVariants.all { variant -> 115 | variant.outputs.each { output -> 116 | // For each separate APK per architecture, set a unique version code as described here: 117 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits 118 | def versionCodes = ["armeabi-v7a":1, "x86":2] 119 | def abi = output.getFilter(OutputFile.ABI) 120 | if (abi != null) { // null for the universal-debug, universal-release variants 121 | output.versionCodeOverride = 122 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 123 | } 124 | } 125 | } 126 | configurations.all { 127 | resolutionStrategy.force 'com.android.support:support-core-utils:26.1.0' 128 | resolutionStrategy.force 'com.android.support:support-core-ui:26.1.0' 129 | } 130 | } 131 | 132 | dependencies { 133 | compile(project(':react-native-maps')) { 134 | exclude group: 'com.google.android.gms', module: 'play-services-base' 135 | // This resolution make compiler ignoring play-service-base's version requirement in react-native-maps 136 | // so that it only read from react-native-fcm 137 | // you can also lock the version in this gradle file and ignore all module declaration 138 | // or you can use ResolutionStragety 139 | } 140 | compile project(':react-native-fcm') 141 | compile fileTree(dir: "libs", include: ["*.jar"]) 142 | compile "com.facebook.react:react-native:+" // From node_modules 143 | } 144 | 145 | // Run this once to be able to run the application with BUCK 146 | // puts all compile dependencies into folder libs for BUCK to use 147 | task copyDownloadableDepsToLibs(type: Copy) { 148 | from configurations.compile 149 | into 'libs' 150 | } 151 | 152 | apply plugin: "com.google.gms.google-services" 153 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/java/com/google/firebase/quickstart/fcm/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.google.firebase.quickstart.fcm; 2 | 3 | import android.content.Intent; 4 | 5 | import com.facebook.react.ReactActivity; 6 | 7 | public class MainActivity extends ReactActivity { 8 | 9 | /** 10 | * Returns the name of the main component registered from JavaScript. 11 | * This is used to schedule rendering of the component. 12 | */ 13 | @Override 14 | protected String getMainComponentName() { 15 | return "SimpleFcmClient"; 16 | } 17 | 18 | @Override 19 | public void onNewIntent(Intent intent) { 20 | super.onNewIntent(intent); 21 | setIntent(intent); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/java/com/google/firebase/quickstart/fcm/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.google.firebase.quickstart.fcm; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.airbnb.android.react.maps.MapsPackage; 8 | import com.evollu.react.fcm.FIRMessagingPackage; 9 | import com.facebook.react.ReactInstanceManager; 10 | import com.facebook.react.ReactNativeHost; 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.react.shell.MainReactPackage; 13 | import com.facebook.soloader.SoLoader; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | public class MainApplication extends Application implements ReactApplication { 19 | 20 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 21 | @Override 22 | public boolean getUseDeveloperSupport() { 23 | return BuildConfig.DEBUG; 24 | } 25 | 26 | @Override 27 | protected List getPackages() { 28 | return Arrays.asList( 29 | new MainReactPackage(), 30 | new MapsPackage(), 31 | new FIRMessagingPackage() 32 | ); 33 | } 34 | }; 35 | 36 | @Override 37 | public ReactNativeHost getReactNativeHost() { 38 | return mReactNativeHost; 39 | } 40 | 41 | @Override 42 | public void onCreate() { // <-- Check this block exists 43 | super.onCreate(); 44 | SoLoader.init(this, /* native exopackage */ false); // <-- Check this line exists within the block 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-hdpi/ic_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-hdpi/ic_notif.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-mdpi/ic_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-mdpi/ic_notif.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-xhdpi/ic_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-xhdpi/ic_notif.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/mipmap-xxhdpi/ic_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/mipmap-xxhdpi/ic_notif.png -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/raw/bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/app/src/main/res/raw/bell.mp3 -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SimpleFcmClient 3 | 4 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | maven { 7 | url 'https://maven.google.com/' 8 | name 'Google' 9 | } 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.1.1' 13 | classpath 'com.google.gms:google-services:3.1.2' 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | mavenLocal() 23 | jcenter() 24 | maven { 25 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 26 | url "$rootDir/../node_modules/react-native/android" 27 | } 28 | maven { 29 | url 'https://maven.google.com/' 30 | name 'Google' 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu May 03 13:49:29 EDT 2018 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-4.4-all.zip 7 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'SimpleFcmClient' 2 | include ':react-native-maps' 3 | project(':react-native-maps').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-maps/lib/android') 4 | 5 | include ':app' 6 | include ':react-native-fcm' 7 | project(':react-native-fcm').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fcm/android') 8 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/app/App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from "react"; 8 | import { 9 | StyleSheet, 10 | Text, 11 | TouchableOpacity, 12 | View, 13 | Clipboard, 14 | Platform, 15 | ScrollView 16 | } from "react-native"; 17 | 18 | import { StackNavigator } from "react-navigation"; 19 | 20 | import FCM, { NotificationActionType } from "react-native-fcm"; 21 | 22 | import { registerKilledListener, registerAppListener } from "./Listeners"; 23 | import firebaseClient from "./FirebaseClient"; 24 | 25 | registerKilledListener(); 26 | 27 | class MainPage extends Component { 28 | constructor(props) { 29 | super(props); 30 | 31 | this.state = { 32 | token: "", 33 | tokenCopyFeedback: "" 34 | }; 35 | } 36 | 37 | async componentDidMount() { 38 | //FCM.createNotificationChannel is mandatory for Android targeting >=8. Otherwise you won't see any notification 39 | FCM.createNotificationChannel({ 40 | id: 'default', 41 | name: 'Default', 42 | description: 'used for example', 43 | priority: 'high' 44 | }) 45 | registerAppListener(this.props.navigation); 46 | FCM.getInitialNotification().then(notif => { 47 | this.setState({ 48 | initNotif: notif 49 | }); 50 | if (notif && notif.targetScreen === "detail") { 51 | setTimeout(() => { 52 | this.props.navigation.navigate("Detail"); 53 | }, 500); 54 | } 55 | }); 56 | 57 | try { 58 | let result = await FCM.requestPermissions({ 59 | badge: false, 60 | sound: true, 61 | alert: true 62 | }); 63 | } catch (e) { 64 | console.error(e); 65 | } 66 | 67 | FCM.getFCMToken().then(token => { 68 | console.log("TOKEN (getFCMToken)", token); 69 | this.setState({ token: token || "" }); 70 | }); 71 | 72 | if (Platform.OS === "ios") { 73 | FCM.getAPNSToken().then(token => { 74 | console.log("APNS TOKEN (getFCMToken)", token); 75 | }); 76 | } 77 | 78 | // topic example 79 | // FCM.subscribeToTopic('sometopic') 80 | // FCM.unsubscribeFromTopic('sometopic') 81 | } 82 | 83 | showLocalNotification() { 84 | FCM.presentLocalNotification({ 85 | channel: 'default', 86 | id: new Date().valueOf().toString(), // (optional for instant notification) 87 | title: "Test Notification with action", // as FCM payload 88 | body: "Force touch to reply", // as FCM payload (required) 89 | sound: "bell.mp3", // "default" or filename 90 | priority: "high", // as FCM payload 91 | click_action: "com.myapp.MyCategory", // as FCM payload - this is used as category identifier on iOS. 92 | badge: 10, // as FCM payload IOS only, set 0 to clear badges 93 | number: 10, // Android only 94 | ticker: "My Notification Ticker", // Android only 95 | auto_cancel: true, // Android only (default true) 96 | large_icon: 97 | "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", // Android only 98 | icon: "ic_launcher", // as FCM payload, you can relace this with custom icon you put in mipmap 99 | big_text: "Show when notification is expanded", // Android only 100 | sub_text: "This is a subText", // Android only 101 | color: "red", // Android only 102 | vibrate: 300, // Android only default: 300, no vibration if you pass 0 103 | wake_screen: true, // Android only, wake up screen when notification arrives 104 | group: "group", // Android only 105 | picture: 106 | "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png", // Android only bigPicture style 107 | ongoing: true, // Android only 108 | my_custom_data: "my_custom_field_value", // extra data you want to throw 109 | lights: true, // Android only, LED blinking (default false) 110 | show_in_foreground: true // notification when app is in foreground (local & remote) 111 | }); 112 | } 113 | 114 | scheduleLocalNotification() { 115 | FCM.scheduleLocalNotification({ 116 | id: "testnotif", 117 | fire_date: new Date().getTime() + 5000, 118 | vibrate: 500, 119 | title: "Hello", 120 | body: "Test Scheduled Notification", 121 | sub_text: "sub text", 122 | priority: "high", 123 | large_icon: 124 | "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", 125 | show_in_foreground: true, 126 | picture: 127 | "https://firebase.google.com/_static/af7ae4b3fc/images/firebase/lockup.png", 128 | wake_screen: true, 129 | extra1: { a: 1 }, 130 | extra2: 1 131 | }); 132 | } 133 | 134 | sendRemoteNotification(token) { 135 | let body; 136 | 137 | if (Platform.OS === "android") { 138 | body = { 139 | to: token, 140 | data: { 141 | custom_notification: { 142 | title: "Simple FCM Client", 143 | body: "Click me to go to detail", 144 | sound: "default", 145 | priority: "high", 146 | show_in_foreground: true, 147 | targetScreen: "detail" 148 | } 149 | }, 150 | priority: 10 151 | }; 152 | } else { 153 | body = { 154 | to: token, 155 | notification: { 156 | title: "Simple FCM Client", 157 | body: "Click me to go to detail", 158 | sound: "default" 159 | }, 160 | data: { 161 | targetScreen: "detail" 162 | }, 163 | priority: 10 164 | }; 165 | } 166 | 167 | firebaseClient.send(JSON.stringify(body), "notification"); 168 | } 169 | 170 | sendRemoteData(token) { 171 | let body = { 172 | to: token, 173 | data: { 174 | title: "Simple FCM Client", 175 | body: "This is a notification with only DATA.", 176 | sound: "default" 177 | }, 178 | priority: "normal" 179 | }; 180 | 181 | firebaseClient.send(JSON.stringify(body), "data"); 182 | } 183 | 184 | showLocalNotificationWithAction() { 185 | FCM.presentLocalNotification({ 186 | title: "Test Notification with action", 187 | body: "Force touch to reply", 188 | priority: "high", 189 | show_in_foreground: true, 190 | click_action: "com.myidentifi.fcm.text", // for ios 191 | android_actions: JSON.stringify([ 192 | { 193 | id: "view", 194 | title: "view" 195 | }, 196 | { 197 | id: "dismiss", 198 | title: "dismiss" 199 | } 200 | ]) // for android, take syntax similar to ios's. only buttons are supported 201 | }); 202 | } 203 | 204 | render() { 205 | let { token, tokenCopyFeedback } = this.state; 206 | 207 | return ( 208 | 209 | 210 | Welcome to Simple Fcm Client! 211 | 212 | {this.state.tokenCopyFeedback} 213 | 214 | 215 | Remote notif won't be available to iOS emulators 216 | 217 | 218 | this.sendRemoteNotification(token)} 220 | style={styles.button} 221 | > 222 | Send Remote Notification 223 | 224 | 225 | this.sendRemoteData(token)} 227 | style={styles.button} 228 | > 229 | Send Remote Data 230 | 231 | 232 | this.showLocalNotification()} 234 | style={styles.button} 235 | > 236 | Show Local Notification 237 | 238 | 239 | this.showLocalNotificationWithAction(token)} 241 | style={styles.button} 242 | > 243 | 244 | Show Local Notification with Action 245 | 246 | 247 | 248 | this.scheduleLocalNotification()} 250 | style={styles.button} 251 | > 252 | Schedule Notification in 5s 253 | 254 | 255 | Init notif: 256 | {JSON.stringify(this.state.initNotif)} 257 | 258 | Token: 259 | this.setClipboardContent(this.state.token)} 262 | > 263 | {this.state.token} 264 | 265 | 266 | 267 | ); 268 | } 269 | 270 | setClipboardContent(text) { 271 | Clipboard.setString(text); 272 | this.setState({ tokenCopyFeedback: "Token copied to clipboard." }); 273 | setTimeout(() => { 274 | this.clearTokenCopyFeedback(); 275 | }, 2000); 276 | } 277 | 278 | clearTokenCopyFeedback() { 279 | this.setState({ tokenCopyFeedback: "" }); 280 | } 281 | } 282 | 283 | class DetailPage extends Component { 284 | render() { 285 | return ( 286 | 287 | Detail page 288 | 289 | ); 290 | } 291 | } 292 | 293 | export default StackNavigator( 294 | { 295 | Main: { 296 | screen: MainPage 297 | }, 298 | Detail: { 299 | screen: DetailPage 300 | } 301 | }, 302 | { 303 | initialRouteName: "Main" 304 | } 305 | ); 306 | 307 | const styles = StyleSheet.create({ 308 | container: { 309 | flex: 1, 310 | justifyContent: "center", 311 | alignItems: "center", 312 | backgroundColor: "#F5FCFF" 313 | }, 314 | welcome: { 315 | fontSize: 20, 316 | textAlign: "center", 317 | margin: 10 318 | }, 319 | instructions: { 320 | textAlign: "center", 321 | color: "#333333", 322 | marginBottom: 2 323 | }, 324 | feedback: { 325 | textAlign: "center", 326 | color: "#996633", 327 | marginBottom: 3 328 | }, 329 | button: { 330 | backgroundColor: "teal", 331 | paddingHorizontal: 20, 332 | paddingVertical: 15, 333 | marginVertical: 10, 334 | borderRadius: 10 335 | }, 336 | buttonText: { 337 | color: "white", 338 | backgroundColor: "transparent" 339 | } 340 | }); 341 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/app/FirebaseClient.js: -------------------------------------------------------------------------------- 1 | import FirebaseConstants from "./FirebaseConstants"; 2 | import { Alert } from "react-native"; 3 | 4 | const API_URL = "https://fcm.googleapis.com/fcm/send"; 5 | 6 | class FirebaseClient { 7 | 8 | async send(body, type) { 9 | if(FirebaseConstants.KEY === 'YOUR_API_KEY'){ 10 | Alert.alert('Set your API_KEY in app/FirebaseConstants.js') 11 | return; 12 | } 13 | let headers = new Headers({ 14 | "Content-Type": "application/json", 15 | "Authorization": "key=" + FirebaseConstants.KEY 16 | }); 17 | 18 | try { 19 | let response = await fetch(API_URL, { method: "POST", headers, body }); 20 | console.log(response); 21 | try{ 22 | response = await response.json(); 23 | if(!response.success){ 24 | Alert.alert('Failed to send notification, check error log') 25 | } 26 | } catch (err){ 27 | Alert.alert('Failed to send notification, check error log') 28 | } 29 | } catch (err) { 30 | Alert.alert(err && err.message) 31 | } 32 | } 33 | 34 | } 35 | 36 | let firebaseClient = new FirebaseClient(); 37 | export default firebaseClient; 38 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/app/FirebaseConstants.js: -------------------------------------------------------------------------------- 1 | 2 | const FirebaseConstants = { 3 | KEY: "YOUR_API_KEY" 4 | } 5 | 6 | export default FirebaseConstants; 7 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/app/Listeners.js: -------------------------------------------------------------------------------- 1 | import { Platform, AsyncStorage, AppState } from 'react-native'; 2 | 3 | import FCM, {FCMEvent, RemoteNotificationResult, WillPresentNotificationResult, NotificationType, NotificationActionType, NotificationActionOption, NotificationCategoryOption} from "react-native-fcm"; 4 | 5 | AsyncStorage.getItem('lastNotification').then(data=>{ 6 | if(data){ 7 | // if notification arrives when app is killed, it should still be logged here 8 | console.log('last notification', JSON.parse(data)); 9 | AsyncStorage.removeItem('lastNotification'); 10 | } 11 | }) 12 | 13 | AsyncStorage.getItem('lastMessage').then(data=>{ 14 | if(data){ 15 | // if notification arrives when app is killed, it should still be logged here 16 | console.log('last message', JSON.parse(data)); 17 | AsyncStorage.removeItem('lastMessage'); 18 | } 19 | }) 20 | 21 | export function registerKilledListener(){ 22 | // these callback will be triggered even when app is killed 23 | FCM.on(FCMEvent.Notification, notif => { 24 | AsyncStorage.setItem('lastNotification', JSON.stringify(notif)); 25 | if(notif.opened_from_tray){ 26 | setTimeout(()=>{ 27 | if(notif._actionIdentifier === 'reply'){ 28 | if(AppState.currentState !== 'background'){ 29 | console.log('User replied '+ JSON.stringify(notif._userText)) 30 | alert('User replied '+ JSON.stringify(notif._userText)); 31 | } else { 32 | AsyncStorage.setItem('lastMessage', JSON.stringify(notif._userText)); 33 | } 34 | } 35 | if(notif._actionIdentifier === 'view'){ 36 | alert("User clicked View in App"); 37 | } 38 | if(notif._actionIdentifier === 'dismiss'){ 39 | alert("User clicked Dismiss"); 40 | } 41 | }, 1000) 42 | } 43 | }); 44 | } 45 | 46 | // these callback will be triggered only when app is foreground or background 47 | export function registerAppListener(navigation){ 48 | FCM.on(FCMEvent.Notification, notif => { 49 | console.log("Notification", notif); 50 | 51 | if(Platform.OS ==='ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification){ 52 | // this notification is only to decide if you want to show the notification when user if in foreground. 53 | // usually you can ignore it. just decide to show or not. 54 | notif.finish(WillPresentNotificationResult.All) 55 | return; 56 | } 57 | 58 | if(notif.opened_from_tray){ 59 | if(notif.targetScreen === 'detail'){ 60 | setTimeout(()=>{ 61 | navigation.navigate('Detail') 62 | }, 500) 63 | } 64 | setTimeout(()=>{ 65 | alert(`User tapped notification\n${JSON.stringify(notif)}`) 66 | }, 500) 67 | } 68 | 69 | if(Platform.OS ==='ios'){ 70 | //optional 71 | //iOS requires developers to call completionHandler to end notification process. If you do not call it your background remote notifications could be throttled, to read more about it see the above documentation link. 72 | //This library handles it for you automatically with default behavior (for remote notification, finish with NoData; for WillPresent, finish depend on "show_in_foreground"). However if you want to return different result, follow the following code to override 73 | //notif._notificationType is available for iOS platfrom 74 | switch(notif._notificationType){ 75 | case NotificationType.Remote: 76 | notif.finish(RemoteNotificationResult.NewData) //other types available: RemoteNotificationResult.NewData, RemoteNotificationResult.ResultFailed 77 | break; 78 | case NotificationType.NotificationResponse: 79 | notif.finish(); 80 | break; 81 | case NotificationType.WillPresent: 82 | notif.finish(WillPresentNotificationResult.All) //other types available: WillPresentNotificationResult.None 83 | // this type of notificaiton will be called only when you are in foreground. 84 | // if it is a remote notification, don't do any app logic here. Another notification callback will be triggered with type NotificationType.Remote 85 | break; 86 | } 87 | } 88 | }); 89 | 90 | FCM.on(FCMEvent.RefreshToken, token => { 91 | console.log("TOKEN (refreshUnsubscribe)", token); 92 | }); 93 | 94 | FCM.enableDirectChannel(); 95 | FCM.on(FCMEvent.DirectChannelConnectionChanged, (data) => { 96 | console.log('direct channel connected' + data); 97 | }); 98 | setTimeout(function() { 99 | FCM.isDirectChannelEstablished().then(d => console.log(d)); 100 | }, 1000); 101 | } 102 | 103 | FCM.setNotificationCategories([ 104 | { 105 | id: 'com.myidentifi.fcm.text', 106 | actions: [ 107 | { 108 | type: NotificationActionType.TextInput, 109 | id: 'reply', 110 | title: 'Quick Reply', 111 | textInputButtonTitle: 'Send', 112 | textInputPlaceholder: 'Say something', 113 | intentIdentifiers: [], 114 | options: NotificationActionOption.AuthenticationRequired 115 | }, 116 | { 117 | type: NotificationActionType.Default, 118 | id: 'view', 119 | title: 'View in App', 120 | intentIdentifiers: [], 121 | options: NotificationActionOption.Foreground 122 | }, 123 | { 124 | type: NotificationActionType.Default, 125 | id: 'dismiss', 126 | title: 'Dismiss', 127 | intentIdentifiers: [], 128 | options: NotificationActionOption.Destructive 129 | } 130 | ], 131 | options: [NotificationCategoryOption.CustomDismissAction, NotificationCategoryOption.PreviewsShowTitle] 132 | } 133 | ]) 134 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { 9 | AppRegistry, 10 | StyleSheet, 11 | Text, 12 | View 13 | } from 'react-native'; 14 | 15 | import App from "./app/App"; 16 | 17 | export default class SimpleFcmClient extends Component { 18 | render() { 19 | return (); 20 | } 21 | } 22 | 23 | AppRegistry.registerComponent('SimpleFcmClient', () => SimpleFcmClient); 24 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/index.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { 9 | AppRegistry, 10 | StyleSheet, 11 | Text, 12 | View 13 | } from 'react-native'; 14 | 15 | import App from "./app/App"; 16 | 17 | export default class SimpleFcmClient extends Component { 18 | render() { 19 | return (); 20 | } 21 | } 22 | 23 | AppRegistry.registerComponent('SimpleFcmClient', () => SimpleFcmClient); 24 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | pod 'Firebase/Messaging' 5 | 6 | target 'SimpleFcmClient' do 7 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks 8 | # use_frameworks! 9 | 10 | # Pods for SimpleFcmClient 11 | 12 | target 'SimpleFcmClientTests' do 13 | inherit! :search_paths 14 | # Pods for testing 15 | end 16 | 17 | end 18 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Firebase/Core (4.9.0): 3 | - FirebaseAnalytics (= 4.0.9) 4 | - FirebaseCore (= 4.0.15) 5 | - Firebase/Messaging (4.9.0): 6 | - Firebase/Core 7 | - FirebaseMessaging (= 2.1.0) 8 | - FirebaseAnalytics (4.0.9): 9 | - FirebaseCore (~> 4.0) 10 | - FirebaseInstanceID (~> 2.0) 11 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 12 | - nanopb (~> 0.3) 13 | - FirebaseCore (4.0.15): 14 | - GoogleToolboxForMac/NSData+zlib (~> 2.1) 15 | - FirebaseInstanceID (2.0.9): 16 | - FirebaseCore (~> 4.0) 17 | - FirebaseMessaging (2.1.0): 18 | - FirebaseAnalytics (~> 4.0) 19 | - FirebaseCore (~> 4.0) 20 | - FirebaseInstanceID (~> 2.0) 21 | - GoogleToolboxForMac/Logger (~> 2.1) 22 | - Protobuf (~> 3.1) 23 | - GoogleToolboxForMac/Defines (2.1.3) 24 | - GoogleToolboxForMac/Logger (2.1.3): 25 | - GoogleToolboxForMac/Defines (= 2.1.3) 26 | - GoogleToolboxForMac/NSData+zlib (2.1.3): 27 | - GoogleToolboxForMac/Defines (= 2.1.3) 28 | - nanopb (0.3.8): 29 | - nanopb/decode (= 0.3.8) 30 | - nanopb/encode (= 0.3.8) 31 | - nanopb/decode (0.3.8) 32 | - nanopb/encode (0.3.8) 33 | - Protobuf (3.5.0) 34 | 35 | DEPENDENCIES: 36 | - Firebase/Messaging 37 | 38 | SPEC CHECKSUMS: 39 | Firebase: 632216af3ed7f31e3be34776947fdc7546cfb572 40 | FirebaseAnalytics: 388b630c15713f5dbf364071f5f3d6077fb52f4e 41 | FirebaseCore: 3bd047463058fa6b5d312c97502c52e45401cdfb 42 | FirebaseInstanceID: d2058a35e9bebda1b6dd42486b84917bde552a9d 43 | FirebaseMessaging: 2bafab2d0f3ab3dfd753101c2c32995c2051b5da 44 | GoogleToolboxForMac: 2501e2ad72a52eb3dfe7bd9aee7dad11b858bd20 45 | nanopb: 5601e6bca2dbf1ed831b519092ec110f66982ca3 46 | Protobuf: 8a9838fba8dae3389230e1b7f8c104aa32389c03 47 | 48 | PODFILE CHECKSUM: 31f07bb14b00eef65c77cff51721f530ad6eb826 49 | 50 | COCOAPODS: 1.4.0 51 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient.xcodeproj/xcshareddata/xcschemes/SimpleFcmClient.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 62 | 68 | 69 | 70 | 71 | 72 | 78 | 79 | 80 | 81 | 82 | 83 | 94 | 96 | 102 | 103 | 104 | 105 | 106 | 107 | 113 | 115 | 121 | 122 | 123 | 124 | 126 | 127 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @import UserNotifications; 13 | 14 | @interface AppDelegate : UIResponder 15 | 16 | @property (nonatomic, strong) UIWindow *window; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import 13 | #import "RCTRootView.h" 14 | 15 | #import "RNFIRMessaging.h" 16 | 17 | @implementation AppDelegate 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 20 | { 21 | NSURL *jsCodeLocation; 22 | 23 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 24 | 25 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 26 | moduleName:@"SimpleFcmClient" 27 | initialProperties:nil 28 | launchOptions:launchOptions]; 29 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 30 | 31 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 32 | UIViewController *rootViewController = [UIViewController new]; 33 | rootViewController.view = rootView; 34 | self.window.rootViewController = rootViewController; 35 | [self.window makeKeyAndVisible]; 36 | 37 | [FIRApp configure]; 38 | [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; 39 | 40 | return YES; 41 | } 42 | 43 | - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler 44 | { 45 | [RNFIRMessaging willPresentNotification:notification withCompletionHandler:completionHandler]; 46 | } 47 | 48 | #if defined(__IPHONE_11_0) 49 | - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler 50 | { 51 | [RNFIRMessaging didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; 52 | } 53 | #else 54 | - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler 55 | { 56 | [RNFIRMessaging didReceiveNotificationResponse:response withCompletionHandler:completionHandler]; 57 | } 58 | #endif 59 | 60 | -(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { 61 | [RNFIRMessaging didReceiveLocalNotification:notification]; 62 | } 63 | 64 | - (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{ 65 | [RNFIRMessaging didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Simple Fcm Client 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSTemporaryExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UIBackgroundModes 41 | 42 | remote-notification 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIRequiredDeviceCapabilities 47 | 48 | armv7 49 | 50 | UISupportedInterfaceOrientations 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationLandscapeLeft 54 | UIInterfaceOrientationLandscapeRight 55 | 56 | UIViewControllerBasedStatusBarAppearance 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/SimpleFcmClient.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClient/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClientTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/SimpleFcmClientTests/SimpleFcmClientTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface SimpleFcmClientTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation SimpleFcmClientTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /Examples/simple-fcm-client/ios/bell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/Examples/simple-fcm-client/ios/bell.mp3 -------------------------------------------------------------------------------- /Examples/simple-fcm-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SimpleFcmClient", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest" 8 | }, 9 | "dependencies": { 10 | "react": "16.0.0-alpha.12", 11 | "react-native": "^0.55.4", 12 | "react-native-fcm": "^16.2.3", 13 | "react-native-maps": "^0.20.1", 14 | "react-navigation": "^1.2.1" 15 | }, 16 | "jest": { 17 | "preset": "jest-react-native" 18 | }, 19 | "devDependencies": { 20 | "babel-jest": "16.0.0", 21 | "babel-preset-react-native": "1.9.0", 22 | "jest": "16.0.1", 23 | "jest-react-native": "16.0.0", 24 | "react-test-renderer": "15.3.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE: -------------------------------------------------------------------------------- 1 | Before openning an issue 2 | 1. Reproduce it with the example project in this repo. 3 | 2. If you can't receive iOS notification, make sure you can receive notification using quickstart-ios project provided by Firebase team 4 | 5 | When openning an issue, please include following information for better support 6 | 7 | 1. What version of RN and react-native-fcm are you running? 8 | 2. What device are you using? (e.g iOS9 emulator, Android 6 device)? 9 | 3. Is your app running in foreground, background or not running? 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Howard Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def DEFAULT_COMPILE_SDK_VERSION = 28 4 | def DEFAULT_BUILD_TOOLS_VERSION = "28.0.2" 5 | def DEFAULT_TARGET_SDK_VERSION = 28 6 | def DEFAULT_GOOGLE_PLAY_SERVICES_VERSION = "+" 7 | def DEFAULT_FIREBASE_CORE_VERSION = "+" 8 | def DEFAULT_FIREBASE_MESSAGING_VERSION = "+" 9 | 10 | android { 11 | compileSdkVersion project.hasProperty('compileSdkVersion') ? project.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION 12 | buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION 13 | 14 | defaultConfig { 15 | minSdkVersion 16 16 | targetSdkVersion project.hasProperty('targetSdkVersion') ? project.targetSdkVersion : DEFAULT_TARGET_SDK_VERSION 17 | versionCode 1 18 | versionName "1.0" 19 | } 20 | } 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | 26 | dependencies { 27 | def googlePlayServicesVersion = project.hasProperty('googlePlayServicesVersion') ? project.googlePlayServicesVersion : DEFAULT_GOOGLE_PLAY_SERVICES_VERSION 28 | def firebaseCoreVersion = project.hasProperty('firebaseCoreVersion') ? project.firebaseCoreVersion : DEFAULT_FIREBASE_CORE_VERSION 29 | def firebaseMessagingVersion = project.hasProperty('firebaseMessagingVersion') ? project.firebaseMessagingVersion : DEFAULT_FIREBASE_MESSAGING_VERSION 30 | 31 | compile fileTree(include: ['*.jar'], dir: 'libs') 32 | compile 'com.facebook.react:react-native:+' 33 | compile "com.google.firebase:firebase-core:$firebaseCoreVersion" 34 | compile "com.google.firebase:firebase-messaging:$firebaseMessagingVersion" 35 | compile 'me.leolin:ShortcutBadger:1.1.17@aar' 36 | compile "com.android.support:support-core-utils:28.0.0" 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/BadgeHelper.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.util.Log; 6 | 7 | import me.leolin.shortcutbadger.ShortcutBadger; 8 | 9 | public class BadgeHelper { 10 | 11 | private static final String TAG = "BadgeHelper"; 12 | private static final String PREFERENCES_FILE = "BadgeCountFile"; 13 | private static final String BADGE_COUNT_KEY = "BadgeCount"; 14 | 15 | private Context mContext; 16 | private SharedPreferences sharedPreferences = null; 17 | 18 | public BadgeHelper(Context context) { 19 | mContext = context; 20 | sharedPreferences = (SharedPreferences) mContext.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); 21 | } 22 | 23 | public int getBadgeCount() { 24 | return sharedPreferences.getInt(BADGE_COUNT_KEY, 0); 25 | } 26 | 27 | public void setBadgeCount(int badgeCount) { 28 | storeBadgeCount(badgeCount); 29 | if (badgeCount == 0) { 30 | ShortcutBadger.removeCount(mContext); 31 | Log.d(TAG, "Remove count"); 32 | } else { 33 | ShortcutBadger.applyCount(mContext, badgeCount); 34 | Log.d(TAG, "Apply count: " + badgeCount); 35 | } 36 | } 37 | 38 | private void storeBadgeCount(int badgeCount) { 39 | SharedPreferences.Editor editor = sharedPreferences.edit(); 40 | editor.putInt(BADGE_COUNT_KEY, badgeCount); 41 | editor.apply(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/BundleJSONConverter.java: -------------------------------------------------------------------------------- 1 | //steal from https://github.com/facebook/facebook-android-sdk/blob/master/facebook/src/main/java/com/facebook/internal/BundleJSONConverter.java 2 | 3 | /** 4 | * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. 5 | * 6 | * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, 7 | * copy, modify, and distribute this software in source code or binary form for use 8 | * in connection with the web services and APIs provided by Facebook. 9 | * 10 | * As with any software that integrates with the Facebook platform, your use of 11 | * this software is subject to the Facebook Developer Principles and Policies 12 | * [http://developers.facebook.com/policy/]. This copyright notice shall be 13 | * included in all copies or substantial portions of the software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | package com.evollu.react.fcm; 24 | 25 | import android.os.Bundle; 26 | import org.json.JSONArray; 27 | import org.json.JSONException; 28 | import org.json.JSONObject; 29 | 30 | import java.util.*; 31 | 32 | /** 33 | * com.facebook.internal is solely for the use of other packages within the Facebook SDK for 34 | * Android. Use of any of the classes in this package is unsupported, and they may be modified or 35 | * removed without warning at any time. 36 | * 37 | * A helper class that can round trip between JSON and Bundle objects that contains the types: 38 | * Boolean, Integer, Long, Double, String 39 | * If other types are found, an IllegalArgumentException is thrown. 40 | */ 41 | public class BundleJSONConverter { 42 | private static final Map, Setter> SETTERS = new HashMap, Setter>(); 43 | 44 | static { 45 | SETTERS.put(Boolean.class, new Setter() { 46 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 47 | bundle.putBoolean(key, (Boolean) value); 48 | } 49 | 50 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 51 | json.put(key, value); 52 | } 53 | }); 54 | SETTERS.put(Integer.class, new Setter() { 55 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 56 | bundle.putInt(key, (Integer) value); 57 | } 58 | 59 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 60 | json.put(key, value); 61 | } 62 | }); 63 | SETTERS.put(Long.class, new Setter() { 64 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 65 | bundle.putLong(key, (Long) value); 66 | } 67 | 68 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 69 | json.put(key, value); 70 | } 71 | }); 72 | SETTERS.put(Double.class, new Setter() { 73 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 74 | bundle.putDouble(key, (Double) value); 75 | } 76 | 77 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 78 | json.put(key, value); 79 | } 80 | }); 81 | SETTERS.put(String.class, new Setter() { 82 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 83 | bundle.putString(key, (String) value); 84 | } 85 | 86 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 87 | json.put(key, value); 88 | } 89 | }); 90 | SETTERS.put(String[].class, new Setter() { 91 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 92 | throw new IllegalArgumentException("Unexpected type from JSON"); 93 | } 94 | 95 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 96 | JSONArray jsonArray = new JSONArray(); 97 | for (String stringValue : (String[])value) { 98 | jsonArray.put(stringValue); 99 | } 100 | json.put(key, jsonArray); 101 | } 102 | }); 103 | 104 | SETTERS.put(JSONArray.class, new Setter() { 105 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException { 106 | JSONArray jsonArray = (JSONArray)value; 107 | ArrayList stringArrayList = new ArrayList(); 108 | // Empty list, can't even figure out the type, assume an ArrayList 109 | if (jsonArray.length() == 0) { 110 | bundle.putStringArrayList(key, stringArrayList); 111 | return; 112 | } 113 | 114 | // Only strings are supported for now 115 | for (int i = 0; i < jsonArray.length(); i++) { 116 | Object current = jsonArray.get(i); 117 | if (current instanceof String) { 118 | stringArrayList.add((String)current); 119 | } else { 120 | throw new IllegalArgumentException("Unexpected type in an array: " + current.getClass()); 121 | } 122 | } 123 | bundle.putStringArrayList(key, stringArrayList); 124 | } 125 | 126 | @Override 127 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException { 128 | throw new IllegalArgumentException("JSONArray's are not supported in bundles."); 129 | } 130 | }); 131 | } 132 | 133 | public interface Setter { 134 | public void setOnBundle(Bundle bundle, String key, Object value) throws JSONException; 135 | public void setOnJSON(JSONObject json, String key, Object value) throws JSONException; 136 | } 137 | 138 | public static JSONObject convertToJSON(Bundle bundle) throws JSONException { 139 | JSONObject json = new JSONObject(); 140 | 141 | for(String key : bundle.keySet()) { 142 | Object value = bundle.get(key); 143 | if (value == null) { 144 | // Null is not supported. 145 | continue; 146 | } 147 | 148 | // Special case List as getClass would not work, since List is an interface 149 | if (value instanceof List) { 150 | JSONArray jsonArray = new JSONArray(); 151 | @SuppressWarnings("unchecked") 152 | List listValue = (List)value; 153 | for (String stringValue : listValue) { 154 | jsonArray.put(stringValue); 155 | } 156 | json.put(key, jsonArray); 157 | continue; 158 | } 159 | 160 | // Special case Bundle as it's one way, on the return it will be JSONObject 161 | if (value instanceof Bundle) { 162 | json.put(key, convertToJSON((Bundle)value)); 163 | continue; 164 | } 165 | 166 | Setter setter = SETTERS.get(value.getClass()); 167 | if (setter == null) { 168 | throw new IllegalArgumentException("Unsupported type: " + value.getClass()); 169 | } 170 | setter.setOnJSON(json, key, value); 171 | } 172 | 173 | return json; 174 | } 175 | 176 | public static Bundle convertToBundle(JSONObject jsonObject) throws JSONException { 177 | Bundle bundle = new Bundle(); 178 | @SuppressWarnings("unchecked") 179 | Iterator jsonIterator = jsonObject.keys(); 180 | while (jsonIterator.hasNext()) { 181 | String key = jsonIterator.next(); 182 | Object value = jsonObject.get(key); 183 | if (value == null || value == JSONObject.NULL) { 184 | // Null is not supported. 185 | continue; 186 | } 187 | 188 | // Special case JSONObject as it's one way, on the return it would be Bundle. 189 | if (value instanceof JSONObject) { 190 | bundle.putBundle(key, convertToBundle((JSONObject)value)); 191 | continue; 192 | } 193 | 194 | Setter setter = SETTERS.get(value.getClass()); 195 | if (setter == null) { 196 | throw new IllegalArgumentException("Unsupported type: " + value.getClass()); 197 | } 198 | setter.setOnBundle(bundle, key, value); 199 | } 200 | 201 | return bundle; 202 | } 203 | } -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/FIRLocalMessagingHelper.java: -------------------------------------------------------------------------------- 1 | //Credits to react-native-push-notification 2 | 3 | package com.evollu.react.fcm; 4 | 5 | import android.app.AlarmManager; 6 | import android.app.Application; 7 | import android.app.NotificationManager; 8 | import android.app.PendingIntent; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.SharedPreferences; 12 | import android.os.Build; 13 | import android.os.Bundle; 14 | import android.util.Log; 15 | 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import java.util.ArrayList; 20 | 21 | public class FIRLocalMessagingHelper { 22 | private static final String TAG = FIRLocalMessagingHelper.class.getSimpleName(); 23 | private final static String PREFERENCES_KEY = "ReactNativeSystemNotification"; 24 | private static boolean mIsForeground = false; //this is a hack 25 | 26 | private Context mContext; 27 | private SharedPreferences sharedPreferences = null; 28 | 29 | public FIRLocalMessagingHelper(Application context) { 30 | mContext = context; 31 | sharedPreferences = (SharedPreferences) mContext.getSharedPreferences(PREFERENCES_KEY, Context.MODE_PRIVATE); 32 | } 33 | 34 | public String getMainActivityClassName() { 35 | String packageName = mContext.getPackageName(); 36 | Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); 37 | String className = launchIntent.getComponent().getClassName(); 38 | return className; 39 | } 40 | 41 | private AlarmManager getAlarmManager() { 42 | return (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 43 | } 44 | 45 | public void sendNotification(Bundle bundle) { 46 | new SendNotificationTask(mContext, sharedPreferences, mIsForeground, bundle).execute(); 47 | } 48 | 49 | public void sendNotificationScheduled(Bundle bundle) { 50 | String intentClassName = getMainActivityClassName(); 51 | if (intentClassName == null) { 52 | return; 53 | } 54 | 55 | String notificationId = bundle.getString("id"); 56 | if(notificationId == null){ 57 | Log.e(TAG, "failed to schedule notification because id is missing"); 58 | return; 59 | } 60 | 61 | Long fireDate = bundle.getLong("fire_date", -1); 62 | if (fireDate == -1) { 63 | fireDate = (long) bundle.getDouble("fire_date", -1); 64 | } 65 | if (fireDate == -1) { 66 | Log.e(TAG, "failed to schedule notification because fire date is missing"); 67 | return; 68 | } 69 | 70 | Intent notificationIntent = new Intent(mContext, FIRLocalMessagingPublisher.class); 71 | notificationIntent.putExtras(bundle); 72 | PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 73 | 74 | Long interval = null; 75 | switch (bundle.getString("repeat_interval", "")) { 76 | case "minute": 77 | interval = (long) 60000; 78 | break; 79 | case "hour": 80 | interval = AlarmManager.INTERVAL_HOUR; 81 | break; 82 | case "day": 83 | interval = AlarmManager.INTERVAL_DAY; 84 | break; 85 | case "week": 86 | interval = AlarmManager.INTERVAL_DAY * 7; 87 | break; 88 | } 89 | 90 | if(interval != null){ 91 | getAlarmManager().setRepeating(AlarmManager.RTC_WAKEUP, fireDate, interval, pendingIntent); 92 | } else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){ 93 | getAlarmManager().setExact(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); 94 | }else { 95 | getAlarmManager().set(AlarmManager.RTC_WAKEUP, fireDate, pendingIntent); 96 | } 97 | 98 | //store intent 99 | SharedPreferences.Editor editor = sharedPreferences.edit(); 100 | try { 101 | JSONObject json = BundleJSONConverter.convertToJSON(bundle); 102 | editor.putString(notificationId, json.toString()); 103 | editor.apply(); 104 | } catch (JSONException e) { 105 | e.printStackTrace(); 106 | } 107 | } 108 | 109 | public void cancelLocalNotification(String notificationId) { 110 | cancelAlarm(notificationId); 111 | SharedPreferences.Editor editor = sharedPreferences.edit(); 112 | editor.remove(notificationId); 113 | editor.apply(); 114 | } 115 | 116 | public void cancelAllLocalNotifications() { 117 | java.util.Map keyMap = sharedPreferences.getAll(); 118 | SharedPreferences.Editor editor = sharedPreferences.edit(); 119 | for(java.util.Map.Entry entry:keyMap.entrySet()){ 120 | cancelAlarm(entry.getKey()); 121 | } 122 | editor.clear(); 123 | editor.apply(); 124 | } 125 | 126 | public void removeDeliveredNotification(String notificationId){ 127 | NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 128 | notificationManager.cancel(notificationId.hashCode()); 129 | } 130 | 131 | public void removeAllDeliveredNotifications(){ 132 | NotificationManager notificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); 133 | notificationManager.cancelAll(); 134 | } 135 | 136 | public void cancelAlarm(String notificationId) { 137 | Intent notificationIntent = new Intent(mContext, FIRLocalMessagingPublisher.class); 138 | PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, notificationId.hashCode(), notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); 139 | getAlarmManager().cancel(pendingIntent); 140 | } 141 | 142 | public ArrayList getScheduledLocalNotifications(){ 143 | ArrayList array = new ArrayList(); 144 | java.util.Map keyMap = sharedPreferences.getAll(); 145 | for(java.util.Map.Entry entry:keyMap.entrySet()){ 146 | try { 147 | JSONObject json = new JSONObject((String)entry.getValue()); 148 | Bundle bundle = BundleJSONConverter.convertToBundle(json); 149 | array.add(bundle); 150 | } catch (JSONException e) { 151 | e.printStackTrace(); 152 | } 153 | } 154 | return array; 155 | } 156 | 157 | public void setApplicationForeground(boolean foreground){ 158 | mIsForeground = foreground; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/FIRLocalMessagingPublisher.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.app.Application; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | 8 | public class FIRLocalMessagingPublisher extends BroadcastReceiver { 9 | 10 | @Override 11 | public void onReceive(Context context, Intent intent) { 12 | new FIRLocalMessagingHelper((Application) context.getApplicationContext()).sendNotification(intent.getExtras()); 13 | } 14 | } -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/FIRMessagingModule.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.app.Activity; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.content.BroadcastReceiver; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | 10 | import com.facebook.react.bridge.ActivityEventListener; 11 | import com.facebook.react.bridge.Arguments; 12 | import com.facebook.react.bridge.LifecycleEventListener; 13 | import com.facebook.react.bridge.Promise; 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 16 | import com.facebook.react.bridge.ReactMethod; 17 | import com.facebook.react.bridge.ReadableMap; 18 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 19 | import com.facebook.react.bridge.WritableArray; 20 | import com.facebook.react.bridge.WritableMap; 21 | import com.facebook.react.modules.core.DeviceEventManagerModule; 22 | import com.google.firebase.iid.FirebaseInstanceId; 23 | import com.google.firebase.messaging.FirebaseMessaging; 24 | import com.google.firebase.messaging.RemoteMessage; 25 | import com.google.firebase.messaging.RemoteMessage.Notification; 26 | 27 | import android.app.Application; 28 | import android.os.Build; 29 | import android.os.Bundle; 30 | import android.support.v4.app.NotificationManagerCompat; 31 | import android.support.v4.content.LocalBroadcastManager; 32 | import android.util.Log; 33 | 34 | import android.content.Context; 35 | import java.io.IOException; 36 | import java.util.ArrayList; 37 | import java.util.Map; 38 | import java.util.Set; 39 | import java.util.UUID; 40 | import com.google.firebase.FirebaseApp; 41 | 42 | import static android.content.Context.NOTIFICATION_SERVICE; 43 | 44 | public class FIRMessagingModule extends ReactContextBaseJavaModule implements LifecycleEventListener, ActivityEventListener { 45 | private final static String TAG = FIRMessagingModule.class.getCanonicalName(); 46 | private FIRLocalMessagingHelper mFIRLocalMessagingHelper; 47 | private BadgeHelper mBadgeHelper; 48 | 49 | public FIRMessagingModule(ReactApplicationContext reactContext) { 50 | super(reactContext); 51 | mFIRLocalMessagingHelper = new FIRLocalMessagingHelper((Application) reactContext.getApplicationContext()); 52 | mBadgeHelper = new BadgeHelper(reactContext.getApplicationContext()); 53 | getReactApplicationContext().addLifecycleEventListener(this); 54 | getReactApplicationContext().addActivityEventListener(this); 55 | registerTokenRefreshHandler(); 56 | registerMessageHandler(); 57 | registerLocalMessageHandler(); 58 | } 59 | 60 | @Override 61 | public String getName() { 62 | return "RNFIRMessaging"; 63 | } 64 | 65 | @ReactMethod 66 | public void getInitialNotification(Promise promise){ 67 | Activity activity = getCurrentActivity(); 68 | if(activity == null){ 69 | promise.resolve(null); 70 | return; 71 | } 72 | promise.resolve(parseIntent(activity.getIntent())); 73 | } 74 | 75 | @ReactMethod 76 | public void requestPermissions(Promise promise){ 77 | if(NotificationManagerCompat.from(getReactApplicationContext()).areNotificationsEnabled()){ 78 | promise.resolve(true); 79 | } else { 80 | promise.reject(null, "Notification disabled"); 81 | } 82 | } 83 | 84 | @ReactMethod 85 | public void createNotificationChannel(ReadableMap details, Promise promise){ 86 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 87 | NotificationManager mngr = (NotificationManager) getReactApplicationContext().getSystemService(NOTIFICATION_SERVICE); 88 | String id = details.getString("id"); 89 | String name = details.getString("name"); 90 | String priority = details.getString("priority"); 91 | int importance; 92 | switch(priority) { 93 | case "min": 94 | importance = NotificationManager.IMPORTANCE_MIN; 95 | break; 96 | case "low": 97 | importance = NotificationManager.IMPORTANCE_LOW; 98 | break; 99 | case "high": 100 | importance = NotificationManager.IMPORTANCE_HIGH; 101 | break; 102 | case "max": 103 | importance = NotificationManager.IMPORTANCE_MAX; 104 | break; 105 | default: 106 | importance = NotificationManager.IMPORTANCE_DEFAULT; 107 | } 108 | if (mngr.getNotificationChannel(id) != null) { 109 | promise.resolve(null); 110 | return; 111 | } 112 | // 113 | NotificationChannel channel = new NotificationChannel( 114 | id, 115 | name, 116 | importance); 117 | // Configure the notification channel. 118 | if(details.hasKey("description")){ 119 | channel.setDescription(details.getString("description")); 120 | } 121 | mngr.createNotificationChannel(channel); 122 | } 123 | promise.resolve(null); 124 | } 125 | 126 | @ReactMethod 127 | public void deleteNotificationChannel(String id, Promise promise) { 128 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 129 | NotificationManager mngr = (NotificationManager) getReactApplicationContext().getSystemService(NOTIFICATION_SERVICE); 130 | mngr.deleteNotificationChannel(id); 131 | } 132 | promise.resolve(null); 133 | } 134 | 135 | @ReactMethod 136 | public void getFCMToken(Promise promise) { 137 | try { 138 | Log.d(TAG, "Firebase token: " + FirebaseInstanceId.getInstance().getToken()); 139 | promise.resolve(FirebaseInstanceId.getInstance().getToken()); 140 | } catch (Throwable e) { 141 | e.printStackTrace(); 142 | promise.reject(null,e.getMessage()); 143 | } 144 | } 145 | 146 | @ReactMethod 147 | public void getEntityFCMToken(Promise promise) { 148 | try { 149 | String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId(); 150 | String token = FirebaseInstanceId.getInstance().getToken(senderId, "FCM"); 151 | Log.d(TAG, "Firebase token: " + token); 152 | promise.resolve(token); 153 | } catch (Throwable e) { 154 | e.printStackTrace(); 155 | promise.reject(null,e.getMessage()); 156 | } 157 | } 158 | 159 | @ReactMethod 160 | public void deleteEntityFCMToken(Promise promise) { 161 | try { 162 | String senderId = FirebaseApp.getInstance().getOptions().getGcmSenderId(); 163 | FirebaseInstanceId.getInstance().deleteToken(senderId, "FCM"); 164 | promise.resolve(null); 165 | } catch (Throwable e) { 166 | e.printStackTrace(); 167 | promise.reject(null,e.getMessage()); 168 | } 169 | } 170 | 171 | @ReactMethod 172 | public void deleteInstanceId(Promise promise){ 173 | try { 174 | FirebaseInstanceId.getInstance().deleteInstanceId(); 175 | promise.resolve(null); 176 | } catch (Exception e) { 177 | e.printStackTrace(); 178 | promise.reject(null,e.getMessage()); 179 | } 180 | } 181 | 182 | @ReactMethod 183 | public void presentLocalNotification(ReadableMap details) { 184 | Bundle bundle = Arguments.toBundle(details); 185 | mFIRLocalMessagingHelper.sendNotification(bundle); 186 | } 187 | 188 | @ReactMethod 189 | public void scheduleLocalNotification(ReadableMap details) { 190 | Bundle bundle = Arguments.toBundle(details); 191 | mFIRLocalMessagingHelper.sendNotificationScheduled(bundle); 192 | } 193 | 194 | @ReactMethod 195 | public void cancelLocalNotification(String notificationID) { 196 | mFIRLocalMessagingHelper.cancelLocalNotification(notificationID); 197 | } 198 | @ReactMethod 199 | public void cancelAllLocalNotifications() { 200 | mFIRLocalMessagingHelper.cancelAllLocalNotifications(); 201 | } 202 | 203 | @ReactMethod 204 | public void removeDeliveredNotification(String notificationID) { 205 | mFIRLocalMessagingHelper.removeDeliveredNotification(notificationID); 206 | } 207 | 208 | @ReactMethod 209 | public void removeAllDeliveredNotifications(){ 210 | mFIRLocalMessagingHelper.removeAllDeliveredNotifications(); 211 | } 212 | 213 | @ReactMethod 214 | public void subscribeToTopic(String topic, Promise promise){ 215 | try { 216 | FirebaseMessaging.getInstance().subscribeToTopic(topic); 217 | promise.resolve(null); 218 | } catch (Exception e) { 219 | e.printStackTrace(); 220 | promise.reject(null,e.getMessage()); 221 | } 222 | } 223 | 224 | @ReactMethod 225 | public void unsubscribeFromTopic(String topic, Promise promise){ 226 | try { 227 | FirebaseMessaging.getInstance().unsubscribeFromTopic(topic); 228 | promise.resolve(null); 229 | } catch (Exception e) { 230 | e.printStackTrace(); 231 | promise.reject(null,e.getMessage()); 232 | } 233 | } 234 | 235 | @ReactMethod 236 | public void getScheduledLocalNotifications(Promise promise){ 237 | ArrayList bundles = mFIRLocalMessagingHelper.getScheduledLocalNotifications(); 238 | WritableArray array = Arguments.createArray(); 239 | for(Bundle bundle:bundles){ 240 | array.pushMap(Arguments.fromBundle(bundle)); 241 | } 242 | promise.resolve(array); 243 | } 244 | 245 | @ReactMethod 246 | public void setBadgeNumber(int badgeNumber) { 247 | mBadgeHelper.setBadgeCount(badgeNumber); 248 | } 249 | 250 | @ReactMethod 251 | public void getBadgeNumber(Promise promise) { 252 | promise.resolve(mBadgeHelper.getBadgeCount()); 253 | } 254 | 255 | private void sendEvent(String eventName, Object params) { 256 | getReactApplicationContext() 257 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 258 | .emit(eventName, params); 259 | } 260 | 261 | private void registerTokenRefreshHandler() { 262 | IntentFilter intentFilter = new IntentFilter("com.evollu.react.fcm.FCMRefreshToken"); 263 | LocalBroadcastManager.getInstance(getReactApplicationContext()).registerReceiver(new BroadcastReceiver() { 264 | @Override 265 | public void onReceive(Context context, Intent intent) { 266 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 267 | String token = intent.getStringExtra("token"); 268 | sendEvent("FCMTokenRefreshed", token); 269 | } 270 | } 271 | }, intentFilter); 272 | } 273 | 274 | @ReactMethod 275 | public void send(String senderId, ReadableMap payload) throws Exception { 276 | FirebaseMessaging fm = FirebaseMessaging.getInstance(); 277 | RemoteMessage.Builder message = new RemoteMessage.Builder(senderId + "@gcm.googleapis.com") 278 | .setMessageId(UUID.randomUUID().toString()); 279 | 280 | ReadableMapKeySetIterator iterator = payload.keySetIterator(); 281 | while (iterator.hasNextKey()) { 282 | String key = iterator.nextKey(); 283 | String value = getStringFromReadableMap(payload, key); 284 | message.addData(key, value); 285 | } 286 | fm.send(message.build()); 287 | } 288 | 289 | private String getStringFromReadableMap(ReadableMap map, String key) throws Exception { 290 | switch (map.getType(key)) { 291 | case String: 292 | return map.getString(key); 293 | case Number: 294 | try { 295 | return String.valueOf(map.getInt(key)); 296 | } catch (Exception e) { 297 | return String.valueOf(map.getDouble(key)); 298 | } 299 | case Boolean: 300 | return String.valueOf(map.getBoolean(key)); 301 | default: 302 | throw new Exception("Unknown data type: " + map.getType(key).name() + " for message key " + key ); 303 | } 304 | } 305 | 306 | private void registerMessageHandler() { 307 | IntentFilter intentFilter = new IntentFilter("com.evollu.react.fcm.ReceiveNotification"); 308 | 309 | LocalBroadcastManager.getInstance(getReactApplicationContext()).registerReceiver(new BroadcastReceiver() { 310 | @Override 311 | public void onReceive(Context context, Intent intent) { 312 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 313 | RemoteMessage message = intent.getParcelableExtra("data"); 314 | WritableMap params = Arguments.createMap(); 315 | WritableMap fcmData = Arguments.createMap(); 316 | 317 | if (message.getNotification() != null) { 318 | Notification notification = message.getNotification(); 319 | fcmData.putString("title", notification.getTitle()); 320 | fcmData.putString("body", notification.getBody()); 321 | fcmData.putString("color", notification.getColor()); 322 | fcmData.putString("icon", notification.getIcon()); 323 | fcmData.putString("tag", notification.getTag()); 324 | fcmData.putString("action", notification.getClickAction()); 325 | } 326 | params.putMap("fcm", fcmData); 327 | params.putString("collapse_key", message.getCollapseKey()); 328 | params.putString("from", message.getFrom()); 329 | params.putString("google.message_id", message.getMessageId()); 330 | params.putDouble("google.sent_time", message.getSentTime()); 331 | 332 | if(message.getData() != null){ 333 | Map data = message.getData(); 334 | Set keysIterator = data.keySet(); 335 | for(String key: keysIterator){ 336 | params.putString(key, data.get(key)); 337 | } 338 | } 339 | sendEvent("FCMNotificationReceived", params); 340 | 341 | } 342 | } 343 | }, intentFilter); 344 | } 345 | 346 | private void registerLocalMessageHandler() { 347 | IntentFilter intentFilter = new IntentFilter("com.evollu.react.fcm.ReceiveLocalNotification"); 348 | 349 | LocalBroadcastManager.getInstance(getReactApplicationContext()).registerReceiver(new BroadcastReceiver() { 350 | @Override 351 | public void onReceive(Context context, Intent intent) { 352 | if (getReactApplicationContext().hasActiveCatalystInstance()) { 353 | sendEvent("FCMNotificationReceived", Arguments.fromBundle(intent.getExtras())); 354 | } 355 | } 356 | }, intentFilter); 357 | } 358 | 359 | private WritableMap parseIntent(Intent intent){ 360 | WritableMap params; 361 | Bundle extras = intent.getExtras(); 362 | if (extras != null) { 363 | try { 364 | params = Arguments.fromBundle(extras); 365 | } catch (Exception e){ 366 | Log.e(TAG, e.getMessage()); 367 | params = Arguments.createMap(); 368 | } 369 | } else { 370 | params = Arguments.createMap(); 371 | } 372 | WritableMap fcm = Arguments.createMap(); 373 | fcm.putString("action", intent.getAction()); 374 | params.putMap("fcm", fcm); 375 | 376 | params.putInt("opened_from_tray", 1); 377 | return params; 378 | } 379 | 380 | @Override 381 | public void onHostResume() { 382 | mFIRLocalMessagingHelper.setApplicationForeground(true); 383 | } 384 | 385 | @Override 386 | public void onHostPause() { 387 | mFIRLocalMessagingHelper.setApplicationForeground(false); 388 | } 389 | 390 | @Override 391 | public void onHostDestroy() { 392 | 393 | } 394 | 395 | @Override 396 | public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { 397 | } 398 | 399 | @Override 400 | public void onNewIntent(Intent intent){ 401 | sendEvent("FCMNotificationReceived", parseIntent(intent)); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/FIRMessagingPackage.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.Collections; 11 | import java.util.List; 12 | 13 | public class FIRMessagingPackage implements ReactPackage { 14 | 15 | public FIRMessagingPackage(){ 16 | } 17 | 18 | @Override 19 | public List createNativeModules(ReactApplicationContext reactContext) { 20 | List modules = new ArrayList<>(); 21 | 22 | modules.add(new FIRMessagingModule(reactContext)); 23 | return modules; 24 | } 25 | 26 | // support rn < 0.47 27 | public List> createJSModules() { 28 | return Collections.emptyList(); 29 | } 30 | 31 | @Override 32 | public List createViewManagers(ReactApplicationContext reactContext) { 33 | return Arrays.asList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/FIRSystemBootEventReceiver.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.app.Application; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | 8 | import java.util.ArrayList; 9 | 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | 13 | /** 14 | * Set alarms for scheduled notification after system reboot. 15 | */ 16 | public class FIRSystemBootEventReceiver extends BroadcastReceiver { 17 | 18 | @Override 19 | public void onReceive(Context context, Intent intent) { 20 | Log.i("FCMSystemBootReceiver", "Received reboot event"); 21 | FIRLocalMessagingHelper helper = new FIRLocalMessagingHelper((Application) context.getApplicationContext()); 22 | ArrayList bundles = helper.getScheduledLocalNotifications(); 23 | for(Bundle bundle: bundles){ 24 | helper.sendNotificationScheduled(bundle); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/InstanceIdService.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.support.v4.content.LocalBroadcastManager; 8 | import android.util.Log; 9 | 10 | import com.facebook.react.ReactApplication; 11 | import com.facebook.react.ReactInstanceManager; 12 | import com.facebook.react.bridge.ReactContext; 13 | import com.google.firebase.iid.FirebaseInstanceId; 14 | import com.google.firebase.iid.FirebaseInstanceIdService; 15 | 16 | public class InstanceIdService extends FirebaseInstanceIdService { 17 | 18 | private static final String TAG = "InstanceIdService"; 19 | 20 | /** 21 | * Called if InstanceID token is updated. This may occur if the security of 22 | * the previous token had been compromised. This call is initiated by the 23 | * InstanceID provider. 24 | */ 25 | // [START refresh_token] 26 | @Override 27 | public void onTokenRefresh() { 28 | // Get updated InstanceID token. 29 | String refreshedToken = FirebaseInstanceId.getInstance().getToken(); 30 | Log.d(TAG, "Refreshed token: " + refreshedToken); 31 | 32 | // Broadcast refreshed token 33 | Intent i = new Intent("com.evollu.react.fcm.FCMRefreshToken"); 34 | Bundle bundle = new Bundle(); 35 | bundle.putString("token", refreshedToken); 36 | i.putExtras(bundle); 37 | 38 | final Intent message = i; 39 | 40 | Handler handler = new Handler(Looper.getMainLooper()); 41 | handler.post(new Runnable() { 42 | public void run() { 43 | // Construct and load our normal React JS code bundle 44 | ReactInstanceManager mReactInstanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager(); 45 | ReactContext context = mReactInstanceManager.getCurrentReactContext(); 46 | // If it's constructed, send a notification 47 | if (context != null) { 48 | LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message); 49 | } else { 50 | // Otherwise wait for construction, then send the notification 51 | mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { 52 | public void onReactContextInitialized(ReactContext context) { 53 | LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message); 54 | } 55 | }); 56 | if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { 57 | // Construct it in the background 58 | mReactInstanceManager.createReactContextInBackground(); 59 | } 60 | } 61 | } 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/MessagingService.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import java.util.Map; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.os.Looper; 8 | import android.support.v4.content.LocalBroadcastManager; 9 | import android.util.Log; 10 | 11 | import com.facebook.react.ReactApplication; 12 | import com.facebook.react.ReactInstanceManager; 13 | import com.facebook.react.bridge.ReactContext; 14 | import com.google.firebase.messaging.FirebaseMessagingService; 15 | import com.google.firebase.messaging.RemoteMessage; 16 | 17 | import org.json.JSONException; 18 | import org.json.JSONObject; 19 | 20 | public class MessagingService extends FirebaseMessagingService { 21 | 22 | private static final String TAG = "MessagingService"; 23 | 24 | @Override 25 | public void onMessageReceived(RemoteMessage remoteMessage) { 26 | Log.d(TAG, "Remote message received"); 27 | Intent i = new Intent("com.evollu.react.fcm.ReceiveNotification"); 28 | i.putExtra("data", remoteMessage); 29 | handleBadge(remoteMessage); 30 | buildLocalNotification(remoteMessage); 31 | 32 | final Intent message = i; 33 | 34 | // We need to run this on the main thread, as the React code assumes that is true. 35 | // Namely, DevServerHelper constructs a Handler() without a Looper, which triggers: 36 | // "Can't create handler inside thread that has not called Looper.prepare()" 37 | Handler handler = new Handler(Looper.getMainLooper()); 38 | handler.post(new Runnable() { 39 | public void run() { 40 | // Construct and load our normal React JS code bundle 41 | ReactInstanceManager mReactInstanceManager = ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager(); 42 | ReactContext context = mReactInstanceManager.getCurrentReactContext(); 43 | // If it's constructed, send a notification 44 | if (context != null) { 45 | LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message); 46 | } else { 47 | // Otherwise wait for construction, then send the notification 48 | mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { 49 | public void onReactContextInitialized(ReactContext context) { 50 | LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(message); 51 | } 52 | }); 53 | if (!mReactInstanceManager.hasStartedCreatingInitialContext()) { 54 | // Construct it in the background 55 | mReactInstanceManager.createReactContextInBackground(); 56 | } 57 | } 58 | } 59 | }); 60 | } 61 | 62 | public void handleBadge(RemoteMessage remoteMessage) { 63 | BadgeHelper badgeHelper = new BadgeHelper(this); 64 | if (remoteMessage.getData() == null) { 65 | return; 66 | } 67 | 68 | Map data = remoteMessage.getData(); 69 | if (data.get("badge") == null) { 70 | return; 71 | } 72 | 73 | try { 74 | int badgeCount = Integer.parseInt((String)data.get("badge")); 75 | badgeHelper.setBadgeCount(badgeCount); 76 | } catch (Exception e) { 77 | Log.e(TAG, "Badge count needs to be an integer", e); 78 | } 79 | } 80 | 81 | public void buildLocalNotification(RemoteMessage remoteMessage) { 82 | if(remoteMessage.getData() == null){ 83 | return; 84 | } 85 | Map data = remoteMessage.getData(); 86 | String customNotification = data.get("custom_notification"); 87 | if(customNotification != null){ 88 | try { 89 | Bundle bundle = BundleJSONConverter.convertToBundle(new JSONObject(customNotification)); 90 | FIRLocalMessagingHelper helper = new FIRLocalMessagingHelper(this.getApplication()); 91 | helper.sendNotification(bundle); 92 | } catch (JSONException e) { 93 | e.printStackTrace(); 94 | } 95 | 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/ReactNativeJson.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import com.facebook.react.bridge.ReadableArray; 4 | import com.facebook.react.bridge.ReadableMap; 5 | import com.facebook.react.bridge.ReadableMapKeySetIterator; 6 | import com.facebook.react.bridge.WritableArray; 7 | import com.facebook.react.bridge.WritableMap; 8 | import com.facebook.react.bridge.WritableNativeArray; 9 | import com.facebook.react.bridge.WritableNativeMap; 10 | 11 | import org.json.JSONArray; 12 | import org.json.JSONException; 13 | import org.json.JSONObject; 14 | 15 | import java.util.Iterator; 16 | 17 | public class ReactNativeJson { 18 | public static WritableMap convertJsonToMap(JSONObject jsonObject) throws JSONException { 19 | WritableMap map = new WritableNativeMap(); 20 | 21 | Iterator iterator = jsonObject.keys(); 22 | while (iterator.hasNext()) { 23 | String key = iterator.next(); 24 | Object value = jsonObject.get(key); 25 | if (value instanceof JSONObject) { 26 | map.putMap(key, convertJsonToMap((JSONObject) value)); 27 | } else if (value instanceof JSONArray) { 28 | map.putArray(key, convertJsonToArray((JSONArray) value)); 29 | } else if (value instanceof Boolean) { 30 | map.putBoolean(key, (Boolean) value); 31 | } else if (value instanceof Integer) { 32 | map.putInt(key, (Integer) value); 33 | } else if (value instanceof Double) { 34 | map.putDouble(key, (Double) value); 35 | } else if (value instanceof String) { 36 | map.putString(key, (String) value); 37 | } else { 38 | map.putString(key, value.toString()); 39 | } 40 | } 41 | return map; 42 | } 43 | 44 | public static WritableArray convertJsonToArray(JSONArray jsonArray) throws JSONException { 45 | WritableArray array = new WritableNativeArray(); 46 | 47 | for (int i = 0; i < jsonArray.length(); i++) { 48 | Object value = jsonArray.get(i); 49 | if (value instanceof JSONObject) { 50 | array.pushMap(convertJsonToMap((JSONObject) value)); 51 | } else if (value instanceof JSONArray) { 52 | array.pushArray(convertJsonToArray((JSONArray) value)); 53 | } else if (value instanceof Boolean) { 54 | array.pushBoolean((Boolean) value); 55 | } else if (value instanceof Integer) { 56 | array.pushInt((Integer) value); 57 | } else if (value instanceof Double) { 58 | array.pushDouble((Double) value); 59 | } else if (value instanceof String) { 60 | array.pushString((String) value); 61 | } else { 62 | array.pushString(value.toString()); 63 | } 64 | } 65 | return array; 66 | } 67 | 68 | public static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException { 69 | JSONObject object = new JSONObject(); 70 | ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); 71 | while (iterator.hasNextKey()) { 72 | String key = iterator.nextKey(); 73 | switch (readableMap.getType(key)) { 74 | case Null: 75 | object.put(key, JSONObject.NULL); 76 | break; 77 | case Boolean: 78 | object.put(key, readableMap.getBoolean(key)); 79 | break; 80 | case Number: 81 | object.put(key, readableMap.getDouble(key)); 82 | break; 83 | case String: 84 | object.put(key, readableMap.getString(key)); 85 | break; 86 | case Map: 87 | object.put(key, convertMapToJson(readableMap.getMap(key))); 88 | break; 89 | case Array: 90 | object.put(key, convertArrayToJson(readableMap.getArray(key))); 91 | break; 92 | } 93 | } 94 | return object; 95 | } 96 | 97 | public static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException { 98 | JSONArray array = new JSONArray(); 99 | for (int i = 0; i < readableArray.size(); i++) { 100 | switch (readableArray.getType(i)) { 101 | case Null: 102 | break; 103 | case Boolean: 104 | array.put(readableArray.getBoolean(i)); 105 | break; 106 | case Number: 107 | array.put(readableArray.getDouble(i)); 108 | break; 109 | case String: 110 | array.put(readableArray.getString(i)); 111 | break; 112 | case Map: 113 | array.put(convertMapToJson(readableArray.getMap(i))); 114 | break; 115 | case Array: 116 | array.put(convertArrayToJson(readableArray.getArray(i))); 117 | break; 118 | } 119 | } 120 | return array; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /android/src/main/java/com/evollu/react/fcm/SendNotificationTask.java: -------------------------------------------------------------------------------- 1 | package com.evollu.react.fcm; 2 | 3 | import android.app.Notification; 4 | import android.app.PendingIntent; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.SharedPreferences; 8 | import android.content.pm.ApplicationInfo; 9 | import android.content.res.Resources; 10 | import android.graphics.Bitmap; 11 | import android.graphics.BitmapFactory; 12 | import android.graphics.Color; 13 | import android.media.RingtoneManager; 14 | import android.net.Uri; 15 | import android.os.AsyncTask; 16 | import android.os.Build; 17 | import android.os.Bundle; 18 | import android.os.PowerManager; 19 | import android.support.v4.app.NotificationCompat; 20 | import android.support.v4.app.NotificationManagerCompat; 21 | import android.support.v4.content.LocalBroadcastManager; 22 | import android.util.Log; 23 | 24 | import com.facebook.react.bridge.ReadableMap; 25 | import com.facebook.react.bridge.WritableArray; 26 | 27 | import org.json.JSONArray; 28 | 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.net.HttpURLConnection; 32 | import java.net.URL; 33 | import java.net.URLDecoder; 34 | 35 | import static com.facebook.react.common.ReactConstants.TAG; 36 | 37 | public class SendNotificationTask extends AsyncTask { 38 | private static final long DEFAULT_VIBRATION = 300L; 39 | 40 | private Context mContext; 41 | private Bundle bundle; 42 | private SharedPreferences sharedPreferences; 43 | private Boolean mIsForeground; 44 | 45 | SendNotificationTask(Context context, SharedPreferences sharedPreferences, Boolean mIsForeground, Bundle bundle){ 46 | this.mContext = context; 47 | this.bundle = bundle; 48 | this.sharedPreferences = sharedPreferences; 49 | this.mIsForeground = mIsForeground; 50 | } 51 | 52 | protected Void doInBackground(Void... params) { 53 | try { 54 | String intentClassName = getMainActivityClassName(); 55 | if (intentClassName == null) { 56 | return null; 57 | } 58 | 59 | String body = bundle.getString("body"); 60 | if (body == null) { 61 | return null; 62 | } 63 | body = URLDecoder.decode( body, "UTF-8" ); 64 | 65 | Resources res = mContext.getResources(); 66 | String packageName = mContext.getPackageName(); 67 | 68 | String title = bundle.getString("title"); 69 | if (title == null) { 70 | ApplicationInfo appInfo = mContext.getApplicationInfo(); 71 | title = mContext.getPackageManager().getApplicationLabel(appInfo).toString(); 72 | } 73 | title = URLDecoder.decode( title, "UTF-8" ); 74 | 75 | String ticker = bundle.getString("ticker"); 76 | if (ticker != null) ticker = URLDecoder.decode( ticker, "UTF-8" ); 77 | 78 | String subText = bundle.getString("sub_text"); 79 | if (subText != null) subText = URLDecoder.decode( subText, "UTF-8" ); 80 | 81 | NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext, bundle.getString("channel")) 82 | .setContentTitle(title) 83 | .setContentText(body) 84 | .setTicker(ticker) 85 | .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) 86 | .setAutoCancel(bundle.getBoolean("auto_cancel", true)) 87 | .setNumber(bundle.getInt("number", (int)bundle.getDouble("number"))) 88 | .setSubText(subText) 89 | .setVibrate(new long[]{0, DEFAULT_VIBRATION}) 90 | .setExtras(bundle.getBundle("data")); 91 | 92 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ 93 | String group = bundle.getString("group"); 94 | if (group != null) group = URLDecoder.decode( group, "UTF-8" ); 95 | 96 | notification.setGroup(group); 97 | 98 | String groupAlertBehavior = bundle.getString("groupAlertBehavior", "not-set"); 99 | switch(groupAlertBehavior) { 100 | case "children": 101 | notification.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN); 102 | break; 103 | case "summary": 104 | notification.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); 105 | break; 106 | case "all": 107 | notification.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL); 108 | break; 109 | default: 110 | break; // Leave default behavior to Android defaults. 111 | } 112 | 113 | if (bundle.containsKey("groupSummary") && bundle.getBoolean("groupSummary")) { 114 | notification.setGroupSummary(true); 115 | } 116 | } 117 | 118 | if (bundle.containsKey("ongoing") && bundle.getBoolean("ongoing")) { 119 | notification.setOngoing(bundle.getBoolean("ongoing")); 120 | } 121 | 122 | //priority 123 | String priority = bundle.getString("priority", ""); 124 | switch(priority) { 125 | case "min": 126 | notification.setPriority(NotificationCompat.PRIORITY_MIN); 127 | break; 128 | case "high": 129 | notification.setPriority(NotificationCompat.PRIORITY_HIGH); 130 | break; 131 | case "max": 132 | notification.setPriority(NotificationCompat.PRIORITY_MAX); 133 | break; 134 | default: 135 | notification.setPriority(NotificationCompat.PRIORITY_DEFAULT); 136 | } 137 | 138 | //icon 139 | String smallIcon = bundle.getString("icon", "ic_launcher"); 140 | int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName); 141 | if(smallIconResId == 0){ 142 | smallIconResId = res.getIdentifier(smallIcon, "drawable", packageName); 143 | } 144 | if(smallIconResId != 0){ 145 | notification.setSmallIcon(smallIconResId); 146 | } 147 | 148 | //large icon 149 | String largeIcon = bundle.getString("large_icon"); 150 | if(largeIcon != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ 151 | if (largeIcon.startsWith("http://") || largeIcon.startsWith("https://")) { 152 | Bitmap bitmap = getBitmapFromURL(largeIcon); 153 | notification.setLargeIcon(bitmap); 154 | } else { 155 | int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName); 156 | Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId); 157 | 158 | if (largeIconResId != 0) { 159 | notification.setLargeIcon(largeIconBitmap); 160 | } 161 | } 162 | } 163 | 164 | //big text 165 | String bigText = bundle.getString("big_text"); 166 | if(bigText != null){ 167 | bigText = URLDecoder.decode( bigText, "UTF-8" ); 168 | notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)); 169 | } 170 | 171 | //picture 172 | String picture = bundle.getString("picture"); 173 | 174 | if(picture!=null){ 175 | NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle(); 176 | 177 | if (picture.startsWith("http://") || picture.startsWith("https://")) { 178 | Bitmap bitmap = getBitmapFromURL(picture); 179 | bigPicture.bigPicture(bitmap); 180 | } else { 181 | int pictureResId = res.getIdentifier(picture, "mipmap", packageName); 182 | Bitmap pictureResIdBitmap = BitmapFactory.decodeResource(res, pictureResId); 183 | 184 | if (pictureResId != 0) { 185 | bigPicture.bigPicture(pictureResIdBitmap); 186 | } 187 | } 188 | // setBigContentTitle and setSummaryText overrides current title with body and subtext 189 | // that cause to display duplicated body in subtext when picture has specified 190 | notification.setStyle(bigPicture); 191 | } 192 | 193 | //sound 194 | String soundName = bundle.getString("sound"); 195 | if (soundName != null) { 196 | if (soundName.equalsIgnoreCase("default")) { 197 | notification.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); 198 | } else { 199 | int soundResourceId = res.getIdentifier(soundName, "raw", packageName); 200 | if (soundResourceId == 0) { 201 | soundName = soundName.substring(0, soundName.lastIndexOf('.')); 202 | soundResourceId = res.getIdentifier(soundName, "raw", packageName); 203 | } 204 | notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId)); 205 | } 206 | } 207 | 208 | //color 209 | if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 210 | notification.setCategory(NotificationCompat.CATEGORY_CALL); 211 | 212 | String color = bundle.getString("color"); 213 | if (color != null) { 214 | notification.setColor(Color.parseColor(color)); 215 | } 216 | } 217 | 218 | //vibrate 219 | if(bundle.containsKey("vibrate")){ 220 | long vibrate = Math.round(bundle.getDouble("vibrate", DEFAULT_VIBRATION)); 221 | if(vibrate > 0){ 222 | notification.setVibrate(new long[]{0, vibrate}); 223 | }else{ 224 | notification.setVibrate(null); 225 | } 226 | } 227 | 228 | //lights 229 | if (bundle.getBoolean("lights")) { 230 | notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS); 231 | } 232 | 233 | if(bundle.containsKey("fire_date")) { 234 | Log.d(TAG, "broadcast intent if it is a scheduled notification"); 235 | Intent i = new Intent("com.evollu.react.fcm.ReceiveLocalNotification"); 236 | i.putExtras(bundle); 237 | LocalBroadcastManager.getInstance(mContext).sendBroadcast(i); 238 | } 239 | 240 | if(!mIsForeground || bundle.getBoolean("show_in_foreground")){ 241 | Intent intent = new Intent(); 242 | intent.setClassName(mContext, intentClassName); 243 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 244 | intent.putExtras(bundle); 245 | 246 | String clickAction = bundle.getString("click_action"); 247 | if (clickAction != null) clickAction = URLDecoder.decode( clickAction, "UTF-8" ); 248 | 249 | intent.setAction(clickAction); 250 | 251 | int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis(); 252 | PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent, 253 | PendingIntent.FLAG_UPDATE_CURRENT); 254 | 255 | notification.setContentIntent(pendingIntent); 256 | 257 | if (bundle.containsKey("android_actions")) { 258 | String androidActions = bundle.getString("android_actions"); 259 | androidActions = URLDecoder.decode( androidActions, "UTF-8" ); 260 | 261 | WritableArray actions = ReactNativeJson.convertJsonToArray(new JSONArray(androidActions)); 262 | for (int a = 0; a < actions.size(); a++) { 263 | ReadableMap action = actions.getMap(a); 264 | String actionTitle = action.getString("title"); 265 | String actionId = action.getString("id"); 266 | Intent actionIntent = new Intent(); 267 | actionIntent.setClassName(mContext, intentClassName); 268 | actionIntent.setAction("com.evollu.react.fcm." + actionId + "_ACTION"); 269 | actionIntent.putExtras(bundle); 270 | actionIntent.putExtra("_actionIdentifier", actionId); 271 | actionIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); 272 | PendingIntent pendingActionIntent = PendingIntent.getActivity(mContext, notificationID, actionIntent, 273 | PendingIntent.FLAG_UPDATE_CURRENT); 274 | 275 | notification.addAction(0, actionTitle, pendingActionIntent); 276 | } 277 | } 278 | 279 | Notification info = notification.build(); 280 | 281 | NotificationManagerCompat.from(mContext).notify(notificationID, info); 282 | } 283 | 284 | if(bundle.getBoolean("wake_screen", false)){ 285 | PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); 286 | if(pm != null && !pm.isScreenOn()) 287 | { 288 | PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK |PowerManager.ACQUIRE_CAUSES_WAKEUP |PowerManager.ON_AFTER_RELEASE,"FCMLock"); 289 | wl.acquire(5000); 290 | } 291 | } 292 | 293 | //clear out one time scheduled notification once fired 294 | if(!bundle.containsKey("repeat_interval") && bundle.containsKey("fire_date")) { 295 | SharedPreferences.Editor editor = sharedPreferences.edit(); 296 | editor.remove(bundle.getString("id")); 297 | editor.apply(); 298 | } 299 | } catch (Exception e) { 300 | Log.e(TAG, "failed to send local notification", e); 301 | } 302 | return null; 303 | } 304 | 305 | private Bitmap getBitmapFromURL(String strURL) { 306 | try { 307 | URL url = new URL(strURL); 308 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 309 | connection.setDoInput(true); 310 | connection.connect(); 311 | InputStream input = connection.getInputStream(); 312 | return BitmapFactory.decodeStream(input); 313 | } catch (IOException e) { 314 | e.printStackTrace(); 315 | return null; 316 | } 317 | } 318 | 319 | protected String getMainActivityClassName() { 320 | String packageName = mContext.getPackageName(); 321 | Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); 322 | return launchIntent != null ? launchIntent.getComponent().getClassName() : null; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-native-fcm" { 2 | type FCMEventType = 3 | | "FCMTokenRefreshed" 4 | | "FCMNotificationReceived" 5 | | "FCMDirectChannelConnectionChanged"; 6 | export namespace FCMEvent { 7 | const RefreshToken = "FCMTokenRefreshed"; 8 | const Notification = "FCMNotificationReceived"; 9 | const DirectChannelConnectionChanged: "FCMDirectChannelConnectionChanged"; 10 | } 11 | 12 | export namespace RemoteNotificationResult { 13 | const NewData = "UIBackgroundFetchResultNewData"; 14 | const NoData = "UIBackgroundFetchResultNoData"; 15 | const ResultFailed = "UIBackgroundFetchResultFailed"; 16 | } 17 | 18 | export namespace WillPresentNotificationResult { 19 | const All = "UNNotificationPresentationOptionAll"; 20 | const None = "UNNotificationPresentationOptionNone"; 21 | } 22 | 23 | export namespace NotificationType { 24 | const Remote = "remote_notification"; 25 | const NotificationResponse = "notification_response"; 26 | const WillPresent = "will_present_notification"; 27 | const Local = "local_notification"; 28 | } 29 | 30 | export enum NotificationCategoryOption { 31 | CustomDismissAction = "UNNotificationCategoryOptionCustomDismissAction", 32 | AllowInCarPlay = "UNNotificationCategoryOptionAllowInCarPlay", 33 | PreviewsShowTitle = "UNNotificationCategoryOptionHiddenPreviewsShowTitle", 34 | PreviewsShowSubtitle = "UNNotificationCategoryOptionHiddenPreviewsShowSubtitle", 35 | None = "UNNotificationCategoryOptionNone" 36 | } 37 | 38 | export enum NotificationActionOption { 39 | AuthenticationRequired = "UNNotificationActionOptionAuthenticationRequired", 40 | Destructive = "UNNotificationActionOptionDestructive", 41 | Foreground = "UNNotificationActionOptionForeground", 42 | None = "UNNotificationActionOptionNone" 43 | } 44 | 45 | export enum NotificationActionType { 46 | Default = "UNNotificationActionTypeDefault", 47 | TextInput = "UNNotificationActionTypeTextInput" 48 | } 49 | 50 | export interface Notification { 51 | collapse_key: string; 52 | opened_from_tray: boolean; 53 | from: string; 54 | notification: { 55 | title?: string; 56 | body: string; 57 | icon: string; 58 | }; 59 | fcm: { 60 | action?: string; 61 | tag?: string; 62 | icon?: string; 63 | color?: string; 64 | body: string; 65 | title?: string; 66 | }; 67 | local_notification?: boolean; 68 | _notificationType: string; 69 | _actionIdentifier?: string; 70 | _userText?: string; 71 | finish(type?: string): void; 72 | [key: string]: any; 73 | } 74 | 75 | export interface LocalNotification { 76 | id?: string; 77 | title?: string; 78 | body: string; 79 | icon?: string; 80 | vibrate?: number; 81 | sound?: string; 82 | big_text?: string; 83 | sub_text?: string; 84 | color?: string; 85 | large_icon?: string; 86 | priority?: string; 87 | show_in_foreground?: boolean; 88 | click_action?: string; 89 | badge?: number; 90 | number?: number; 91 | ticker?: string; 92 | auto_cancel?: boolean; 93 | group?: string; 94 | groupSummary?: boolean; 95 | groupAlertBehavior?: string; 96 | picture?: string; 97 | ongoing?: boolean; 98 | lights?: boolean; 99 | [key: string]: any; 100 | } 101 | 102 | export interface ScheduleLocalNotification extends LocalNotification { 103 | id: string; 104 | fire_date: number; 105 | repeat_interval?: "week" | "day" | "hour"; 106 | } 107 | 108 | export interface Subscription { 109 | remove(): void; 110 | } 111 | 112 | export interface NotificationAction { 113 | type: NotificationActionType; 114 | id: string; 115 | title?: string; 116 | textInputButtonTitle?: string; 117 | textInputPlaceholder?: string; 118 | options: NotificationActionOption | NotificationActionOption[]; 119 | } 120 | 121 | export interface NotificationCategory { 122 | id: string; 123 | actions: NotificationAction[]; 124 | intentIdentifiers: string[]; 125 | hiddenPreviewsBodyPlaceholder?: string; 126 | options?: NotificationCategoryOption | NotificationCategoryOption[]; 127 | } 128 | 129 | export class FCM { 130 | static requestPermissions(): Promise; 131 | static getFCMToken(): Promise; 132 | static on( 133 | event: "FCMTokenRefreshed", 134 | handler: (token: string) => void 135 | ): Subscription; 136 | static on( 137 | event: "FCMNotificationReceived", 138 | handler: (notification: Notification) => void 139 | ): Subscription; 140 | static subscribeToTopic(topic: string): void; 141 | static unsubscribeFromTopic(topic: string): void; 142 | static getInitialNotification(): Promise; 143 | static presentLocalNotification(notification: LocalNotification): void; 144 | 145 | static scheduleLocalNotification(schedule: ScheduleLocalNotification): void; 146 | static getScheduledLocalNotifications(): Promise; 147 | 148 | static removeAllDeliveredNotifications(): void; 149 | static removeDeliveredNotification(id: string): void; 150 | 151 | static cancelAllLocalNotifications(): void; 152 | static cancelLocalNotification(id: string): string; 153 | 154 | static setBadgeNumber(badge: number): void; 155 | static getBadgeNumber(): Promise; 156 | static send(id: string, data: any): void; 157 | 158 | static enableDirectChannel(): void; 159 | static isDirectChannelEstablished(): Promise; 160 | static getAPNSToken(): Promise; 161 | 162 | static setNotificationCategories(categories: NotificationCategory[]): void; 163 | static createNotificationChannel(config: { 164 | id: string; 165 | name: string; 166 | description?: string; 167 | priority?: string; 168 | }); 169 | } 170 | 171 | export default FCM; 172 | } 173 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { NativeModules, NativeEventEmitter, Platform } from 'react-native'; 2 | 3 | const EventEmitter = new NativeEventEmitter(NativeModules.RNFIRMessaging || {}); 4 | 5 | export const FCMEvent = { 6 | RefreshToken: 'FCMTokenRefreshed', 7 | Notification: 'FCMNotificationReceived', 8 | DirectChannelConnectionChanged: 'FCMDirectChannelConnectionChanged' 9 | }; 10 | 11 | export const RemoteNotificationResult = { 12 | NewData: 'UIBackgroundFetchResultNewData', 13 | NoData: 'UIBackgroundFetchResultNoData', 14 | ResultFailed: 'UIBackgroundFetchResultFailed' 15 | }; 16 | 17 | export const WillPresentNotificationResult = { 18 | All: 'UNNotificationPresentationOptionAll', 19 | None: 'UNNotificationPresentationOptionNone' 20 | }; 21 | 22 | export const NotificationType = { 23 | Remote: 'remote_notification', 24 | NotificationResponse: 'notification_response', 25 | WillPresent: 'will_present_notification', 26 | Local: 'local_notification' 27 | }; 28 | 29 | export const NotificationCategoryOption = { 30 | CustomDismissAction: 'UNNotificationCategoryOptionCustomDismissAction', 31 | AllowInCarPlay: 'UNNotificationCategoryOptionAllowInCarPlay', 32 | PreviewsShowTitle: 'UNNotificationCategoryOptionHiddenPreviewsShowTitle', 33 | PreviewsShowSubtitle: 'UNNotificationCategoryOptionHiddenPreviewsShowSubtitle', 34 | None: 'UNNotificationCategoryOptionNone' 35 | }; 36 | 37 | export const NotificationActionOption = { 38 | AuthenticationRequired: 'UNNotificationActionOptionAuthenticationRequired', 39 | Destructive: 'UNNotificationActionOptionDestructive', 40 | Foreground: 'UNNotificationActionOptionForeground', 41 | None: 'UNNotificationActionOptionNone', 42 | }; 43 | 44 | export const NotificationActionType = { 45 | Default: 'UNNotificationActionTypeDefault', 46 | TextInput: 'UNNotificationActionTypeTextInput', 47 | }; 48 | 49 | const RNFIRMessaging = NativeModules.RNFIRMessaging; 50 | 51 | const FCM = {}; 52 | 53 | FCM.getInitialNotification = () => { 54 | return RNFIRMessaging.getInitialNotification(); 55 | }; 56 | 57 | FCM.enableDirectChannel = () => { 58 | if (Platform.OS === 'ios') { 59 | return RNFIRMessaging.enableDirectChannel(); 60 | } 61 | }; 62 | 63 | FCM.isDirectChannelEstablished = () => { 64 | return Platform.OS === 'ios' ? RNFIRMessaging.isDirectChannelEstablished() : Promise.resolve(true); 65 | }; 66 | 67 | FCM.getFCMToken = () => { 68 | return RNFIRMessaging.getFCMToken(); 69 | }; 70 | 71 | FCM.getEntityFCMToken = () => { 72 | return RNFIRMessaging.getEntityFCMToken(); 73 | } 74 | 75 | FCM.deleteEntityFCMToken = () => { 76 | return RNFIRMessaging.deleteEntityFCMToken(); 77 | } 78 | 79 | FCM.deleteInstanceId = () =>{ 80 | return RNFIRMessaging.deleteInstanceId(); 81 | }; 82 | 83 | FCM.getAPNSToken = () => { 84 | if (Platform.OS === 'ios') { 85 | return RNFIRMessaging.getAPNSToken(); 86 | } 87 | }; 88 | 89 | FCM.requestPermissions = () => { 90 | return RNFIRMessaging.requestPermissions(); 91 | }; 92 | 93 | FCM.createNotificationChannel = (channel) => { 94 | if (Platform.OS === 'android') { 95 | return RNFIRMessaging.createNotificationChannel(channel); 96 | } 97 | } 98 | 99 | FCM.deleteNotificationChannel = (channel) => { 100 | if (Platform.OS === 'android') { 101 | return RNFIRMessaging.deleteNotificationChannel(channel); 102 | } 103 | } 104 | 105 | FCM.presentLocalNotification = (details) => { 106 | details.id = details.id || new Date().getTime().toString(); 107 | details.local_notification = true; 108 | RNFIRMessaging.presentLocalNotification(details); 109 | }; 110 | 111 | FCM.scheduleLocalNotification = function(details) { 112 | if (!details.id) { 113 | throw new Error('id is required for scheduled notification'); 114 | } 115 | details.local_notification = true; 116 | RNFIRMessaging.scheduleLocalNotification(details); 117 | }; 118 | 119 | FCM.getScheduledLocalNotifications = function() { 120 | return RNFIRMessaging.getScheduledLocalNotifications(); 121 | }; 122 | 123 | FCM.cancelLocalNotification = (notificationID) => { 124 | if (!notificationID) { 125 | return; 126 | } 127 | RNFIRMessaging.cancelLocalNotification(notificationID); 128 | }; 129 | 130 | FCM.cancelAllLocalNotifications = () => { 131 | RNFIRMessaging.cancelAllLocalNotifications(); 132 | }; 133 | 134 | FCM.removeDeliveredNotification = (notificationID) => { 135 | if (!notificationID) { 136 | return; 137 | } 138 | RNFIRMessaging.removeDeliveredNotification(notificationID); 139 | }; 140 | 141 | FCM.removeAllDeliveredNotifications = () => { 142 | RNFIRMessaging.removeAllDeliveredNotifications(); 143 | }; 144 | 145 | FCM.setBadgeNumber = (number) => { 146 | RNFIRMessaging.setBadgeNumber(number); 147 | }; 148 | 149 | FCM.getBadgeNumber = () => { 150 | return RNFIRMessaging.getBadgeNumber(); 151 | }; 152 | 153 | function finish(result) { 154 | if (Platform.OS !== 'ios') { 155 | return; 156 | } 157 | if (!this._finishCalled && this._completionHandlerId) { 158 | this._finishCalled = true; 159 | switch (this._notificationType) { 160 | case NotificationType.Remote: 161 | result = result || RemoteNotificationResult.NoData; 162 | if (!Object.values(RemoteNotificationResult).includes(result)) { 163 | throw new Error(`Invalid RemoteNotificationResult, use import {RemoteNotificationResult} from 'react-native-fcm' to avoid typo`); 164 | } 165 | RNFIRMessaging.finishRemoteNotification(this._completionHandlerId, result); 166 | return; 167 | case NotificationType.NotificationResponse: 168 | RNFIRMessaging.finishNotificationResponse(this._completionHandlerId); 169 | return; 170 | case NotificationType.WillPresent: 171 | result = result || (this.show_in_foreground ? WillPresentNotificationResult.All : WillPresentNotificationResult.None); 172 | if (!Object.values(WillPresentNotificationResult).includes(result)) { 173 | throw new Error(`Invalid WillPresentNotificationResult, make sure you use import {WillPresentNotificationResult} from 'react-native-fcm' to avoid typo`); 174 | } 175 | RNFIRMessaging.finishWillPresentNotification(this._completionHandlerId, result); 176 | return; 177 | default: 178 | return; 179 | } 180 | } 181 | } 182 | 183 | FCM.on = (event, callback) => { 184 | if (!Object.values(FCMEvent).includes(event)) { 185 | throw new Error(`Invalid FCM event subscription, use import {FCMEvent} from 'react-native-fcm' to avoid typo`); 186 | }; 187 | 188 | if (event === FCMEvent.Notification) { 189 | return EventEmitter.addListener(event, async(data) => { 190 | data.finish = finish; 191 | try { 192 | await callback(data); 193 | } catch (err) { 194 | console.error('Notification handler err:\n'+err.stack); 195 | throw err; 196 | } 197 | if (!data._finishCalled) { 198 | data.finish(); 199 | } 200 | }); 201 | } 202 | return EventEmitter.addListener(event, callback); 203 | }; 204 | 205 | FCM.subscribeToTopic = (topic) => { 206 | RNFIRMessaging.subscribeToTopic(topic); 207 | }; 208 | 209 | FCM.unsubscribeFromTopic = (topic) => { 210 | RNFIRMessaging.unsubscribeFromTopic(topic); 211 | }; 212 | 213 | FCM.send = (senderId, payload) => { 214 | RNFIRMessaging.send(senderId, payload); 215 | }; 216 | 217 | FCM.setNotificationCategories = (categories) => { 218 | if (Platform.OS === 'ios') { 219 | RNFIRMessaging.setNotificationCategories(categories); 220 | } 221 | } 222 | 223 | export default FCM; 224 | 225 | export {}; 226 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | #if __has_include() 5 | #import 6 | #import 7 | #import 8 | #else 9 | @import Firebase; 10 | #endif 11 | 12 | #import 13 | 14 | @import UserNotifications; 15 | 16 | @interface RNFIRMessaging : RCTEventEmitter 17 | 18 | typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result); 19 | typedef void (^RCTWillPresentNotificationCallback)(UNNotificationPresentationOptions result); 20 | typedef void (^RCTNotificationResponseCallback)(); 21 | 22 | @property (nonatomic, assign) bool connectedToFCM; 23 | 24 | #if !TARGET_OS_TV 25 | + (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull RCTRemoteNotificationCallback)completionHandler; 26 | + (void)didReceiveLocalNotification:(nonnull UILocalNotification *)notification; 27 | + (void)didReceiveNotificationResponse:(nonnull UNNotificationResponse *)response withCompletionHandler:(nonnull RCTNotificationResponseCallback)completionHandler; 28 | + (void)willPresentNotification:(nonnull UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler; 29 | #endif 30 | 31 | @end 32 | 33 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.m: -------------------------------------------------------------------------------- 1 | #import "RNFIRMessaging.h" 2 | 3 | #import 4 | #import 5 | 6 | @import UserNotifications; 7 | 8 | #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 9 | 10 | #define UIUserNotificationTypeAlert UIRemoteNotificationTypeAlert 11 | #define UIUserNotificationTypeBadge UIRemoteNotificationTypeBadge 12 | #define UIUserNotificationTypeSound UIRemoteNotificationTypeSound 13 | #define UIUserNotificationTypeNone UIRemoteNotificationTypeNone 14 | #define UIUserNotificationType UIRemoteNotificationType 15 | 16 | #endif 17 | 18 | NSString *const FCMNotificationReceived = @"FCMNotificationReceived"; 19 | NSString *const FCMTokenRefreshed = @"FCMTokenRefreshed"; 20 | NSString *const FCMDirectChannelConnectionChanged = @"FCMDirectChannelConnectionChanged"; 21 | 22 | @implementation RCTConvert (NSCalendarUnit) 23 | 24 | RCT_ENUM_CONVERTER(NSCalendarUnit, 25 | (@{ 26 | @"year": @(NSCalendarUnitYear), 27 | @"month": @(NSCalendarUnitMonth), 28 | @"week": @(NSCalendarUnitWeekOfYear), 29 | @"day": @(NSCalendarUnitDay), 30 | @"hour": @(NSCalendarUnitHour), 31 | @"minute": @(NSCalendarUnitMinute) 32 | }), 33 | 0, 34 | integerValue) 35 | @end 36 | 37 | 38 | @implementation RCTConvert (UNNotificationRequest) 39 | 40 | + (UNNotificationRequest *)UNNotificationRequest:(id)json 41 | { 42 | NSDictionary *details = [self NSDictionary:json]; 43 | UNMutableNotificationContent *content = [UNMutableNotificationContent new]; 44 | content.title =[RCTConvert NSString:details[@"title"]]; 45 | content.body =[RCTConvert NSString:details[@"body"]]; 46 | NSString* sound = [RCTConvert NSString:details[@"sound"]]; 47 | if(sound != nil){ 48 | if ([sound isEqual:@"default"]) { 49 | content.sound = [UNNotificationSound defaultSound]; 50 | } else { 51 | content.sound = [UNNotificationSound soundNamed:sound]; 52 | } 53 | } 54 | content.categoryIdentifier = [RCTConvert NSString:details[@"click_action"]]; 55 | content.userInfo = details; 56 | content.badge = [RCTConvert NSNumber:details[@"badge"]]; 57 | 58 | NSDate *fireDate = [RCTConvert NSDate:details[@"fire_date"]]; 59 | 60 | if(fireDate == nil){ 61 | return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:nil]; 62 | } 63 | 64 | NSCalendarUnit interval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]]; 65 | NSCalendarUnit unitFlags; 66 | switch (interval) { 67 | case NSCalendarUnitMinute: { 68 | unitFlags = NSCalendarUnitSecond; 69 | break; 70 | } 71 | case NSCalendarUnitHour: { 72 | unitFlags = NSCalendarUnitMinute | NSCalendarUnitSecond; 73 | break; 74 | } 75 | case NSCalendarUnitDay: { 76 | unitFlags = NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 77 | break; 78 | } 79 | case NSCalendarUnitWeekOfYear: { 80 | unitFlags = NSCalendarUnitWeekday | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 81 | break; 82 | } 83 | case NSCalendarUnitMonth:{ 84 | unitFlags = NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 85 | break; 86 | } 87 | case NSCalendarUnitYear:{ 88 | unitFlags = NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 89 | break; 90 | } 91 | default: 92 | unitFlags = NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond; 93 | break; 94 | } 95 | NSDateComponents *components = [[NSCalendar currentCalendar] components:unitFlags fromDate:fireDate]; 96 | UNCalendarNotificationTrigger *trigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:interval != 0]; 97 | return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:trigger]; 98 | } 99 | 100 | @end 101 | 102 | @implementation RCTConvert (UILocalNotification) 103 | 104 | + (UILocalNotification *)UILocalNotification:(id)json 105 | { 106 | NSDictionary *details = [self NSDictionary:json]; 107 | UILocalNotification *notification = [UILocalNotification new]; 108 | notification.fireDate = [RCTConvert NSDate:details[@"fire_date"]] ?: [NSDate date]; 109 | if([notification respondsToSelector:@selector(setAlertTitle:)]){ 110 | [notification setAlertTitle:[RCTConvert NSString:details[@"title"]]]; 111 | } 112 | notification.alertBody = [RCTConvert NSString:details[@"body"]]; 113 | notification.alertAction = [RCTConvert NSString:details[@"alert_action"]]; 114 | notification.soundName = [RCTConvert NSString:details[@"sound"]] ?: UILocalNotificationDefaultSoundName; 115 | notification.userInfo = details; 116 | notification.category = [RCTConvert NSString:details[@"click_action"]]; 117 | notification.repeatInterval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]]; 118 | notification.applicationIconBadgeNumber = [RCTConvert NSInteger:details[@"badge"]]; 119 | return notification; 120 | } 121 | 122 | RCT_ENUM_CONVERTER(UIBackgroundFetchResult, (@{ 123 | @"UIBackgroundFetchResultNewData": @(UIBackgroundFetchResultNewData), 124 | @"UIBackgroundFetchResultNoData": @(UIBackgroundFetchResultNoData), 125 | @"UIBackgroundFetchResultFailed": @(UIBackgroundFetchResultFailed), 126 | }), UIBackgroundFetchResultNoData, integerValue) 127 | 128 | RCT_ENUM_CONVERTER(UNNotificationPresentationOptions, (@{ 129 | @"UNNotificationPresentationOptionAll": @(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound), 130 | @"UNNotificationPresentationOptionNone": @(UNNotificationPresentationOptionNone)}), UIBackgroundFetchResultNoData, integerValue) 131 | 132 | @end 133 | 134 | @implementation RCTConvert (UNNotificationAction) 135 | 136 | typedef NS_ENUM(NSUInteger, UNNotificationActionType) { 137 | UNNotificationActionTypeDefault, 138 | UNNotificationActionTypeTextInput 139 | }; 140 | 141 | + (UNNotificationAction *) UNNotificationAction:(id)json { 142 | NSDictionary *details = [self NSDictionary:json]; 143 | 144 | NSString *identifier = [RCTConvert NSString: details[@"id"]]; 145 | NSString *title = [RCTConvert NSString: details[@"title"]]; 146 | UNNotificationActionOptions options = [RCTConvert UNNotificationActionOptions: details[@"options"]]; 147 | UNNotificationActionType type = [RCTConvert UNNotificationActionType:details[@"type"]]; 148 | 149 | if (type == UNNotificationActionTypeTextInput) { 150 | NSString *textInputButtonTitle = [RCTConvert NSString: details[@"textInputButtonTitle"]]; 151 | NSString *textInputPlaceholder = [RCTConvert NSString: details[@"textInputPlaceholder"]]; 152 | 153 | return [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:textInputButtonTitle textInputPlaceholder:textInputPlaceholder]; 154 | } 155 | 156 | return [UNNotificationAction actionWithIdentifier:identifier 157 | title:title 158 | options:options]; 159 | 160 | } 161 | 162 | RCT_ENUM_CONVERTER(UNNotificationActionType, (@{ 163 | @"UNNotificationActionTypeDefault": @(UNNotificationActionTypeDefault), 164 | @"UNNotificationActionTypeTextInput": @(UNNotificationActionTypeTextInput), 165 | }), UNNotificationActionTypeDefault, integerValue) 166 | 167 | 168 | RCT_MULTI_ENUM_CONVERTER(UNNotificationActionOptions, (@{ 169 | @"UNNotificationActionOptionAuthenticationRequired": @(UNNotificationActionOptionAuthenticationRequired), 170 | @"UNNotificationActionOptionDestructive": @(UNNotificationActionOptionDestructive), 171 | @"UNNotificationActionOptionForeground": @(UNNotificationActionOptionForeground), 172 | @"UNNotificationActionOptionNone": @(UNNotificationActionOptionNone), 173 | }), UNNotificationActionOptionNone, integerValue) 174 | 175 | 176 | @end 177 | 178 | @implementation RCTConvert (UNNotificationCategory) 179 | 180 | 181 | + (UNNotificationCategory *) UNNotificationCategory:(id)json { 182 | NSDictionary *details = [self NSDictionary:json]; 183 | 184 | NSString *identifier = [RCTConvert NSString: details[@"id"]]; 185 | 186 | NSMutableArray *actions = [[NSMutableArray alloc] init]; 187 | for (NSDictionary *actionDict in details[@"actions"]) { 188 | [actions addObject:[RCTConvert UNNotificationAction:actionDict]]; 189 | } 190 | 191 | NSArray *intentIdentifiers = [RCTConvert NSStringArray:details[@"intentIdentifiers"]]; 192 | NSString *hiddenPreviewsBodyPlaceholder = [RCTConvert NSString:details[@"hiddenPreviewsBodyPlaceholder"]]; 193 | UNNotificationCategoryOptions options = [RCTConvert UNNotificationCategoryOptions: details[@"options"]]; 194 | 195 | if (hiddenPreviewsBodyPlaceholder) { 196 | #if defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 197 | return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder options:options]; 198 | #endif 199 | } 200 | 201 | return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers options:options]; 202 | } 203 | 204 | #pragma clang diagnostic push 205 | #pragma clang diagnostic ignored "-Wpartial-availability" 206 | 207 | RCT_MULTI_ENUM_CONVERTER(UNNotificationCategoryOptions, (@{ 208 | @"UNNotificationCategoryOptionNone": @(UNNotificationCategoryOptionNone), 209 | @"UNNotificationCategoryOptionCustomDismissAction": @(UNNotificationCategoryOptionCustomDismissAction), 210 | @"UNNotificationCategoryOptionAllowInCarPlay": @(UNNotificationCategoryOptionAllowInCarPlay), 211 | @"UNNotificationCategoryOptionHiddenPreviewsShowTitle": @(UNNotificationCategoryOptionHiddenPreviewsShowTitle), 212 | @"UNNotificationCategoryOptionHiddenPreviewsShowSubtitle": @(UNNotificationCategoryOptionHiddenPreviewsShowSubtitle), 213 | }), UNNotificationCategoryOptionNone, integerValue) 214 | 215 | #pragma clang diagnostic pop 216 | 217 | 218 | @end 219 | 220 | @interface RCTEventEmitter () 221 | - (void) addListener:(NSString *)eventName; 222 | @end 223 | 224 | @interface RNFIRMessaging () 225 | @property (nonatomic, strong) NSMutableDictionary *notificationCallbacks; 226 | @end 227 | 228 | @implementation RNFIRMessaging 229 | 230 | static bool jsHandlerRegistered; 231 | static NSMutableArray* pendingNotifications; 232 | static NSString* refreshToken; 233 | 234 | RCT_EXPORT_MODULE(); 235 | 236 | - (NSArray *)supportedEvents { 237 | return @[FCMNotificationReceived, FCMTokenRefreshed, FCMDirectChannelConnectionChanged]; 238 | } 239 | 240 | + (BOOL)requiresMainQueueSetup { 241 | return YES; 242 | } 243 | 244 | + (void)didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo fetchCompletionHandler:(nonnull RCTRemoteNotificationCallback)completionHandler { 245 | NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: userInfo]; 246 | [data setValue:@"remote_notification" forKey:@"_notificationType"]; 247 | [data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"]; 248 | [self sendNotificationEventWhenAvailable:@{@"data": data, @"completionHandler": completionHandler}]; 249 | } 250 | 251 | + (void)didReceiveLocalNotification:(UILocalNotification *)notification { 252 | NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.userInfo]; 253 | [data setValue:@"local_notification" forKey:@"_notificationType"]; 254 | [data setValue:@(RCTSharedApplication().applicationState == UIApplicationStateInactive) forKey:@"opened_from_tray"]; 255 | [self sendNotificationEventWhenAvailable:@{@"data": data}]; 256 | } 257 | 258 | + (void)didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(nonnull RCTNotificationResponseCallback)completionHandler 259 | { 260 | NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: response.notification.request.content.userInfo]; 261 | [data setValue:@"notification_response" forKey:@"_notificationType"]; 262 | [data setValue:@YES forKey:@"opened_from_tray"]; 263 | if (response.actionIdentifier) { 264 | [data setValue:response.actionIdentifier forKey:@"_actionIdentifier"]; 265 | } 266 | 267 | if ([response isKindOfClass:UNTextInputNotificationResponse.class]) { 268 | [data setValue:[(UNTextInputNotificationResponse *)response userText] forKey:@"_userText"]; 269 | } 270 | 271 | NSDictionary *userInfo = @{@"data": data, @"completionHandler": completionHandler}; 272 | [self sendNotificationEventWhenAvailable:userInfo]; 273 | 274 | } 275 | 276 | + (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler 277 | { 278 | NSMutableDictionary* data = [[NSMutableDictionary alloc] initWithDictionary: notification.request.content.userInfo]; 279 | [data setValue:@"will_present_notification" forKey:@"_notificationType"]; 280 | [self sendNotificationEventWhenAvailable:@{@"data": data, @"completionHandler": completionHandler}]; 281 | } 282 | 283 | + (void)sendNotificationEventWhenAvailable:(NSDictionary*)data 284 | { 285 | if(!jsHandlerRegistered){ 286 | // JS hasn't registered callback yet. hold on that 287 | if(!pendingNotifications){ 288 | pendingNotifications = [NSMutableArray array]; 289 | } 290 | [pendingNotifications addObject:data]; 291 | } else { 292 | [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data]; 293 | } 294 | } 295 | 296 | - (void)dealloc 297 | { 298 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 299 | } 300 | 301 | - (instancetype)init { 302 | self = [super init]; 303 | 304 | [[NSNotificationCenter defaultCenter] addObserver:self 305 | selector:@selector(handleNotificationReceived:) 306 | name:FCMNotificationReceived 307 | object:nil]; 308 | 309 | [[NSNotificationCenter defaultCenter] 310 | addObserver:self selector:@selector(sendDataMessageFailure:) 311 | name:FIRMessagingSendErrorNotification object:nil]; 312 | 313 | [[NSNotificationCenter defaultCenter] 314 | addObserver:self selector:@selector(sendDataMessageSuccess:) 315 | name:FIRMessagingSendSuccessNotification object:nil]; 316 | 317 | [[NSNotificationCenter defaultCenter] 318 | addObserver:self selector:@selector(connectionStateChanged:) 319 | name:FIRMessagingConnectionStateChangedNotification object:nil]; 320 | 321 | // For iOS 10 data message (sent via FCM) 322 | dispatch_async(dispatch_get_main_queue(), ^{ 323 | [[FIRMessaging messaging] setDelegate:self]; 324 | }); 325 | 326 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 327 | if(!jsHandlerRegistered){ 328 | [self sendPendingNotifications]; 329 | } 330 | if(refreshToken != nil){ 331 | [self sendEventWithName:FCMTokenRefreshed body:refreshToken]; 332 | } 333 | }); 334 | 335 | return self; 336 | } 337 | 338 | -(void) addListener:(NSString *)eventName { 339 | [super addListener:eventName]; 340 | 341 | if([eventName isEqualToString:FCMNotificationReceived]) { 342 | [self sendPendingNotifications]; 343 | } else if([eventName isEqualToString:FCMTokenRefreshed] && refreshToken != nil) { 344 | [self sendEventWithName:FCMTokenRefreshed body:refreshToken]; 345 | refreshToken = nil; 346 | } 347 | } 348 | 349 | -(void) sendPendingNotifications { 350 | static dispatch_once_t onceToken; 351 | dispatch_once(&onceToken, ^{ 352 | jsHandlerRegistered = true; 353 | 354 | for (NSDictionary* data in pendingNotifications) { 355 | [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data]; 356 | } 357 | 358 | [pendingNotifications removeAllObjects]; 359 | 360 | }); 361 | } 362 | 363 | RCT_EXPORT_METHOD(enableDirectChannel) 364 | { 365 | [[FIRMessaging messaging] setShouldEstablishDirectChannel:@YES]; 366 | } 367 | 368 | RCT_EXPORT_METHOD(isDirectChannelEstablished:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 369 | { 370 | resolve([[FIRMessaging messaging] isDirectChannelEstablished] ? @YES: @NO); 371 | } 372 | 373 | RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 374 | { 375 | NSDictionary* initialNotif; 376 | NSDictionary *localUserInfo = [[self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] userInfo] mutableCopy]; 377 | 378 | NSDictionary *remoteUserInfo = [self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy]; 379 | if(localUserInfo){ 380 | initialNotif = localUserInfo; 381 | } else if (remoteUserInfo) { 382 | initialNotif = remoteUserInfo; 383 | } 384 | if (initialNotif) { 385 | [initialNotif setValue:@YES forKey:@"opened_from_tray"]; 386 | resolve(initialNotif); 387 | } else { 388 | resolve(nil); 389 | } 390 | } 391 | 392 | 393 | 394 | RCT_EXPORT_METHOD(getAPNSToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 395 | { 396 | NSData * deviceToken = [FIRMessaging messaging].APNSToken; 397 | const char *data = [deviceToken bytes]; 398 | NSMutableString *token = [NSMutableString string]; 399 | for (NSUInteger i = 0; i < [deviceToken length]; i++) { 400 | [token appendFormat:@"%02.2hhX", data[i]]; 401 | } 402 | resolve([token copy]); 403 | } 404 | 405 | RCT_EXPORT_METHOD(getFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 406 | { 407 | resolve([FIRMessaging messaging].FCMToken); 408 | } 409 | 410 | RCT_EXPORT_METHOD(getEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 411 | { 412 | FIROptions *options = FIROptions.defaultOptions; 413 | NSString *entity = options.GCMSenderID; 414 | NSData * deviceToken = [FIRMessaging messaging].APNSToken; 415 | 416 | if (deviceToken == nil) { 417 | resolve(nil); 418 | return; 419 | } 420 | 421 | [[FIRInstanceID instanceID]tokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging options:@{@"apns_token": deviceToken} handler:^(NSString * _Nullable token, NSError * _Nullable error) { 422 | 423 | if (error != nil) { 424 | reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); 425 | } else { 426 | resolve(token); 427 | } 428 | }]; 429 | } 430 | 431 | RCT_EXPORT_METHOD(deleteEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 432 | { 433 | FIROptions *options = FIROptions.defaultOptions;; 434 | NSString *entity = options.GCMSenderID; 435 | 436 | [[FIRInstanceID instanceID]deleteTokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging handler:^(NSError * _Nullable error) { 437 | 438 | if (error != nil) { 439 | reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); 440 | } else { 441 | resolve(nil); 442 | } 443 | }]; 444 | } 445 | 446 | RCT_EXPORT_METHOD(deleteInstanceId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 447 | { 448 | [[FIRInstanceID instanceID]deleteIDWithHandler:^(NSError * _Nullable error) { 449 | 450 | if (error != nil) { 451 | reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); 452 | } else { 453 | resolve(nil); 454 | } 455 | }]; 456 | } 457 | 458 | - (void)messaging:(nonnull FIRMessaging *)messaging didReceiveRegistrationToken:(nonnull NSString *)fcmToken { 459 | refreshToken = fcmToken; 460 | [self sendEventWithName:FCMTokenRefreshed body:fcmToken]; 461 | } 462 | 463 | - (void)messaging:(nonnull FIRMessaging *)messaging didRefreshRegistrationToken:(nonnull NSString *)fcmToken { 464 | refreshToken = fcmToken; 465 | [self sendEventWithName:FCMTokenRefreshed body:fcmToken]; 466 | } 467 | 468 | RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 469 | { 470 | if (RCTRunningInAppExtension()) { 471 | resolve(nil); 472 | return; 473 | } 474 | if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_9_x_Max) { 475 | UIUserNotificationType allNotificationTypes = 476 | (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge); 477 | UIApplication *app = RCTSharedApplication(); 478 | if ([app respondsToSelector:@selector(registerUserNotificationSettings:)]) { 479 | //iOS 8 or later 480 | UIUserNotificationSettings *notificationSettings = 481 | [UIUserNotificationSettings settingsForTypes:(NSUInteger)allNotificationTypes categories:nil]; 482 | [app registerUserNotificationSettings:notificationSettings]; 483 | } 484 | resolve(nil); 485 | } else { 486 | // iOS 10 or later 487 | #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 488 | UNAuthorizationOptions authOptions = 489 | UNAuthorizationOptionAlert 490 | | UNAuthorizationOptionSound 491 | | UNAuthorizationOptionBadge; 492 | [[UNUserNotificationCenter currentNotificationCenter] 493 | requestAuthorizationWithOptions:authOptions 494 | completionHandler:^(BOOL granted, NSError * _Nullable error) { 495 | if(granted){ 496 | resolve(nil); 497 | } else{ 498 | reject(@"notification_error", @"Failed to grant permission", error); 499 | } 500 | } 501 | ]; 502 | #endif 503 | } 504 | 505 | dispatch_async(dispatch_get_main_queue(), ^{ 506 | [[UIApplication sharedApplication] registerForRemoteNotifications]; 507 | }); 508 | } 509 | 510 | RCT_EXPORT_METHOD(subscribeToTopic: (NSString*) topic) 511 | { 512 | [[FIRMessaging messaging] subscribeToTopic:topic]; 513 | } 514 | 515 | RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) 516 | { 517 | [[FIRMessaging messaging] unsubscribeFromTopic:topic]; 518 | } 519 | 520 | - (void)messaging:(FIRMessaging *)messaging didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage { 521 | [self sendEventWithName:FCMNotificationReceived body:[remoteMessage appData]]; 522 | } 523 | 524 | RCT_EXPORT_METHOD(presentLocalNotification:(id)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 525 | { 526 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 527 | UNNotificationRequest* request = [RCTConvert UNNotificationRequest:data]; 528 | [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { 529 | if (!error) { 530 | resolve(nil); 531 | }else{ 532 | reject(@"notification_error", @"Failed to present local notificaton", error); 533 | } 534 | }]; 535 | }else{ 536 | UILocalNotification* notif = [RCTConvert UILocalNotification:data]; 537 | [RCTSharedApplication() presentLocalNotificationNow:notif]; 538 | resolve(nil); 539 | } 540 | } 541 | 542 | RCT_EXPORT_METHOD(scheduleLocalNotification:(id)data resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 543 | { 544 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 545 | UNNotificationRequest* request = [RCTConvert UNNotificationRequest:data]; 546 | [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { 547 | if (!error) { 548 | resolve(nil); 549 | }else{ 550 | reject(@"notification_error", @"Failed to present local notificaton", error); 551 | } 552 | }]; 553 | }else{ 554 | UILocalNotification* notif = [RCTConvert UILocalNotification:data]; 555 | [RCTSharedApplication() scheduleLocalNotification:notif]; 556 | resolve(nil); 557 | } 558 | } 559 | 560 | RCT_EXPORT_METHOD(removeDeliveredNotification:(NSString*) notificationId) 561 | { 562 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 563 | [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[notificationId]]; 564 | } 565 | } 566 | 567 | RCT_EXPORT_METHOD(removeAllDeliveredNotifications) 568 | { 569 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 570 | [[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications]; 571 | } else { 572 | dispatch_async(dispatch_get_main_queue(), ^{ 573 | [RCTSharedApplication() setApplicationIconBadgeNumber: 0]; 574 | }); 575 | } 576 | } 577 | 578 | RCT_EXPORT_METHOD(cancelAllLocalNotifications) 579 | { 580 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 581 | [[UNUserNotificationCenter currentNotificationCenter] removeAllPendingNotificationRequests]; 582 | } else { 583 | [RCTSharedApplication() cancelAllLocalNotifications]; 584 | } 585 | } 586 | 587 | RCT_EXPORT_METHOD(cancelLocalNotification:(NSString*) notificationId) 588 | { 589 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 590 | [[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[notificationId]]; 591 | }else { 592 | for (UILocalNotification *notification in [UIApplication sharedApplication].scheduledLocalNotifications) { 593 | NSDictionary *notificationInfo = notification.userInfo; 594 | if([notificationId isEqualToString:[notificationInfo valueForKey:@"id"]]){ 595 | [[UIApplication sharedApplication] cancelLocalNotification:notification]; 596 | } 597 | } 598 | } 599 | } 600 | 601 | RCT_EXPORT_METHOD(getScheduledLocalNotifications:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 602 | { 603 | if([UNUserNotificationCenter currentNotificationCenter] != nil){ 604 | [[UNUserNotificationCenter currentNotificationCenter] getPendingNotificationRequestsWithCompletionHandler:^(NSArray * _Nonnull requests) { 605 | NSMutableArray* list = [[NSMutableArray alloc] init]; 606 | for(UNNotificationRequest * notif in requests){ 607 | UNNotificationContent *content = notif.content; 608 | [list addObject:content.userInfo]; 609 | } 610 | resolve(list); 611 | }]; 612 | }else{ 613 | NSMutableArray* list = [[NSMutableArray alloc] init]; 614 | for(UILocalNotification * notif in [RCTSharedApplication() scheduledLocalNotifications]){ 615 | [list addObject:notif.userInfo]; 616 | } 617 | resolve(list); 618 | } 619 | } 620 | 621 | RCT_EXPORT_METHOD(setNotificationCategories:(NSArray *)categories) 622 | { 623 | if([UNUserNotificationCenter currentNotificationCenter] != nil) { 624 | NSMutableSet *categoriesSet = [[NSMutableSet alloc] init]; 625 | 626 | for(NSDictionary *categoryDict in categories) { 627 | UNNotificationCategory *category = [RCTConvert UNNotificationCategory:categoryDict]; 628 | [categoriesSet addObject:category]; 629 | } 630 | 631 | [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categoriesSet]; 632 | } 633 | } 634 | 635 | RCT_EXPORT_METHOD(setBadgeNumber: (NSInteger) number) 636 | { 637 | dispatch_async(dispatch_get_main_queue(), ^{ 638 | [RCTSharedApplication() setApplicationIconBadgeNumber:number]; 639 | }); 640 | } 641 | 642 | RCT_EXPORT_METHOD(getBadgeNumber: (RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) 643 | { 644 | resolve(@([RCTSharedApplication() applicationIconBadgeNumber])); 645 | } 646 | 647 | RCT_EXPORT_METHOD(send:(NSString*)senderId withPayload:(NSDictionary *)message) 648 | { 649 | NSMutableDictionary * mMessage = [message mutableCopy]; 650 | NSMutableDictionary * upstreamMessage = [[NSMutableDictionary alloc] init]; 651 | for (NSString* key in mMessage) { 652 | upstreamMessage[key] = [NSString stringWithFormat:@"%@", [mMessage valueForKey:key]]; 653 | } 654 | 655 | NSDictionary *imMessage = [NSDictionary dictionaryWithDictionary:upstreamMessage]; 656 | 657 | int64_t ttl = 3600; 658 | NSString * receiver = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId]; 659 | 660 | NSUUID *uuid = [NSUUID UUID]; 661 | NSString * messageID = [uuid UUIDString]; 662 | 663 | [[FIRMessaging messaging]sendMessage:imMessage to:receiver withMessageID:messageID timeToLive:ttl]; 664 | } 665 | 666 | RCT_EXPORT_METHOD(finishRemoteNotification: (NSString *)completionHandlerId fetchResult:(UIBackgroundFetchResult)result){ 667 | RCTRemoteNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId]; 668 | if (!completionHandler) { 669 | RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); 670 | return; 671 | } 672 | completionHandler(result); 673 | [self.notificationCallbacks removeObjectForKey:completionHandlerId]; 674 | } 675 | 676 | RCT_EXPORT_METHOD(finishWillPresentNotification: (NSString *)completionHandlerId fetchResult:(UNNotificationPresentationOptions)result){ 677 | RCTWillPresentNotificationCallback completionHandler = self.notificationCallbacks[completionHandlerId]; 678 | if (!completionHandler) { 679 | RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); 680 | return; 681 | } 682 | completionHandler(result); 683 | [self.notificationCallbacks removeObjectForKey:completionHandlerId]; 684 | } 685 | 686 | RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId){ 687 | RCTNotificationResponseCallback completionHandler = self.notificationCallbacks[completionHandlerId]; 688 | if (!completionHandler) { 689 | RCTLogError(@"There is no completion handler with completionHandlerId: %@", completionHandlerId); 690 | return; 691 | } 692 | completionHandler(); 693 | [self.notificationCallbacks removeObjectForKey:completionHandlerId]; 694 | } 695 | 696 | - (void)handleNotificationReceived:(NSNotification *)notification 697 | { 698 | id completionHandler = notification.userInfo[@"completionHandler"]; 699 | NSMutableDictionary* data = notification.userInfo[@"data"]; 700 | if(completionHandler != nil){ 701 | NSString *completionHandlerId = [[NSUUID UUID] UUIDString]; 702 | if (!self.notificationCallbacks) { 703 | // Lazy initialization 704 | self.notificationCallbacks = [NSMutableDictionary dictionary]; 705 | } 706 | self.notificationCallbacks[completionHandlerId] = completionHandler; 707 | data[@"_completionHandlerId"] = completionHandlerId; 708 | } 709 | [self sendEventWithName:FCMNotificationReceived body:data]; 710 | } 711 | 712 | - (void)sendDataMessageFailure:(NSNotification *)notification 713 | { 714 | NSString *messageID = (NSString *)notification.userInfo[@"messageID"]; 715 | 716 | NSLog(@"sendDataMessageFailure: %@", messageID); 717 | } 718 | 719 | - (void)sendDataMessageSuccess:(NSNotification *)notification 720 | { 721 | NSString *messageID = (NSString *)notification.userInfo[@"messageID"]; 722 | 723 | NSLog(@"sendDataMessageSuccess: %@", messageID); 724 | } 725 | 726 | - (void)connectionStateChanged:(NSNotification *)notification 727 | { 728 | [self sendEventWithName:FCMDirectChannelConnectionChanged body:[FIRMessaging messaging].isDirectChannelEstablished ? @YES: @NO]; 729 | NSLog(@"connectionStateChanged: %@", [FIRMessaging messaging].isDirectChannelEstablished ? @"connected": @"disconnected"); 730 | } 731 | 732 | @end 733 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3AD1DC2B1CFA802F008C092E /* RNFIRMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = 3AD1DC2A1CFA802F008C092E /* RNFIRMessaging.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRNFIRMessaging.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFIRMessaging.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 3AD1DC291CFA802F008C092E /* RNFIRMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFIRMessaging.h; sourceTree = ""; }; 28 | 3AD1DC2A1CFA802F008C092E /* RNFIRMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFIRMessaging.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRNFIRMessaging.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 3AD1DC291CFA802F008C092E /* RNFIRMessaging.h */, 54 | 3AD1DC2A1CFA802F008C092E /* RNFIRMessaging.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | indentWidth = 2; 58 | sourceTree = ""; 59 | tabWidth = 2; 60 | }; 61 | /* End PBXGroup section */ 62 | 63 | /* Begin PBXNativeTarget section */ 64 | 58B511DA1A9E6C8500147676 /* RNFIRMessaging */ = { 65 | isa = PBXNativeTarget; 66 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNFIRMessaging" */; 67 | buildPhases = ( 68 | 58B511D71A9E6C8500147676 /* Sources */, 69 | 58B511D81A9E6C8500147676 /* Frameworks */, 70 | 58B511D91A9E6C8500147676 /* CopyFiles */, 71 | ); 72 | buildRules = ( 73 | ); 74 | dependencies = ( 75 | ); 76 | name = RNFIRMessaging; 77 | productName = RCTDataManager; 78 | productReference = 134814201AA4EA6300B7C361 /* libRNFIRMessaging.a */; 79 | productType = "com.apple.product-type.library.static"; 80 | }; 81 | /* End PBXNativeTarget section */ 82 | 83 | /* Begin PBXProject section */ 84 | 58B511D31A9E6C8500147676 /* Project object */ = { 85 | isa = PBXProject; 86 | attributes = { 87 | LastUpgradeCheck = 0610; 88 | ORGANIZATIONNAME = Facebook; 89 | TargetAttributes = { 90 | 58B511DA1A9E6C8500147676 = { 91 | CreatedOnToolsVersion = 6.1.1; 92 | }; 93 | }; 94 | }; 95 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNFIRMessaging" */; 96 | compatibilityVersion = "Xcode 3.2"; 97 | developmentRegion = English; 98 | hasScannedForEncodings = 0; 99 | knownRegions = ( 100 | en, 101 | ); 102 | mainGroup = 58B511D21A9E6C8500147676; 103 | productRefGroup = 58B511D21A9E6C8500147676; 104 | projectDirPath = ""; 105 | projectRoot = ""; 106 | targets = ( 107 | 58B511DA1A9E6C8500147676 /* RNFIRMessaging */, 108 | ); 109 | }; 110 | /* End PBXProject section */ 111 | 112 | /* Begin PBXSourcesBuildPhase section */ 113 | 58B511D71A9E6C8500147676 /* Sources */ = { 114 | isa = PBXSourcesBuildPhase; 115 | buildActionMask = 2147483647; 116 | files = ( 117 | 3AD1DC2B1CFA802F008C092E /* RNFIRMessaging.m in Sources */, 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | /* End PBXSourcesBuildPhase section */ 122 | 123 | /* Begin XCBuildConfiguration section */ 124 | 58B511ED1A9E6C8500147676 /* Debug */ = { 125 | isa = XCBuildConfiguration; 126 | buildSettings = { 127 | ALWAYS_SEARCH_USER_PATHS = NO; 128 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 129 | CLANG_CXX_LIBRARY = "libc++"; 130 | CLANG_ENABLE_MODULES = YES; 131 | CLANG_ENABLE_OBJC_ARC = YES; 132 | CLANG_WARN_BOOL_CONVERSION = YES; 133 | CLANG_WARN_CONSTANT_CONVERSION = YES; 134 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 135 | CLANG_WARN_EMPTY_BODY = YES; 136 | CLANG_WARN_ENUM_CONVERSION = YES; 137 | CLANG_WARN_INT_CONVERSION = YES; 138 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 139 | CLANG_WARN_UNREACHABLE_CODE = YES; 140 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 141 | COPY_PHASE_STRIP = NO; 142 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 143 | ENABLE_STRICT_OBJC_MSGSEND = YES; 144 | GCC_C_LANGUAGE_STANDARD = gnu99; 145 | GCC_DYNAMIC_NO_PIC = NO; 146 | GCC_OPTIMIZATION_LEVEL = 0; 147 | GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; 148 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 149 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 150 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 151 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 152 | GCC_WARN_SHADOW = YES; 153 | GCC_WARN_UNDECLARED_SELECTOR = YES; 154 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 155 | GCC_WARN_UNUSED_FUNCTION = YES; 156 | GCC_WARN_UNUSED_VARIABLE = YES; 157 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 158 | MTL_ENABLE_DEBUG_INFO = YES; 159 | ONLY_ACTIVE_ARCH = YES; 160 | SDKROOT = iphoneos; 161 | WARNING_CFLAGS = ( 162 | "-Wextra", 163 | "-Wall", 164 | "-Wno-semicolon-before-method-body", 165 | "-Wno-unused-parameter", 166 | ); 167 | }; 168 | name = Debug; 169 | }; 170 | 58B511EE1A9E6C8500147676 /* Release */ = { 171 | isa = XCBuildConfiguration; 172 | buildSettings = { 173 | ALWAYS_SEARCH_USER_PATHS = NO; 174 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 175 | CLANG_CXX_LIBRARY = "libc++"; 176 | CLANG_ENABLE_MODULES = YES; 177 | CLANG_ENABLE_OBJC_ARC = YES; 178 | CLANG_WARN_BOOL_CONVERSION = YES; 179 | CLANG_WARN_CONSTANT_CONVERSION = YES; 180 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 181 | CLANG_WARN_EMPTY_BODY = YES; 182 | CLANG_WARN_ENUM_CONVERSION = YES; 183 | CLANG_WARN_INT_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN_UNREACHABLE_CODE = YES; 186 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 187 | COPY_PHASE_STRIP = YES; 188 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 189 | ENABLE_NS_ASSERTIONS = NO; 190 | ENABLE_STRICT_OBJC_MSGSEND = YES; 191 | GCC_C_LANGUAGE_STANDARD = gnu99; 192 | GCC_PREPROCESSOR_DEFINITIONS = ""; 193 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 194 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 195 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 196 | GCC_WARN_SHADOW = YES; 197 | GCC_WARN_UNDECLARED_SELECTOR = YES; 198 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 199 | GCC_WARN_UNUSED_FUNCTION = YES; 200 | GCC_WARN_UNUSED_VARIABLE = YES; 201 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 202 | MTL_ENABLE_DEBUG_INFO = NO; 203 | SDKROOT = iphoneos; 204 | VALIDATE_PRODUCT = YES; 205 | WARNING_CFLAGS = ( 206 | "-Wextra", 207 | "-Wall", 208 | "-Wno-semicolon-before-method-body", 209 | "-Wno-unused-parameter", 210 | ); 211 | }; 212 | name = Release; 213 | }; 214 | 58B511F01A9E6C8500147676 /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 218 | CLANG_STATIC_ANALYZER_MODE = deep; 219 | FRAMEWORK_SEARCH_PATHS = ( 220 | "$(inherited)", 221 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 222 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseCore/**", 223 | "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", 224 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseMessaging/**", 225 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseInstanceID/**", 226 | ); 227 | GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; 228 | HEADER_SEARCH_PATHS = ( 229 | "$(SRCROOT)/../../react-native/React/**", 230 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 231 | "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", 232 | "$(PROJECT_DIR)/../../../ios/Pods/Firebase/**", 233 | "$(PROJECT_DIR)/../../../ios/Pods/Headers/Public/**", 234 | ); 235 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 236 | OTHER_LDFLAGS = "-ObjC"; 237 | PRODUCT_NAME = RNFIRMessaging; 238 | RUN_CLANG_STATIC_ANALYZER = YES; 239 | SKIP_INSTALL = YES; 240 | }; 241 | name = Debug; 242 | }; 243 | 58B511F11A9E6C8500147676 /* Release */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 247 | CLANG_STATIC_ANALYZER_MODE = deep; 248 | FRAMEWORK_SEARCH_PATHS = ( 249 | "$(inherited)", 250 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 251 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseCore/**", 252 | "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", 253 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseMessaging/**", 254 | "$(PROJECT_DIR)/../../../ios/Pods/FirebaseInstanceID/**", 255 | ); 256 | HEADER_SEARCH_PATHS = ( 257 | "$(SRCROOT)/../../react-native/React/**", 258 | "$(PROJECT_DIR)/../../../ios/Frameworks/**", 259 | "$(SRCROOT)/../../../node_modules/react-native-firestack/ios/**", 260 | "$(PROJECT_DIR)/../../../ios/Pods/Firebase/**", 261 | "$(PROJECT_DIR)/../../../ios/Pods/Headers/Public/**", 262 | ); 263 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 264 | OTHER_LDFLAGS = "-ObjC"; 265 | PRODUCT_NAME = RNFIRMessaging; 266 | SKIP_INSTALL = YES; 267 | }; 268 | name = Release; 269 | }; 270 | /* End XCBuildConfiguration section */ 271 | 272 | /* Begin XCConfigurationList section */ 273 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNFIRMessaging" */ = { 274 | isa = XCConfigurationList; 275 | buildConfigurations = ( 276 | 58B511ED1A9E6C8500147676 /* Debug */, 277 | 58B511EE1A9E6C8500147676 /* Release */, 278 | ); 279 | defaultConfigurationIsVisible = 0; 280 | defaultConfigurationName = Release; 281 | }; 282 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNFIRMessaging" */ = { 283 | isa = XCConfigurationList; 284 | buildConfigurations = ( 285 | 58B511F01A9E6C8500147676 /* Debug */, 286 | 58B511F11A9E6C8500147676 /* Release */, 287 | ); 288 | defaultConfigurationIsVisible = 0; 289 | defaultConfigurationName = Release; 290 | }; 291 | /* End XCConfigurationList section */ 292 | }; 293 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 294 | } 295 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/project.xcworkspace/xcuserdata/libinlu.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evollu/react-native-fcm/41014d82beb175b723d9e17c05463ee3dc087a27/ios/RNFIRMessaging.xcodeproj/project.xcworkspace/xcuserdata/libinlu.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/xcuserdata/LLu.xcuserdatad/xcschemes/RNFIRMessaging.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/xcuserdata/LLu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RNFIRMessaging.xcscheme 8 | 9 | orderHint 10 | 4 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/xcuserdata/libinlu.xcuserdatad/xcschemes/RCTPushNotification.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ios/RNFIRMessaging.xcodeproj/xcuserdata/libinlu.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | RCTPushNotification.xcscheme 8 | 9 | orderHint 10 | 9 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 58B511DA1A9E6C8500147676 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "name": "Libin Lu" 4 | }, 5 | "bugs": { 6 | "url": "https://github.com/evollu/react-native-fcm/issues" 7 | }, 8 | "description": "React Native bridge for firebase cloud messaging (FCM)", 9 | "peerDependencies": { 10 | "react-native": ">=0.40.0" 11 | }, 12 | "homepage": "https://github.com/evollu/react-native-fcm", 13 | "keywords": [ 14 | "React-Native", 15 | "ios", 16 | "android", 17 | "fcm", 18 | "firebase", 19 | "cloud messaging" 20 | ], 21 | "license": "MIT", 22 | "name": "react-native-fcm", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/evollu/react-native-fcm.git" 26 | }, 27 | "version": "16.2.4" 28 | } 29 | -------------------------------------------------------------------------------- /react-native-fcm.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | package = JSON.parse(File.read('package.json')) 3 | 4 | Pod::Spec.new do |s| 5 | s.name = package['name'] 6 | s.version = package['version'] 7 | s.summary = package['description'] 8 | s.author = "Libin Lu" 9 | s.license = package['license'] 10 | s.requires_arc = true 11 | s.homepage = "https://github.com/evollu/react-native-fcm" 12 | s.source = { :git => 'https://github.com/evollu/react-native-fcm.git' } 13 | s.platform = :ios, '8.0' 14 | s.source_files = "ios/*.{h,m}" 15 | s.public_header_files = ['ios/RNFIRMessaging.h'] 16 | s.static_framework = true 17 | 18 | s.dependency "React" 19 | s.dependency "Firebase/Messaging" 20 | end 21 | --------------------------------------------------------------------------------