Commit e7f0ad93 authored by Libin Lu's avatar Libin Lu

merge from master

parents 1f1da63f 56f8bbc0
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
- Change the _Bundle Identifier_ in Xcode project settings - Change the _Bundle Identifier_ in Xcode project settings
- Select your _Team_ for both targets (`SimpleFcmClient` and `SimpleFcmClientTests`) - Select your _Team_ for both targets (`SimpleFcmClient` and `SimpleFcmClientTests`)
- Update your API_KEY [here](https://github.com/evollu/react-native-fcm/blob/master/Examples/simple-fcm-client/app/FirebaseConstants.js#L3) - Update your API_KEY [here](https://github.com/evollu/react-native-fcm/blob/master/Examples/simple-fcm-client/app/FirebaseConstants.js#L3)
- run `pod install` under `ios` folder
## Android ## Android
......
...@@ -133,6 +133,10 @@ android { ...@@ -133,6 +133,10 @@ android {
dependencies { dependencies {
compile(project(':react-native-maps')) { compile(project(':react-native-maps')) {
exclude group: 'com.google.android.gms', module: 'play-services-base' exclude group: 'com.google.android.gms', module: 'play-services-base'
// This resolution make compiler ignoring play-service-base's version requirement in react-native-maps
// so that it only read from react-native-fcm
// you can also lock the version in this gradle file and ignore all module declaration
// or you can use ResolutionStragety
} }
compile project(':react-native-fcm') compile project(':react-native-fcm')
compile fileTree(dir: "libs", include: ["*.jar"]) compile fileTree(dir: "libs", include: ["*.jar"])
......
...@@ -10,6 +10,7 @@ import com.facebook.react.ReactInstanceManager; ...@@ -10,6 +10,7 @@ import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage; import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
...@@ -36,4 +37,10 @@ public class MainApplication extends Application implements ReactApplication { ...@@ -36,4 +37,10 @@ public class MainApplication extends Application implements ReactApplication {
public ReactNativeHost getReactNativeHost() { public ReactNativeHost getReactNativeHost() {
return mReactNativeHost; return mReactNativeHost;
} }
@Override
public void onCreate() { // <-- Check this block exists
super.onCreate();
SoLoader.init(this, /* native exopackage */ false); // <-- Check this line exists within the block
}
} }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* @flow * @flow
*/ */
import React, { Component } from 'react'; import React, { Component } from "react";
import { import {
StyleSheet, StyleSheet,
Text, Text,
...@@ -13,14 +13,14 @@ import { ...@@ -13,14 +13,14 @@ import {
Clipboard, Clipboard,
Platform, Platform,
ScrollView ScrollView
} from 'react-native'; } from "react-native";
import { StackNavigator } from 'react-navigation'; import { StackNavigator } from "react-navigation";
import FCM, {NotificationActionType} from "react-native-fcm"; import FCM, { NotificationActionType } from "react-native-fcm";
import {registerKilledListener, registerAppListener} from "./Listeners"; import { registerKilledListener, registerAppListener } from "./Listeners";
import firebaseClient from "./FirebaseClient"; import firebaseClient from "./FirebaseClient";
registerKilledListener(); registerKilledListener();
...@@ -31,10 +31,10 @@ class MainPage extends Component { ...@@ -31,10 +31,10 @@ class MainPage extends Component {
this.state = { this.state = {
token: "", token: "",
tokenCopyFeedback: "" tokenCopyFeedback: ""
} };
} }
async componentDidMount(){ async componentDidMount() {
FCM.createNotificationChannel({ FCM.createNotificationChannel({
id: 'default', id: 'default',
name: 'Default', name: 'Default',
...@@ -45,75 +45,87 @@ class MainPage extends Component { ...@@ -45,75 +45,87 @@ class MainPage extends Component {
FCM.getInitialNotification().then(notif => { FCM.getInitialNotification().then(notif => {
this.setState({ this.setState({
initNotif: notif initNotif: notif
}) });
if(notif && notif.targetScreen === 'detail'){ if (notif && notif.targetScreen === "detail") {
setTimeout(()=>{ setTimeout(() => {
this.props.navigation.navigate('Detail') this.props.navigation.navigate("Detail");
}, 500) }, 500);
} }
}); });
try{ try {
let result = await FCM.requestPermissions({badge: false, sound: true, alert: true}); let result = await FCM.requestPermissions({
} catch(e){ badge: false,
sound: true,
alert: true
});
} catch (e) {
console.error(e); console.error(e);
} }
FCM.getFCMToken().then(token => { FCM.getFCMToken().then(token => {
console.log("TOKEN (getFCMToken)", token); console.log("TOKEN (getFCMToken)", token);
this.setState({token: token || ""}) this.setState({ token: token || "" });
}); });
if(Platform.OS === 'ios'){ if (Platform.OS === "ios") {
FCM.getAPNSToken().then(token => { FCM.getAPNSToken().then(token => {
console.log("APNS TOKEN (getFCMToken)", token); console.log("APNS TOKEN (getFCMToken)", token);
}); });
} }
// topic example
// FCM.subscribeToTopic('sometopic')
// FCM.unsubscribeFromTopic('sometopic')
} }
showLocalNotification() { showLocalNotification() {
FCM.presentLocalNotification({ FCM.presentLocalNotification({
channel: 'default', channel: 'default',
id: new Date().valueOf().toString(), // (optional for instant notification) id: new Date().valueOf().toString(), // (optional for instant notification)
title: "Test Notification with action", // as FCM payload title: "Test Notification with action", // as FCM payload
body: "Force touch to reply", // as FCM payload (required) body: "Force touch to reply", // as FCM payload (required)
sound: "bell.mp3", // "default" or filename sound: "bell.mp3", // "default" or filename
priority: "high", // as FCM payload priority: "high", // as FCM payload
click_action: "com.myapp.MyCategory", // as FCM payload - this is used as category identifier on iOS. click_action: "com.myapp.MyCategory", // as FCM payload - this is used as category identifier on iOS.
badge: 10, // as FCM payload IOS only, set 0 to clear badges badge: 10, // as FCM payload IOS only, set 0 to clear badges
number: 10, // Android only number: 10, // Android only
ticker: "My Notification Ticker", // Android only ticker: "My Notification Ticker", // Android only
auto_cancel: true, // Android only (default true) auto_cancel: true, // Android only (default true)
large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", // Android only large_icon:
icon: "ic_launcher", // as FCM payload, you can relace this with custom icon you put in mipmap "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", // Android only
big_text: "Show when notification is expanded", // Android only icon: "ic_launcher", // as FCM payload, you can relace this with custom icon you put in mipmap
sub_text: "This is a subText", // Android only big_text: "Show when notification is expanded", // Android only
color: "red", // Android only sub_text: "This is a subText", // Android only
vibrate: 300, // Android only default: 300, no vibration if you pass 0 color: "red", // Android only
wake_screen: true, // Android only, wake up screen when notification arrives vibrate: 300, // Android only default: 300, no vibration if you pass 0
group: "group", // Android only wake_screen: true, // Android only, wake up screen when notification arrives
picture: "https://google.png", // Android only bigPicture style group: "group", // Android only
ongoing: true, // Android only picture:
my_custom_data:'my_custom_field_value', // extra data you want to throw "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png", // Android only bigPicture style
lights: true, // Android only, LED blinking (default false) ongoing: true, // Android only
show_in_foreground: true // notification when app is in foreground (local & remote) my_custom_data: "my_custom_field_value", // extra data you want to throw
lights: true, // Android only, LED blinking (default false)
show_in_foreground: true // notification when app is in foreground (local & remote)
}); });
} }
scheduleLocalNotification() { scheduleLocalNotification() {
FCM.scheduleLocalNotification({ FCM.scheduleLocalNotification({
id: 'testnotif', id: "testnotif",
fire_date: new Date().getTime()+5000, fire_date: new Date().getTime() + 5000,
vibrate: 500, vibrate: 500,
title: 'Hello', title: "Hello",
body: 'Test Scheduled Notification', body: "Test Scheduled Notification",
sub_text: 'sub text', sub_text: "sub text",
priority: "high", priority: "high",
large_icon: "https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg", large_icon:
"https://image.freepik.com/free-icon/small-boy-cartoon_318-38077.jpg",
show_in_foreground: true, show_in_foreground: true,
picture: 'https://firebase.google.com/_static/af7ae4b3fc/images/firebase/lockup.png', picture:
"https://firebase.google.com/_static/af7ae4b3fc/images/firebase/lockup.png",
wake_screen: true, wake_screen: true,
extra1: {a: 1}, extra1: { a: 1 },
extra2: 1 extra2: 1
}); });
} }
...@@ -121,67 +133,70 @@ class MainPage extends Component { ...@@ -121,67 +133,70 @@ class MainPage extends Component {
sendRemoteNotification(token) { sendRemoteNotification(token) {
let body; let body;
if(Platform.OS === 'android'){ if (Platform.OS === "android") {
body = { body = {
"to": token, to: token,
"data":{ data: {
"custom_notification": { custom_notification: {
"title": "Simple FCM Client", title: "Simple FCM Client",
"body": "Click me to go to detail", body: "Click me to go to detail",
"sound": "default", sound: "default",
"priority": "high", priority: "high",
"show_in_foreground": true, show_in_foreground: true,
targetScreen: 'detail' targetScreen: "detail"
} }
}, },
"priority": 10 priority: 10
} };
} else { } else {
body = { body = {
"to": token, to: token,
"notification":{ notification: {
"title": "Simple FCM Client", title: "Simple FCM Client",
"body": "Click me to go to detail", body: "Click me to go to detail",
"sound": "default" sound: "default"
}, },
data: { data: {
targetScreen: 'detail' targetScreen: "detail"
}, },
"priority": 10 priority: 10
} };
} }
firebaseClient.send(JSON.stringify(body), "notification"); firebaseClient.send(JSON.stringify(body), "notification");
} }
sendRemoteData(token) { sendRemoteData(token) {
let body = { let body = {
"to": token, to: token,
"data":{ data: {
"title": "Simple FCM Client", title: "Simple FCM Client",
"body": "This is a notification with only DATA.", body: "This is a notification with only DATA.",
"sound": "default" sound: "default"
}, },
"priority": "normal" priority: "normal"
} };
firebaseClient.send(JSON.stringify(body), "data"); firebaseClient.send(JSON.stringify(body), "data");
} }
showLocalNotificationWithAction() { showLocalNotificationWithAction() {
FCM.presentLocalNotification({ FCM.presentLocalNotification({
title: 'Test Notification with action', title: "Test Notification with action",
body: 'Force touch to reply', body: "Force touch to reply",
priority: "high", priority: "high",
show_in_foreground: true, show_in_foreground: true,
click_action: "com.myidentifi.fcm.text", // for ios click_action: "com.myidentifi.fcm.text", // for ios
android_actions: JSON.stringify([{ android_actions: JSON.stringify([
id: "view", {
title: 'view' id: "view",
},{ title: "view"
id: "dismiss", },
title: 'dismiss' {
}]) // for android, take syntax similar to ios's. only buttons are supported id: "dismiss",
title: "dismiss"
}
]) // for android, take syntax similar to ios's. only buttons are supported
}); });
} }
...@@ -190,52 +205,62 @@ class MainPage extends Component { ...@@ -190,52 +205,62 @@ class MainPage extends Component {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ScrollView style={{paddingHorizontal: 20}}> <ScrollView style={{ paddingHorizontal: 20 }}>
<Text style={styles.welcome}> <Text style={styles.welcome}>Welcome to Simple Fcm Client!</Text>
Welcome to Simple Fcm Client!
</Text> <Text style={styles.feedback}>{this.state.tokenCopyFeedback}</Text>
<Text style={styles.feedback}> <Text style={styles.feedback}>
{this.state.tokenCopyFeedback} Remote notif won't be available to iOS emulators
</Text> </Text>
<Text style={styles.feedback}> <TouchableOpacity
Remote notif won't be available to iOS emulators onPress={() => this.sendRemoteNotification(token)}
</Text> style={styles.button}
>
<TouchableOpacity onPress={() => this.sendRemoteNotification(token)} style={styles.button}> <Text style={styles.buttonText}>Send Remote Notification</Text>
<Text style={styles.buttonText}>Send Remote Notification</Text> </TouchableOpacity>
</TouchableOpacity>
<TouchableOpacity
<TouchableOpacity onPress={() => this.sendRemoteData(token)} style={styles.button}> onPress={() => this.sendRemoteData(token)}
<Text style={styles.buttonText}>Send Remote Data</Text> style={styles.button}
</TouchableOpacity> >
<Text style={styles.buttonText}>Send Remote Data</Text>
<TouchableOpacity onPress={() => this.showLocalNotification()} style={styles.button}> </TouchableOpacity>
<Text style={styles.buttonText}>Show Local Notification</Text>
</TouchableOpacity> <TouchableOpacity
onPress={() => this.showLocalNotification()}
<TouchableOpacity onPress={() => this.showLocalNotificationWithAction(token)} style={styles.button}> style={styles.button}
<Text style={styles.buttonText}>Show Local Notification with Action</Text> >
</TouchableOpacity> <Text style={styles.buttonText}>Show Local Notification</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.scheduleLocalNotification()} style={styles.button}>
<Text style={styles.buttonText}>Schedule Notification in 5s</Text> <TouchableOpacity
</TouchableOpacity> onPress={() => this.showLocalNotificationWithAction(token)}
style={styles.button}
<Text style={styles.instructions}> >
Init notif: <Text style={styles.buttonText}>
</Text> Show Local Notification with Action
<Text> </Text>
{JSON.stringify(this.state.initNotif)} </TouchableOpacity>
</Text>
<TouchableOpacity
<Text style={styles.instructions}> onPress={() => this.scheduleLocalNotification()}
Token: style={styles.button}
</Text> >
<Text selectable={true} onPress={() => this.setClipboardContent(this.state.token)}> <Text style={styles.buttonText}>Schedule Notification in 5s</Text>
{this.state.token} </TouchableOpacity>
</Text>
<Text style={styles.instructions}>Init notif:</Text>
<Text>{JSON.stringify(this.state.initNotif)}</Text>
<Text style={styles.instructions}>Token:</Text>
<Text
selectable={true}
onPress={() => this.setClipboardContent(this.state.token)}
>
{this.state.token}
</Text>
</ScrollView> </ScrollView>
</View> </View>
); );
...@@ -243,55 +268,62 @@ class MainPage extends Component { ...@@ -243,55 +268,62 @@ class MainPage extends Component {
setClipboardContent(text) { setClipboardContent(text) {
Clipboard.setString(text); Clipboard.setString(text);
this.setState({tokenCopyFeedback: "Token copied to clipboard."}); this.setState({ tokenCopyFeedback: "Token copied to clipboard." });
setTimeout(() => {this.clearTokenCopyFeedback()}, 2000); setTimeout(() => {
this.clearTokenCopyFeedback();
}, 2000);
} }
clearTokenCopyFeedback() { clearTokenCopyFeedback() {
this.setState({tokenCopyFeedback: ""}); this.setState({ tokenCopyFeedback: "" });
} }
} }
class DetailPage extends Component { class DetailPage extends Component {
render(){ render() {
return <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> return (
<Text>Detail page</Text> <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
</View> <Text>Detail page</Text>
</View>
);
} }
} }
export default StackNavigator({ export default StackNavigator(
Main: { {
screen: MainPage, Main: {
screen: MainPage
},
Detail: {
screen: DetailPage
}
}, },
Detail: { {
screen: DetailPage initialRouteName: "Main"
} }
}, { );
initialRouteName: 'Main',
});
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
justifyContent: 'center', justifyContent: "center",
alignItems: 'center', alignItems: "center",
backgroundColor: '#F5FCFF', backgroundColor: "#F5FCFF"
}, },
welcome: { welcome: {
fontSize: 20, fontSize: 20,
textAlign: 'center', textAlign: "center",
margin: 10, margin: 10
}, },
instructions: { instructions: {
textAlign: 'center', textAlign: "center",
color: '#333333', color: "#333333",
marginBottom: 2, marginBottom: 2
}, },
feedback: { feedback: {
textAlign: 'center', textAlign: "center",
color: '#996633', color: "#996633",
marginBottom: 3, marginBottom: 3
}, },
button: { button: {
backgroundColor: "teal", backgroundColor: "teal",
...@@ -303,5 +335,5 @@ const styles = StyleSheet.create({ ...@@ -303,5 +335,5 @@ const styles = StyleSheet.create({
buttonText: { buttonText: {
color: "white", color: "white",
backgroundColor: "transparent" backgroundColor: "transparent"
}, }
}); });
...@@ -6,7 +6,7 @@ const API_URL = "https://fcm.googleapis.com/fcm/send"; ...@@ -6,7 +6,7 @@ const API_URL = "https://fcm.googleapis.com/fcm/send";
class FirebaseClient { class FirebaseClient {
async send(body, type) { async send(body, type) {
if(FirebaseClient.KEY === 'YOUR_API_KEY'){ if(FirebaseConstants.KEY === 'YOUR_API_KEY'){
Alert.alert('Set your API_KEY in app/FirebaseConstants.js') Alert.alert('Set your API_KEY in app/FirebaseConstants.js')
return; return;
} }
......
...@@ -49,7 +49,7 @@ export function registerAppListener(navigation){ ...@@ -49,7 +49,7 @@ export function registerAppListener(navigation){
console.log("Notification", notif); console.log("Notification", notif);
if(Platform.OS ==='ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification){ if(Platform.OS ==='ios' && notif._notificationType === NotificationType.WillPresent && !notif.local_notification){
// this notification is only to decide if you want to show the notification when user if in forground. // this notification is only to decide if you want to show the notification when user if in foreground.
// usually you can ignore it. just decide to show or not. // usually you can ignore it. just decide to show or not.
notif.finish(WillPresentNotificationResult.All) notif.finish(WillPresentNotificationResult.All)
return; return;
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
}, },
"dependencies": { "dependencies": {
"react": "16.0.0-alpha.12", "react": "16.0.0-alpha.12",
"react-native": "^0.47.2", "react-native": "^0.55.4",
"react-native-fcm": "^14.1.0", "react-native-fcm": "^15.0.1",
"react-native-maps": "^0.20.1", "react-native-maps": "^0.20.1",
"react-navigation": "^1.2.1" "react-navigation": "^1.2.1"
}, },
......
This source diff could not be displayed because it is too large. You can view the blob instead.
[![Join the chat at https://gitter.im/evollu/react-native-fcm](https://badges.gitter.im/evollu/react-native-fcm.svg)](https://gitter.im/evollu/react-native-fcm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/evollu/react-native-fcm](https://badges.gitter.im/evollu/react-native-fcm.svg)](https://gitter.im/evollu/react-native-fcm?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## NOTES: ## NOTES:
- current latest version: v10.x [react-native-firebase](https://github.com/invertase/react-native-firebase/releases/tag/v4.0.0) has introduced new firebase messaging and remote/local notification features. It has good integration with other firebase features. I would recommend new comers to check that.
Note that there are some advanced features still in progress
- handle iOS remote notification when app is not running
- custom iOS notification actions
### To existing react-native-fcm users
`react-native-firebase` now can do what `react-native-fcm` can so it is a waste of effort to build the same thing in parallel.
Since I'm getting busier these days and start feeling challenging to maintain this repo every day while `react-native-firebase` has a larger team/company backing it, existing users may consider migrating to `react-native-firebase`.
I've created [an example project](https://github.com/evollu/react-native-fcm/tree/firebase/Examples/firebase-migration) using react-native-firebase as a migration reference
`react-native-fcm` will still take PRs and bug fixes, but possibly no new feature developement any more.
### Versions
- for iOS SDK < 4, use react-native-fcm@6.2.3 (v6.x is still compatible with Firebase SDK v4) - for iOS SDK < 4, use react-native-fcm@6.2.3 (v6.x is still compatible with Firebase SDK v4)
- for RN < 0.40.0, use react-native-fcm@2.5.6 - for RN < 0.40.0, use react-native-fcm@2.5.6
- for RN < 0.33.0, use react-native-fcm@1.1.0 - for RN < 0.33.0, use react-native-fcm@1.1.0
- for RN < 0.30.0, use react-native-fcm@1.0.15 - for RN < 0.30.0, use react-native-fcm@1.0.15
- local notification is not only available in V1
### Example
- An example working project is available at: https://github.com/evollu/react-native-fcm/tree/master/Examples/simple-fcm-client - An example working project is available at: https://github.com/evollu/react-native-fcm/tree/master/Examples/simple-fcm-client
### Android 26
- DO NOT change Android targetSdkVersion >= 26. The notification won't show up because of notification channel requirement. - DO NOT change Android targetSdkVersion >= 26. The notification won't show up because of notification channel requirement.
If you have to upgrade, you can use sdk-26 branch and post feedback on [here](https://github.com/evollu/react-native-fcm/pull/699) If you have to upgrade, you can use sdk-26 branch and post feedback on [here](https://github.com/evollu/react-native-fcm/pull/699)
...@@ -18,6 +36,7 @@ If you have to upgrade, you can use sdk-26 branch and post feedback on [here](ht ...@@ -18,6 +36,7 @@ If you have to upgrade, you can use sdk-26 branch and post feedback on [here](ht
- Run `npm install react-native-fcm --save` - Run `npm install react-native-fcm --save`
- [Link libraries](https://facebook.github.io/react-native/docs/linking-libraries-ios.html) - [Link libraries](https://facebook.github.io/react-native/docs/linking-libraries-ios.html)
Note: the auto link doesn't work with xcworkspace so CocoaPods user needs to do manual linking Note: the auto link doesn't work with xcworkspace so CocoaPods user needs to do manual linking
- You many need [this package for huawei phone](https://github.com/pgengoux/react-native-huawei-protected-apps)
## Configure Firebase Console ## Configure Firebase Console
### FCM config file ### FCM config file
...@@ -318,7 +337,7 @@ NOTE: `com.evollu.react.fcm.FIRLocalMessagingPublisher` is required for presenti ...@@ -318,7 +337,7 @@ NOTE: `com.evollu.react.fcm.FIRLocalMessagingPublisher` is required for presenti
### Build custom push notification for Android ### Build custom push notification for Android
Firebase android misses important feature of android notification like `group`, `priority` and etc. As a work around you can send data message (no `notification` payload at all) and this repo will build a local notification for you. If you pass `custom_notification` in the payload, the repo will treat the content as a local notification config and shows immediately. Firebase android misses important feature of android notification like `group`, `priority` and etc. As a work around you can send data message (no `notification` payload at all) and this repo will build a local notification for you. If you pass `custom_notification` in the payload, the repo will treat the content as a local notification config and shows immediately.
NOTE: By using this work around, you will have to send different types of payload for iOS and Android devices because custom_notification isn't supported on iOS NOTE: By using this work around, you will have to send different types of payload for iOS and Android devices because **custom_notification isn't supported on iOS**
WARNING: `custom_notification` **cannot** be used together with `notification` attribute. use `data` **ALONE** WARNING: `custom_notification` **cannot** be used together with `notification` attribute. use `data` **ALONE**
...@@ -342,6 +361,9 @@ Example of payload that is sent to FCM server: ...@@ -342,6 +361,9 @@ Example of payload that is sent to FCM server:
} }
``` ```
Example of payload that is sent through firebase console:
<img width="753" alt="screen shot 2018-04-04 at 1 18 09 pm" src="https://user-images.githubusercontent.com/9213224/38323255-bbbfdd66-380a-11e8-95c2-bf32ced959b8.png">
Check local notification guide below for configuration. Check local notification guide below for configuration.
**IMPORTANT**: When using the `admin.messaging` API, you need to `JSON.stringify` the `custom_notification` value: **IMPORTANT**: When using the `admin.messaging` API, you need to `JSON.stringify` the `custom_notification` value:
...@@ -459,7 +481,7 @@ Yes there are `react-native-push-notification` and `react-native-system-notifica ...@@ -459,7 +481,7 @@ Yes there are `react-native-push-notification` and `react-native-system-notifica
- The PushNotificationIOS by react native team is still missing features that recurring, so we are adding it here - The PushNotificationIOS by react native team is still missing features that recurring, so we are adding it here
#### My Android build is failing #### My Android build is failing
Try update your SDK and google play service. Try update your SDK and google play service.
If you are having multiple plugins requiring different version of play-service sdk, skip conflicting group. The example project shows for how to colive with react-native-maps If you are having multiple plugins requiring different version of play-service sdk, skip conflicting group. The example project shows for how to colive with react-native-maps
``` ```
compile(project(':react-native-maps')) { compile(project(':react-native-maps')) {
...@@ -530,8 +552,11 @@ It is up to you! FCM is just a bridging library that passes notification into ja ...@@ -530,8 +552,11 @@ It is up to you! FCM is just a bridging library that passes notification into ja
#### I want to show notification when app is in foreground #### I want to show notification when app is in foreground
Use `show_in_foreground` attribute to tell app to show banner even if the app is in foreground. Use `show_in_foreground` attribute to tell app to show banner even if the app is in foreground.
NOTE: this flag doesn't work for Android push notification, use `custom_notification` to achieve this. NOTE: this flag doesn't work for Android push notification, use `custom_notification` to achieve this.
NOTE: foreground notification is not available on iOS 9 and below
#### Do I need to handle APNS token registration? #### Do I need to handle APNS token registration?
No. Method swizzling in Firebase Cloud Messaging handles this unless you turn that off. Then you are on your own to implement the handling. Check this link https://firebase.google.com/docs/cloud-messaging/ios/client No. Method swizzling in Firebase Cloud Messaging handles this unless you turn that off. Then you are on your own to implement the handling. Check this link https://firebase.google.com/docs/cloud-messaging/ios/client
...@@ -547,12 +572,18 @@ Issues and pull requests are welcome. Let's make this thing better! ...@@ -547,12 +572,18 @@ Issues and pull requests are welcome. Let's make this thing better!
#### Credits #### Credits
Local notification implementation is inspired by react-native-push-notification by zo0r Local notification implementation is inspired by react-native-push-notification by zo0r
#### I get the notifications in the logs, but the native prompt does not show up
Did you remember to ask the user permissions? ;)
```js
await FCM.requestPermissions({ badge: false, sound: true, alert: true })
```
## Sending remote notification ## Sending remote notification
How to send a push notification from your server? You should `POST` to this endpoint: How to send a push notification from your server? You should `POST` to this endpoint:
https://fcm.googleapis.com/fcm/send https://fcm.googleapis.com/fcm/send
You need to set the headers of `Content-Type` to `application/json` and `Authorization` to `key=******` where you replace `******` with the "Legacy server key" from here the Firebase dashbaord. Get this information by first going to: You need to set the headers of `Content-Type` to `application/json` and `Authorization` to `key=******` where you replace `******` with the "Legacy server key" from here the Firebase dashbaord. Get this information by first going to:
1. https://console.firebase.google.com/ 1. https://console.firebase.google.com/
...@@ -607,4 +638,3 @@ fetch('https://fcm.googleapis.com/fcm/send', { ...@@ -607,4 +638,3 @@ fetch('https://fcm.googleapis.com/fcm/send', {
}) })
}) })
``` ```
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
def DEFAULT_COMPILE_SDK_VERSION = 26
def DEFAULT_BUILD_TOOLS_VERSION = "25.0.2"
def DEFAULT_TARGET_SDK_VERSION = 26
def DEFAULT_GOOGLE_PLAY_SERVICES_VERSION = "12.+"
android { android {
compileSdkVersion 26 compileSdkVersion project.hasProperty('compileSdkVersion') ? project.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
buildToolsVersion "25.0.2" buildToolsVersion project.hasProperty('buildToolsVersion') ? project.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 26 targetSdkVersion project.hasProperty('buildToolsVetargetSdkVersionrsion') ? project.buildToolsVersion : DEFAULT_TARGET_SDK_VERSION
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
...@@ -17,10 +22,12 @@ repositories { ...@@ -17,10 +22,12 @@ repositories {
} }
dependencies { dependencies {
def googlePlayServicesVersion = project.hasProperty('googlePlayServicesVersion') ? project.googlePlayServicesVersion : DEFAULT_GOOGLE_PLAY_SERVICES_VERSION
compile fileTree(include: ['*.jar'], dir: 'libs') compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.facebook.react:react-native:+' compile 'com.facebook.react:react-native:+'
compile 'com.google.firebase:firebase-core:+' compile "com.google.firebase:firebase-core:$googlePlayServicesVersion"
compile 'com.google.firebase:firebase-messaging:+' compile "com.google.firebase:firebase-messaging:$googlePlayServicesVersion"
compile 'me.leolin:ShortcutBadger:1.1.17@aar' compile 'me.leolin:ShortcutBadger:1.1.17@aar'
compile "com.android.support:support-core-utils:26.1.0" compile "com.android.support:support-core-utils:26.1.0"
} }
......
...@@ -30,63 +30,75 @@ import java.io.IOException; ...@@ -30,63 +30,75 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder;
import static com.facebook.react.common.ReactConstants.TAG; import static com.facebook.react.common.ReactConstants.TAG;
public class SendNotificationTask extends AsyncTask<Void, Void, Void> { public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
private static final long DEFAULT_VIBRATION = 300L; private static final long DEFAULT_VIBRATION = 300L;
private Context mContext; private Context mContext;
private Bundle bundle; private Bundle bundle;
private SharedPreferences sharedPreferences; private SharedPreferences sharedPreferences;
private Boolean mIsForeground; private Boolean mIsForeground;
SendNotificationTask(Context context, SharedPreferences sharedPreferences, Boolean mIsForeground, Bundle bundle){ SendNotificationTask(Context context, SharedPreferences sharedPreferences, Boolean mIsForeground, Bundle bundle){
this.mContext = context; this.mContext = context;
this.bundle = bundle; this.bundle = bundle;
this.sharedPreferences = sharedPreferences; this.sharedPreferences = sharedPreferences;
this.mIsForeground = mIsForeground; this.mIsForeground = mIsForeground;
} }
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
try { try {
String intentClassName = getMainActivityClassName(); String intentClassName = getMainActivityClassName();
if (intentClassName == null) { if (intentClassName == null) {
return null; return null;
} }
if (bundle.getString("body") == null) { String body = bundle.getString("body");
if (body == null) {
return null; return null;
} }
body = URLDecoder.decode( body, "UTF-8" );
Resources res = mContext.getResources(); Resources res = mContext.getResources();
String packageName = mContext.getPackageName(); String packageName = mContext.getPackageName();
String title = bundle.getString("title"); String title = bundle.getString("title");
if (title == null) { if (title == null) {
ApplicationInfo appInfo = mContext.getApplicationInfo(); ApplicationInfo appInfo = mContext.getApplicationInfo();
title = mContext.getPackageManager().getApplicationLabel(appInfo).toString(); title = mContext.getPackageManager().getApplicationLabel(appInfo).toString();
} }
title = URLDecoder.decode( title, "UTF-8" );
String ticker = bundle.getString("ticker");
if (ticker != null) ticker = URLDecoder.decode( ticker, "UTF-8" );
String subText = bundle.getString("sub_text");
if (subText != null) subText = URLDecoder.decode( subText, "UTF-8" );
NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext, bundle.getString("channel")) NotificationCompat.Builder notification = new NotificationCompat.Builder(mContext, bundle.getString("channel"))
.setContentTitle(title) .setContentTitle(title)
.setContentText(bundle.getString("body")) .setContentText(body)
.setTicker(bundle.getString("ticker")) .setTicker(ticker)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE) .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setAutoCancel(bundle.getBoolean("auto_cancel", true)) .setAutoCancel(bundle.getBoolean("auto_cancel", true))
.setNumber((int)bundle.getDouble("number")) .setNumber(bundle.getInt("number", (int)bundle.getDouble("number")))
.setSubText(bundle.getString("sub_text")) .setSubText(subText)
.setVibrate(new long[]{0, DEFAULT_VIBRATION}) .setVibrate(new long[]{0, DEFAULT_VIBRATION})
.setExtras(bundle.getBundle("data")); .setExtras(bundle.getBundle("data"));
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
notification.setGroup(bundle.getString("group")); String group = bundle.getString("group");
if (group != null) group = URLDecoder.decode( group, "UTF-8" );
notification.setGroup(group);
} }
if (bundle.containsKey("ongoing") && bundle.getBoolean("ongoing")) { if (bundle.containsKey("ongoing") && bundle.getBoolean("ongoing")) {
notification.setOngoing(bundle.getBoolean("ongoing")); notification.setOngoing(bundle.getBoolean("ongoing"));
} }
//priority //priority
String priority = bundle.getString("priority", ""); String priority = bundle.getString("priority", "");
switch(priority) { switch(priority) {
...@@ -102,7 +114,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -102,7 +114,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
default: default:
notification.setPriority(NotificationCompat.PRIORITY_DEFAULT); notification.setPriority(NotificationCompat.PRIORITY_DEFAULT);
} }
//icon //icon
String smallIcon = bundle.getString("icon", "ic_launcher"); String smallIcon = bundle.getString("icon", "ic_launcher");
int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName); int smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
...@@ -112,7 +124,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -112,7 +124,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
if(smallIconResId != 0){ if(smallIconResId != 0){
notification.setSmallIcon(smallIconResId); notification.setSmallIcon(smallIconResId);
} }
//large icon //large icon
String largeIcon = bundle.getString("large_icon"); String largeIcon = bundle.getString("large_icon");
if(largeIcon != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ if(largeIcon != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
...@@ -122,41 +134,42 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -122,41 +134,42 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
} else { } else {
int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName); int largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId); Bitmap largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
if (largeIconResId != 0) { if (largeIconResId != 0) {
notification.setLargeIcon(largeIconBitmap); notification.setLargeIcon(largeIconBitmap);
} }
} }
} }
//big text //big text
String bigText = bundle.getString("big_text"); String bigText = bundle.getString("big_text");
if(bigText != null){ if(bigText != null){
bigText = URLDecoder.decode( bigText, "UTF-8" );
notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText)); notification.setStyle(new NotificationCompat.BigTextStyle().bigText(bigText));
} }
//picture //picture
String picture = bundle.getString("picture"); String picture = bundle.getString("picture");
if(picture!=null){ if(picture!=null){
NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle(); NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle();
if (picture.startsWith("http://") || picture.startsWith("https://")) { if (picture.startsWith("http://") || picture.startsWith("https://")) {
Bitmap bitmap = getBitmapFromURL(picture); Bitmap bitmap = getBitmapFromURL(picture);
bigPicture.bigPicture(bitmap); bigPicture.bigPicture(bitmap);
} else { } else {
int pictureResId = res.getIdentifier(picture, "mipmap", packageName); int pictureResId = res.getIdentifier(picture, "mipmap", packageName);
Bitmap pictureResIdBitmap = BitmapFactory.decodeResource(res, pictureResId); Bitmap pictureResIdBitmap = BitmapFactory.decodeResource(res, pictureResId);
if (pictureResId != 0) { if (pictureResId != 0) {
bigPicture.bigPicture(pictureResIdBitmap); bigPicture.bigPicture(pictureResIdBitmap);
} }
} }
bigPicture.setBigContentTitle(title); // setBigContentTitle and setSummaryText overrides current title with body and subtext
bigPicture.setSummaryText(bundle.getString("body")); // that cause to display duplicated body in subtext when picture has specified
notification.setStyle(bigPicture); notification.setStyle(bigPicture);
} }
//sound //sound
String soundName = bundle.getString("sound"); String soundName = bundle.getString("sound");
if (soundName != null) { if (soundName != null) {
...@@ -171,17 +184,17 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -171,17 +184,17 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId)); notification.setSound(Uri.parse("android.resource://" + packageName + "/" + soundResourceId));
} }
} }
//color //color
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL); notification.setCategory(NotificationCompat.CATEGORY_CALL);
String color = bundle.getString("color"); String color = bundle.getString("color");
if (color != null) { if (color != null) {
notification.setColor(Color.parseColor(color)); notification.setColor(Color.parseColor(color));
} }
} }
//vibrate //vibrate
if(bundle.containsKey("vibrate")){ if(bundle.containsKey("vibrate")){
long vibrate = Math.round(bundle.getDouble("vibrate", DEFAULT_VIBRATION)); long vibrate = Math.round(bundle.getDouble("vibrate", DEFAULT_VIBRATION));
...@@ -191,34 +204,41 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -191,34 +204,41 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
notification.setVibrate(null); notification.setVibrate(null);
} }
} }
//lights //lights
if (bundle.getBoolean("lights")) { if (bundle.getBoolean("lights")) {
notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS); notification.setDefaults(NotificationCompat.DEFAULT_LIGHTS);
} }
if(bundle.containsKey("fire_date")) { if(bundle.containsKey("fire_date")) {
Log.d(TAG, "broadcast intent if it is a scheduled notification"); Log.d(TAG, "broadcast intent if it is a scheduled notification");
Intent i = new Intent("com.evollu.react.fcm.ReceiveLocalNotification"); Intent i = new Intent("com.evollu.react.fcm.ReceiveLocalNotification");
i.putExtras(bundle); i.putExtras(bundle);
LocalBroadcastManager.getInstance(mContext).sendBroadcast(i); LocalBroadcastManager.getInstance(mContext).sendBroadcast(i);
} }
if(!mIsForeground || bundle.getBoolean("show_in_foreground")){ if(!mIsForeground || bundle.getBoolean("show_in_foreground")){
Intent intent = new Intent(); Intent intent = new Intent();
intent.setClassName(mContext, intentClassName); intent.setClassName(mContext, intentClassName);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtras(bundle); intent.putExtras(bundle);
intent.setAction(bundle.getString("click_action"));
String clickAction = bundle.getString("click_action");
if (clickAction != null) clickAction = URLDecoder.decode( clickAction, "UTF-8" );
intent.setAction(clickAction);
int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis(); int notificationID = bundle.containsKey("id") ? bundle.getString("id", "").hashCode() : (int) System.currentTimeMillis();
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent, PendingIntent pendingIntent = PendingIntent.getActivity(mContext, notificationID, intent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
notification.setContentIntent(pendingIntent); notification.setContentIntent(pendingIntent);
if (bundle.containsKey("android_actions")) { if (bundle.containsKey("android_actions")) {
WritableArray actions = ReactNativeJson.convertJsonToArray(new JSONArray(bundle.getString("android_actions"))); String androidActions = bundle.getString("android_actions");
androidActions = URLDecoder.decode( androidActions, "UTF-8" );
WritableArray actions = ReactNativeJson.convertJsonToArray(new JSONArray(androidActions));
for (int a = 0; a < actions.size(); a++) { for (int a = 0; a < actions.size(); a++) {
ReadableMap action = actions.getMap(a); ReadableMap action = actions.getMap(a);
String actionTitle = action.getString("title"); String actionTitle = action.getString("title");
...@@ -235,9 +255,9 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -235,9 +255,9 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
notification.addAction(0, actionTitle, pendingActionIntent); notification.addAction(0, actionTitle, pendingActionIntent);
} }
} }
Notification info = notification.build(); Notification info = notification.build();
NotificationManagerCompat.from(mContext).notify(notificationID, info); NotificationManagerCompat.from(mContext).notify(notificationID, info);
} }
...@@ -261,7 +281,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -261,7 +281,7 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
} }
return null; return null;
} }
private Bitmap getBitmapFromURL(String strURL) { private Bitmap getBitmapFromURL(String strURL) {
try { try {
URL url = new URL(strURL); URL url = new URL(strURL);
...@@ -275,11 +295,10 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> { ...@@ -275,11 +295,10 @@ public class SendNotificationTask extends AsyncTask<Void, Void, Void> {
return null; return null;
} }
} }
protected String getMainActivityClassName() { protected String getMainActivityClassName() {
String packageName = mContext.getPackageName(); String packageName = mContext.getPackageName();
Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName); Intent launchIntent = mContext.getPackageManager().getLaunchIntentForPackage(packageName);
return launchIntent != null ? launchIntent.getComponent().getClassName() : null; return launchIntent != null ? launchIntent.getComponent().getClassName() : null;
} }
} }
...@@ -54,13 +54,13 @@ RCT_ENUM_CONVERTER(NSCalendarUnit, ...@@ -54,13 +54,13 @@ RCT_ENUM_CONVERTER(NSCalendarUnit,
content.categoryIdentifier = [RCTConvert NSString:details[@"click_action"]]; content.categoryIdentifier = [RCTConvert NSString:details[@"click_action"]];
content.userInfo = details; content.userInfo = details;
content.badge = [RCTConvert NSNumber:details[@"badge"]]; content.badge = [RCTConvert NSNumber:details[@"badge"]];
NSDate *fireDate = [RCTConvert NSDate:details[@"fire_date"]]; NSDate *fireDate = [RCTConvert NSDate:details[@"fire_date"]];
if(fireDate == nil){ if(fireDate == nil){
return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:nil]; return [UNNotificationRequest requestWithIdentifier:[RCTConvert NSString:details[@"id"]] content:content trigger:nil];
} }
NSCalendarUnit interval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]]; NSCalendarUnit interval = [RCTConvert NSCalendarUnit:details[@"repeat_interval"]];
NSCalendarUnit unitFlags; NSCalendarUnit unitFlags;
switch (interval) { switch (interval) {
...@@ -145,11 +145,11 @@ typedef NS_ENUM(NSUInteger, UNNotificationActionType) { ...@@ -145,11 +145,11 @@ typedef NS_ENUM(NSUInteger, UNNotificationActionType) {
NSString *title = [RCTConvert NSString: details[@"title"]]; NSString *title = [RCTConvert NSString: details[@"title"]];
UNNotificationActionOptions options = [RCTConvert UNNotificationActionOptions: details[@"options"]]; UNNotificationActionOptions options = [RCTConvert UNNotificationActionOptions: details[@"options"]];
UNNotificationActionType type = [RCTConvert UNNotificationActionType:details[@"type"]]; UNNotificationActionType type = [RCTConvert UNNotificationActionType:details[@"type"]];
if (type == UNNotificationActionTypeTextInput) { if (type == UNNotificationActionTypeTextInput) {
NSString *textInputButtonTitle = [RCTConvert NSString: details[@"textInputButtonTitle"]]; NSString *textInputButtonTitle = [RCTConvert NSString: details[@"textInputButtonTitle"]];
NSString *textInputPlaceholder = [RCTConvert NSString: details[@"textInputPlaceholder"]]; NSString *textInputPlaceholder = [RCTConvert NSString: details[@"textInputPlaceholder"]];
return [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:textInputButtonTitle textInputPlaceholder:textInputPlaceholder]; return [UNTextInputNotificationAction actionWithIdentifier:identifier title:title options:options textInputButtonTitle:textInputButtonTitle textInputPlaceholder:textInputPlaceholder];
} }
...@@ -187,17 +187,17 @@ RCT_MULTI_ENUM_CONVERTER(UNNotificationActionOptions, (@{ ...@@ -187,17 +187,17 @@ RCT_MULTI_ENUM_CONVERTER(UNNotificationActionOptions, (@{
for (NSDictionary *actionDict in details[@"actions"]) { for (NSDictionary *actionDict in details[@"actions"]) {
[actions addObject:[RCTConvert UNNotificationAction:actionDict]]; [actions addObject:[RCTConvert UNNotificationAction:actionDict]];
} }
NSArray<NSString *> *intentIdentifiers = [RCTConvert NSStringArray:details[@"intentIdentifiers"]]; NSArray<NSString *> *intentIdentifiers = [RCTConvert NSStringArray:details[@"intentIdentifiers"]];
NSString *hiddenPreviewsBodyPlaceholder = [RCTConvert NSString:details[@"hiddenPreviewsBodyPlaceholder"]]; NSString *hiddenPreviewsBodyPlaceholder = [RCTConvert NSString:details[@"hiddenPreviewsBodyPlaceholder"]];
UNNotificationCategoryOptions options = [RCTConvert UNNotificationCategoryOptions: details[@"options"]]; UNNotificationCategoryOptions options = [RCTConvert UNNotificationCategoryOptions: details[@"options"]];
if (hiddenPreviewsBodyPlaceholder) { if (hiddenPreviewsBodyPlaceholder) {
#if defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 #if defined(__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0
return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder options:options]; return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers hiddenPreviewsBodyPlaceholder:hiddenPreviewsBodyPlaceholder options:options];
#endif #endif
} }
return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers options:options]; return [UNNotificationCategory categoryWithIdentifier:identifier actions:actions intentIdentifiers:intentIdentifiers options:options];
} }
...@@ -229,6 +229,7 @@ RCT_MULTI_ENUM_CONVERTER(UNNotificationCategoryOptions, (@{ ...@@ -229,6 +229,7 @@ RCT_MULTI_ENUM_CONVERTER(UNNotificationCategoryOptions, (@{
static bool jsHandlerRegistered; static bool jsHandlerRegistered;
static NSMutableArray* pendingNotifications; static NSMutableArray* pendingNotifications;
static NSString* refreshToken;
RCT_EXPORT_MODULE(); RCT_EXPORT_MODULE();
...@@ -262,14 +263,14 @@ RCT_EXPORT_MODULE(); ...@@ -262,14 +263,14 @@ RCT_EXPORT_MODULE();
if (response.actionIdentifier) { if (response.actionIdentifier) {
[data setValue:response.actionIdentifier forKey:@"_actionIdentifier"]; [data setValue:response.actionIdentifier forKey:@"_actionIdentifier"];
} }
if ([response isKindOfClass:UNTextInputNotificationResponse.class]) { if ([response isKindOfClass:UNTextInputNotificationResponse.class]) {
[data setValue:[(UNTextInputNotificationResponse *)response userText] forKey:@"_userText"]; [data setValue:[(UNTextInputNotificationResponse *)response userText] forKey:@"_userText"];
} }
NSDictionary *userInfo = @{@"data": data, @"completionHandler": completionHandler}; NSDictionary *userInfo = @{@"data": data, @"completionHandler": completionHandler};
[self sendNotificationEventWhenAvailable:userInfo]; [self sendNotificationEventWhenAvailable:userInfo];
} }
+ (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler + (void)willPresentNotification:(UNNotification *)notification withCompletionHandler:(nonnull RCTWillPresentNotificationCallback)completionHandler
...@@ -281,15 +282,15 @@ RCT_EXPORT_MODULE(); ...@@ -281,15 +282,15 @@ RCT_EXPORT_MODULE();
+ (void)sendNotificationEventWhenAvailable:(NSDictionary*)data + (void)sendNotificationEventWhenAvailable:(NSDictionary*)data
{ {
if(!jsHandlerRegistered){ if(!jsHandlerRegistered){
// JS hasn't registered callback yet. hold on that // JS hasn't registered callback yet. hold on that
if(!pendingNotifications){ if(!pendingNotifications){
pendingNotifications = [NSMutableArray array]; pendingNotifications = [NSMutableArray array];
}
[pendingNotifications addObject:data];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data];
} }
[pendingNotifications addObject:data];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data];
}
} }
- (void)dealloc - (void)dealloc
...@@ -299,58 +300,64 @@ RCT_EXPORT_MODULE(); ...@@ -299,58 +300,64 @@ RCT_EXPORT_MODULE();
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotificationReceived:) selector:@selector(handleNotificationReceived:)
name:FCMNotificationReceived name:FCMNotificationReceived
object:nil]; object:nil];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(sendDataMessageFailure:) addObserver:self selector:@selector(sendDataMessageFailure:)
name:FIRMessagingSendErrorNotification object:nil]; name:FIRMessagingSendErrorNotification object:nil];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(sendDataMessageSuccess:) addObserver:self selector:@selector(sendDataMessageSuccess:)
name:FIRMessagingSendSuccessNotification object:nil]; name:FIRMessagingSendSuccessNotification object:nil];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(connectionStateChanged:) addObserver:self selector:@selector(connectionStateChanged:)
name:FIRMessagingConnectionStateChangedNotification object:nil]; name:FIRMessagingConnectionStateChangedNotification object:nil];
// For iOS 10 data message (sent via FCM) // For iOS 10 data message (sent via FCM)
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[FIRMessaging messaging] setDelegate:self]; [[FIRMessaging messaging] setDelegate:self];
}); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(!jsHandlerRegistered){ if(!jsHandlerRegistered){
[self sendPendingNotifications]; [self sendPendingNotifications];
} }
}); if(refreshToken != nil){
[self sendEventWithName:FCMTokenRefreshed body:refreshToken];
}
});
return self; return self;
} }
-(void) addListener:(NSString *)eventName { -(void) addListener:(NSString *)eventName {
[super addListener:eventName]; [super addListener:eventName];
if([eventName isEqualToString:FCMNotificationReceived]) { if([eventName isEqualToString:FCMNotificationReceived]) {
[self sendPendingNotifications]; [self sendPendingNotifications];
} } else if([eventName isEqualToString:FCMTokenRefreshed] && refreshToken != nil) {
[self sendEventWithName:FCMTokenRefreshed body:refreshToken];
refreshToken = nil;
}
} }
-(void) sendPendingNotifications { -(void) sendPendingNotifications {
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
jsHandlerRegistered = true; jsHandlerRegistered = true;
for (NSDictionary* data in pendingNotifications) { for (NSDictionary* data in pendingNotifications) {
[[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data]; [[NSNotificationCenter defaultCenter] postNotificationName:FCMNotificationReceived object:self userInfo:data];
} }
[pendingNotifications removeAllObjects]; [pendingNotifications removeAllObjects];
}); });
} }
RCT_EXPORT_METHOD(enableDirectChannel) RCT_EXPORT_METHOD(enableDirectChannel)
...@@ -365,21 +372,21 @@ RCT_EXPORT_METHOD(isDirectChannelEstablished:(RCTPromiseResolveBlock)resolve rej ...@@ -365,21 +372,21 @@ RCT_EXPORT_METHOD(isDirectChannelEstablished:(RCTPromiseResolveBlock)resolve rej
RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXPORT_METHOD(getInitialNotification:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{ {
NSDictionary* initialNotif; NSDictionary* initialNotif;
NSDictionary *localUserInfo = [[self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] userInfo] mutableCopy]; NSDictionary *localUserInfo = [[self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey] userInfo] mutableCopy];
NSDictionary *remoteUserInfo = [self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy]; NSDictionary *remoteUserInfo = [self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy];
if(localUserInfo){ if(localUserInfo){
initialNotif = localUserInfo; initialNotif = localUserInfo;
} else if (remoteUserInfo) { } else if (remoteUserInfo) {
initialNotif = remoteUserInfo; initialNotif = remoteUserInfo;
} }
if (initialNotif) { if (initialNotif) {
[initialNotif setValue:@YES forKey:@"opened_from_tray"]; [initialNotif setValue:@YES forKey:@"opened_from_tray"];
resolve(initialNotif); resolve(initialNotif);
} else { } else {
resolve(nil); resolve(nil);
} }
} }
...@@ -405,14 +412,14 @@ RCT_EXPORT_METHOD(getEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RC ...@@ -405,14 +412,14 @@ RCT_EXPORT_METHOD(getEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:(RC
FIROptions *options = FIROptions.defaultOptions; FIROptions *options = FIROptions.defaultOptions;
NSString *entity = options.GCMSenderID; NSString *entity = options.GCMSenderID;
NSData * deviceToken = [FIRMessaging messaging].APNSToken; NSData * deviceToken = [FIRMessaging messaging].APNSToken;
if (deviceToken == nil) { if (deviceToken == nil) {
resolve(nil); resolve(nil);
return; return;
} }
[[FIRInstanceID instanceID]tokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging options:@{@"apns_token": deviceToken} handler:^(NSString * _Nullable token, NSError * _Nullable error) { [[FIRInstanceID instanceID]tokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging options:@{@"apns_token": deviceToken} handler:^(NSString * _Nullable token, NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil);
} else { } else {
...@@ -425,9 +432,9 @@ RCT_EXPORT_METHOD(deleteEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter: ...@@ -425,9 +432,9 @@ RCT_EXPORT_METHOD(deleteEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:
{ {
FIROptions *options = FIROptions.defaultOptions;; FIROptions *options = FIROptions.defaultOptions;;
NSString *entity = options.GCMSenderID; NSString *entity = options.GCMSenderID;
[[FIRInstanceID instanceID]deleteTokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging handler:^(NSError * _Nullable error) { [[FIRInstanceID instanceID]deleteTokenWithAuthorizedEntity:entity scope:kFIRInstanceIDScopeFirebaseMessaging handler:^(NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil);
} else { } else {
...@@ -438,18 +445,19 @@ RCT_EXPORT_METHOD(deleteEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter: ...@@ -438,18 +445,19 @@ RCT_EXPORT_METHOD(deleteEntityFCMToken:(RCTPromiseResolveBlock)resolve rejecter:
RCT_EXPORT_METHOD(deleteInstanceId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXPORT_METHOD(deleteInstanceId:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
{ {
[[FIRInstanceID instanceID]deleteIDWithHandler:^(NSError * _Nullable error) { [[FIRInstanceID instanceID]deleteIDWithHandler:^(NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil); reject([NSString stringWithFormat:@"%ld",error.code],error.localizedDescription,nil);
} else { } else {
resolve(nil); resolve(nil);
} }
}]; }];
} }
- (void)messaging:(nonnull FIRMessaging *)messaging didRefreshRegistrationToken:(nonnull NSString *)fcmToken { - (void)messaging:(nonnull FIRMessaging *)messaging didReceiveRegistrationToken:(nonnull NSString *)fcmToken {
[self sendEventWithName:FCMTokenRefreshed body:fcmToken]; refreshToken = fcmToken;
[self sendEventWithName:FCMTokenRefreshed body:fcmToken];
} }
RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
...@@ -488,7 +496,7 @@ RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(R ...@@ -488,7 +496,7 @@ RCT_EXPORT_METHOD(requestPermissions:(RCTPromiseResolveBlock)resolve rejecter:(R
]; ];
#endif #endif
} }
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
[[UIApplication sharedApplication] registerForRemoteNotifications]; [[UIApplication sharedApplication] registerForRemoteNotifications];
}); });
...@@ -504,8 +512,7 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic) ...@@ -504,8 +512,7 @@ RCT_EXPORT_METHOD(unsubscribeFromTopic: (NSString*) topic)
[[FIRMessaging messaging] unsubscribeFromTopic:topic]; [[FIRMessaging messaging] unsubscribeFromTopic:topic];
} }
// Receive data message on iOS 10 devices. - (void)messaging:(FIRMessaging *)messaging didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage {
- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
[self sendEventWithName:FCMNotificationReceived body:[remoteMessage appData]]; [self sendEventWithName:FCMNotificationReceived body:[remoteMessage appData]];
} }
...@@ -610,12 +617,12 @@ RCT_EXPORT_METHOD(setNotificationCategories:(NSArray *)categories) ...@@ -610,12 +617,12 @@ RCT_EXPORT_METHOD(setNotificationCategories:(NSArray *)categories)
{ {
if([UNUserNotificationCenter currentNotificationCenter] != nil) { if([UNUserNotificationCenter currentNotificationCenter] != nil) {
NSMutableSet *categoriesSet = [[NSMutableSet alloc] init]; NSMutableSet *categoriesSet = [[NSMutableSet alloc] init];
for(NSDictionary *categoryDict in categories) { for(NSDictionary *categoryDict in categories) {
UNNotificationCategory *category = [RCTConvert UNNotificationCategory:categoryDict]; UNNotificationCategory *category = [RCTConvert UNNotificationCategory:categoryDict];
[categoriesSet addObject:category]; [categoriesSet addObject:category];
} }
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categoriesSet]; [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:categoriesSet];
} }
} }
...@@ -639,15 +646,15 @@ RCT_EXPORT_METHOD(send:(NSString*)senderId withPayload:(NSDictionary *)message) ...@@ -639,15 +646,15 @@ RCT_EXPORT_METHOD(send:(NSString*)senderId withPayload:(NSDictionary *)message)
for (NSString* key in mMessage) { for (NSString* key in mMessage) {
upstreamMessage[key] = [NSString stringWithFormat:@"%@", [mMessage valueForKey:key]]; upstreamMessage[key] = [NSString stringWithFormat:@"%@", [mMessage valueForKey:key]];
} }
NSDictionary *imMessage = [NSDictionary dictionaryWithDictionary:upstreamMessage]; NSDictionary *imMessage = [NSDictionary dictionaryWithDictionary:upstreamMessage];
int64_t ttl = 3600; int64_t ttl = 3600;
NSString * receiver = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId]; NSString * receiver = [NSString stringWithFormat:@"%@@gcm.googleapis.com", senderId];
NSUUID *uuid = [NSUUID UUID]; NSUUID *uuid = [NSUUID UUID];
NSString * messageID = [uuid UUIDString]; NSString * messageID = [uuid UUIDString];
[[FIRMessaging messaging]sendMessage:imMessage to:receiver withMessageID:messageID timeToLive:ttl]; [[FIRMessaging messaging]sendMessage:imMessage to:receiver withMessageID:messageID timeToLive:ttl];
} }
...@@ -700,14 +707,14 @@ RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId){ ...@@ -700,14 +707,14 @@ RCT_EXPORT_METHOD(finishNotificationResponse: (NSString *)completionHandlerId){
- (void)sendDataMessageFailure:(NSNotification *)notification - (void)sendDataMessageFailure:(NSNotification *)notification
{ {
NSString *messageID = (NSString *)notification.userInfo[@"messageID"]; NSString *messageID = (NSString *)notification.userInfo[@"messageID"];
NSLog(@"sendDataMessageFailure: %@", messageID); NSLog(@"sendDataMessageFailure: %@", messageID);
} }
- (void)sendDataMessageSuccess:(NSNotification *)notification - (void)sendDataMessageSuccess:(NSNotification *)notification
{ {
NSString *messageID = (NSString *)notification.userInfo[@"messageID"]; NSString *messageID = (NSString *)notification.userInfo[@"messageID"];
NSLog(@"sendDataMessageSuccess: %@", messageID); NSLog(@"sendDataMessageSuccess: %@", messageID);
} }
......
...@@ -24,5 +24,5 @@ ...@@ -24,5 +24,5 @@
"type": "git", "type": "git",
"url": "git+https://github.com/evollu/react-native-fcm.git" "url": "git+https://github.com/evollu/react-native-fcm.git"
}, },
"version": "14.1.2" "version": "15.0.1"
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment