Hey guys so I have been trying to implement push notifications to my react-native project for almost 2 weeks now. The idea is if the person is not in the chat room(chat room is present in the app) then the other user's message Is sent via push notification and stored to local storage on the receivers device.
I implemented the push notification service through firebase since literally everyone said its super easy etc. My problem comes when I want to dispatch the notification to my reducer etc using React-Redux when the notification comes in a quit state. I am able to save the message to local storage thanks to redux and persisting storage but when the app is not open im not sure how to achieve this.
Any guides and help would be appreciated!
*PS I even shifted my whole provider, reducer etc to my index.js file so that
messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log('Message handled in the background!', remoteMessage);
dispatch({
type: 'save_message',
data: JSON.parse(remoteMessage.data.message)
})
});
can have access to the provider to save the message but that only works when the app is in background and not when its in a quit state. Also I am using #react-native-firebase/messaging v7.8.3 and #react-native-firebase/app v8.4.1
USING REDUX IN REACT NATIVE PUSH NOTIFICATION
-->App.js
import { Configure } from './config/NotificationHandler'
const App = () => {
return (
<SafeAreaProvider>
<StatusBar barStyle="dark-content" hidden={false} backgroundColor="#1A1A1A" translucent={true} />
<Provider store={store} >
<Configure />
<View style={{ flex: 1 }} >
<AuthLoading />
</View>
</Provider>
</SafeAreaProvider>
)
}
export default App;
-->Notificationhandler.js
import React, { useEffect } from 'react';
import PushNotificationIOS from "#react-native-community/push-notification-ios";
import PushNotification from 'react-native-push-notification';
import AsyncStorage from '#react-native-community/async-storage';
import NavigationService from '../routing/NavigationService'
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
const Configure = () => {
const { activeProject } = useSelector(state => ({
activeProject: state.homeReducer.activeProject,
}), shallowEqual);
const dispatch = useDispatch();
// Must be outside of any component LifeCycle (such as `componentDidMount`).
PushNotification.configure({
// (optional) Called when Token is generated (iOS and Android)
onRegister: function (token) {
console.log("RNPNonRegistertoken:", token);
AsyncStorage.setItem('fcmToken', token.token);
},
// (required) Called when a remote is received or opened, or local notification is opened
onNotification: function (notification) {
console.log("NOTIFICATION:", notification, activeProject);
// process the notification
if (notification?.data?.url) {
NavigationService.navigate('PDFScreen', { Key: 'URL', urlForPDF: notification.data.url })
} else if (notification.id > 0 && notification.id < 7 && global.notifNavVar) {
global.localPushID = notification.id
NavigationService.navigate('AllTimersButton')
} else if (notification.id == 7 && global.notifNavVarP) {
NavigationService.navigate('ProjectDetail')
}
// (required) Called when a remote is received or opened, or local notification is opened
notification.finish(PushNotificationIOS.FetchResult.NoData);
},
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
onAction: function (notification) {
console.log("ACTION:", notification.action);
console.log("NOTIFICATION:", notification);
// process the action
},
// (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)
// onRegistrationError: function(err) {
// console.error(err.message, err);
// },
// IOS ONLY (optional): default: all - Permissions to register.
permissions: {
alert: true,
badge: true,
sound: true,
},
largeIcon: "ic_launcher",
smallIcon: "ic_launcher",
// Should the initial notification be popped automatically
// default: true
popInitialNotification: true,
/**
* (optional) default: true
* - Specified if permissions (ios) and token (android and ios) will requested or not,
* - if not, you must call PushNotificationsHandler.requestPermissions() later
* - if you are not using remote notification or do not have Firebase installed, use this:
* requestPermissions: Platform.OS === 'ios'
*/
});
return null
};
const LocalNotificationSchedule = (id, afterSec, message = '', title = '') => {
PushNotification.localNotificationSchedule({
//... You can use all the options from localNotifications
id: id + '',
title,
message, // (required)
date: new Date(Date.now() + afterSec * 1000), // in n secs
playSound: true,
// soundName: 'local_notification_custom_tone.mp3',
vibrate: true,
vibration: 180000,
allowWhileIdle: true,
visibility: "public",
// soundName: 'default',
showWhen: true,
usesChronometer: true,
ignoreInForeground: false,
priority: "max",
})
}
const CancelLocalNotifications = (id) => {
PushNotification.cancelLocalNotifications({ id: id + '' })
}
// const LocalNotification = () => {
// PushNotification.localNotification({
// id: 0, // (optional) Valid unique 32 bit integer specified as string. default: Autogenerated Unique ID
// autoCancel: true,
// bigText: 'This is local notification demo in React Native app. Only shown, when expanded.',
// subText: 'Local Notification Demo',
// title: 'Local Notification Title',
// message: 'Expand me to see more',
// vibrate: true,
// vibration: 300,
// playSound: true,
// soundName:'default',
// actions: '["Yes", "No"]'
// })
// }
export {
Configure,
LocalNotificationSchedule,
CancelLocalNotifications,
// LocalNotification
};
In my React Native app I dispatch to the Redux store when the app is killed by using the vanilla Redux API as setBackgroundMessageHandler is not a React component or hook which don't have access to the Redux provider:
setBackgroundMessageHandler.js:
import store from '../../redux/store';
const setBackgroundMessageHandler = async (remoteMessage) => {
store.dispatch({
type: 'save_message',
data: JSON.parse(remoteMessage.data.message)
})
}
Your data should be safely dispatched from the notification message to the store ready to use once the app loads.
Related
In my React Native app, I'm using react-native-geolocation-service to track location changes. In iOS, the background location tracking works perfectly just by following these instructions. The problem arises in Android which causes the tracking to stop or work randomly when the app goes into background.
Let me emphasize that I don't want the location to be tracked when the app is fully closed. I ONLY want the tracking to work when the app is in the foreground (active) and background states.
I've followed the instructions given in the package's own example project to configure and start the tracking service and just like them I use react-native-foreground-service.
This is the function responsible for tracking the user location with the watchPosition method of Geolocation:
// Track location updates
export const getLocationUpdates = async (watchId, dispatch) => {
// Check if app has permissed
const hasPermission = await hasLocationPermission();
// Show no location modal and return if it hasn't
if (!hasPermission) {
dispatch(setAvailability(false));
return;
}
// Start the location foreground service if platform is Android
if (Platform.OS === 'android') {
await startForegroundService();
}
// Track and update the location refernce value without re-rendering
watchId.current = Geolocation.watchPosition(
position => {
// Hide no location modal
dispatch(setAvailability(true));
// Set coordinates
dispatch(
setCoordinates({
latitude: position?.coords.latitude,
longitude: position?.coords.longitude,
heading: position?.coords?.heading,
}),
);
},
error => {
// Show no location modal
dispatch(setAvailability(false));
},
{
accuracy: {
android: 'high',
ios: 'best',
},
distanceFilter: 100,
interval: 5000,
fastestInterval: 2000,
enableHighAccuracy: true,
forceRequestLocation: true,
showLocationDialog: true,
},
);
};
And this is how the foreground service of react-native-foreground-service is initialized:
// Start the foreground service and display a notification with the defined configuration
export const startForegroundService = async () => {
// Create a notification channel for the foreground service
// For Android 8+ the notification channel should be created before starting the foreground service
if (Platform.Version >= 26) {
await VIForegroundService.getInstance().createNotificationChannel({
id: 'locationChannel',
name: 'Location Tracking Channel',
description: 'Tracks location of user',
enableVibration: false,
});
}
// Start service
return VIForegroundService.getInstance().startService({
channelId: 'locationChannel',
id: 420,
title: 'Sample',
text: 'Tracking location updates',
icon: 'ic_launcher',
});
};
And this is how it's supposed to stop:
// Stop the foreground service
export const stopLocationUpdates = watchId => {
if (Platform.OS === 'android') {
VIForegroundService.getInstance()
.stopService()
.catch(err => {
Toast.show({
type: 'error',
text1: err,
});
});
}
// Stop watching for location updates
if (watchId.current !== null) {
Geolocation.clearWatch(watchId.current);
watchId.current = null;
}
};
The way I start the tracking is just when the Map screen mounts:
const watchId = useRef(null); // Location tracking reference value
const dispatch = useDispatch();
// Start the location foreground service and track user location upon screen mount
useMemo(() => {
getLocationUpdates(watchId, dispatch);
// Stop the service upon unmount
return () => stopLocationUpdates(watchId);
}, []);
I still haven't found a way to keep tracking the location when the app goes into background and have become frustrated with react-native-foreground-service since its service won't stop even after the app is fully closed (The problem is that the cleanup function of useMemo never gets called upon closing the app).
I have heard about react-native-background-geolocation (The free one) but don't know if it will still keep tracking after closing the app (A feature I DON'T want) and am trying my best not to use two different packages to handle the location service (react-native-background-geolocation and react-native-geolocation-service).
Another option would be Headless JS but even with that I'm not quite sure if it would stop tracking after the app is closed.
I welcome and appreciate any help that might guide me to a solution for this frustrating issue.
My app is requiring that google oauth (via federatedSignIn) be tapped twice in iOS devices, prior to actually signing the user in.
Process:
Upon the first tap, inapp browser opens up and you select which account you're intending to sign in with. Inapp browser closes and seems like all the rest of my logic is not being hit.
Upon the second tap, the inapp browser re-opens up again for a split second (screen is blank), and then closes and THEN the user is actually signed in.
On the iOS simulator/android, however, it seems like it works as expected. Another strange thing is that it works as expected for oauth'ing in with Apple on all devices.
Wondering if anyone else has run into this issue and if y'all have a suggestion?
Where I instantiate the hub listener:
useEffect(() => {
// NOTE: amplify hub listener
const listener = async (data: any) => {
switch (data.payload.event) {
case "signIn":
case "cognitoHostedUI":
await signInUser();
break;
case "signOut":
setUser(null);
break;
default:
break;
}
};
Hub.listen("auth", listener);
}, []);
My google oauth button component:
export function GoogleSignInButton({ title }: GoogleSignInButtonProps) {
return (
<SocialIcon
button
type="google"
title={title}
style={{ padding: 50, marginBottom: 10 }}
onPress={() =>
Auth.federatedSignIn({
provider: "Google" as any,
}).catch(federatedSignInError => {
console.log({ federatedSignInError });
throw new Error(federatedSignInError);
})
}
/>
);
}
I'm also using the react-native-inappbrowser-reborn npm package to have an internal webview when signing in, if that's relevant:
async function urlOpener(url: string, redirectUrl: string) {
await InAppBrowser.isAvailable();
const { type, url: newUrl } = (await InAppBrowser.openAuth(url, redirectUrl, {
showTitle: false,
enableUrlBarHiding: true,
enableDefaultShare: false,
ephemeralWebSession: false,
})) as RedirectResult;
if (type === "success") {
Linking.openURL(newUrl);
}
}
const appsyncAuthenticationTypeOverride = {
...config,
oauth: {
...config.oauth,
urlOpener,
},
aws_appsync_authenticationType: "AWS_IAM",
};
Amplify.configure(appsyncAuthenticationTypeOverride);
i had the same issue.
It seems to be related to Cookies in navigator, you seem to be loading the during the first logging attempt, and using the during the second one.
Also it seems to be sometimes related to redirection errors in Cognito Auth Flow.
I managed to solve it by finding this issue :
https://github.com/aws-amplify/amplify-js/issues/7468
Especially this comment :
https://github.com/aws-amplify/amplify-js/issues/7468#issuecomment-816853703
I'm currently working on a Android mobile App.
It's a kitchen recipes app. The app will send notification to the user during the day.
In the settings of the app, the user can choose how many and at what time he will receive the notifications (11 am to 7 pm for example)
This is where the problem begins;
I use the react-native-push-notification library with the following code:
static LocalNotif(string)
{
PushNotification.localNotification({
vibrate: true, // (optional) default: true
vibration: 300, // vibration length in milliseconds, ignored if vibrate=false, default: 1000
title: "Vérifier vos produit", // (optional)
message: string, // (required)
largeIcon: "ic_launcher",
smallIcon: "ic_notification",
});
}
Next, I use the react-native-background-fetch to send a notification, even if the app is not running
static async backFetch(delay_to_next_notif)
{
BackgroundFetch.configure({
minimumFetchInterval: 3600
}, async (taskId) => {
// This is the fetch-event callback.
console.log("[BackgroundFetch] taskId: ", taskId);
// Use a switch statement to route task-handling.
switch (taskId) {
case 'com.foo.customtask':
this.LocalNotif("test")
break;
default:
console.log("Default fetch task");
}
// Finish, providing received taskId.
BackgroundFetch.finish(taskId);
});
// Step 2: Schedule a custom "oneshot" task "com.foo.customtask" to execute 5000ms from now.
BackgroundFetch.scheduleTask({
taskId: "com.foo.customtask",
forceAlarmManager: true,
delay: delay_to_next_notif// <-- milliseconds
});
}
The use of react-native-background-fetch is very strange. Sometime I never receive the notification.
Is it possible to use a push notification library and create a routine so that the user receives notifications at specific times during the day, even if the app is not running?
You can use Pushnptification.configure method and set your state if your app is in forground or background something like this
async componentDidMount() {
await this.requestUserPermission();
PushNotification.configure({
onNotification: (notification) => {
console.log('NOTIFICATION', notification);
if (notification.foreground === false) {
console.log('app is in background')
}
this.setState({
msg: notification.message.body
? notification.message.body
: notification.message,
forground: notification.foreground,
});
},
});
}
and in your return u can do something like this
{this.state.forground === true
? showMessage({
message: this.state.msg,
backgroundColor: '#1191cf',
type: 'default',
duration: 10000,
icon: 'success',
onPress: () => {
console.log('app is in forground')
},
})
: null}
I'm struggling with the configuration of auth + Axios.
I'm currently working on our social login (FB and google). It half works.
First of all, I have my Axios instance configured as a plugin. We do have two instances, one that we use for general API requests and another one that we will use for logged user requests.
plugins/axios.js
export default function({ $axios, redirect, app }) {
$axios.defaults.baseURL = process.env.baseUrl
$axios.defaults.headers = {
Authorization: `Bearer ${process.env.APIKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
lang: app.i18n.locale
}
$axios.onError(error => {
const code = parseInt(error.response && error.response.status)
if (code === 400) {
redirect('/400')
}
if (code === 404) {
redirect('/404')
}
if (code === 500) {
redirect('/500')
}
})
}
plugins/auth-axios.js
export default function({ $axios }, inject) {
const authAxios = $axios.create()
// authAxios.setToken('123', 'Bearer')
inject('authAxios', authAxios)
}
The first client works perfectly in the whole app.
Now I'm configuring the auth plugin. When I log in through Facebook or google I need to take the response data of the logged user from the social network and send it to my API, expecting a user with the token as the response. This will be the user that I'll set in the auth plugin. Every time I log in with Facebook it appears to work well except when It arrives at the plugin code. $auth.loggedIn is always false.
I've made it work forcing $auth.fetchUser at the beginning of the code but it doesn't work well at all. It shows an Axios error when console.log($auth). This is my auth code:
plugins/auth.js
export default async function({ app: { $auth, $axios } }) {
$auth.fetchUser()
console.log($auth)
if (!$auth.loggedIn) {
return
}
const authStrategy = $auth.strategy.name
if (authStrategy === 'facebook' || authStrategy === 'google') {
if ($auth.user.google_id || $auth.user.fb_id) return
try {
const url = `/client/social`
var postData
if (authStrategy == 'facebook')
postData = {
name: $auth.user.name,
email: $auth.user.email,
fb_id: $auth.user.id,
avatar: $auth.user.picture.data.url,
birthday: $auth.user.birthday
}
else
postData = {
name: $auth.user.given_name,
surname: $auth.user.family_name,
email: $auth.user.email,
google_id: $auth.user.sub,
avatar: $auth.user.picture,
locale: $auth.user.locale
}
const { data } = await $axios.post(url, postData)
$auth.setToken('client', data.access_token)
$auth.setUser(data.client)
} catch (e) {
console.log(e)
}
}
}
The console.log($auth) error:
'$state': {
user: null,
loggedIn: false,
strategy: 'facebook'
},
error: TypeError: Cannot set property 'Authorization' of undefined
at Function.setHeader (server.js:1556:42)
at Oauth2Scheme._setToken (server.js:1014:31)
at Oauth2Scheme.mounted (server.js:1001:12)
at Auth.mounted (server.js:516:42)
at Auth.init (server.js:459:18)
at module.exports../.nuxt/auth/plugin.js._webpack_exports_.default (server.js:939:16)
at createApp (server.js:2432:87)
}
And my auth module config:
import dotenv from 'dotenv'
dotenv.config()
export const auth = {
plugins: [
// {
// src: '~/plugins/axios',
// ssr: true
// },
{
src: '~/plugins/auth-axios',
ssr: true
},
'~/plugins/auth.js'
],
redirect: {
login: '/',
callback: '/callback'
},
strategies: {
local: false,
facebook: {
client_id: '#############',
userinfo_endpoint:
'https://graph.facebook.com/v2.12/me?fields=about,name,picture{url},email,birthday',
scope: ['public_profile', 'email', 'user_birthday']
},
google: {
client_id:
'#####################'
}
}
}
Seems that auth when login is trying to set the Axios token (also when I log out it tries to remove it) but it fails. If I go to the Chrome dev tools and debug it to see which Axios instance is trying to use for that. Every time is the main Axios instance and it's supposed to be accessible there.
Screenshot from DevTools:
Screenshot from DevTools
Does someone know what I'm missing? Can I prevent auth facebook strategy to update any Axios instance? Or, can I specify which Axios instance to update (set / remove token)?
EDIT: Forcing the $auth.fetchUser my auth plugin code does work but with the error mentioned before. When I try to logOut it doesn't work due to the same error when trying to remove the token automatically)
I'm going crazy with this issue for two weeks now.
Thanks so much!
I'm trying to create a middleware for check role of my users.
// middleware/is-admin.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'admin' ) {
return context.redirect('/errors/403')
}
}
In my .vue file, I'm putting this on:
middleware: [ 'is-admin' ]
It works.
Now, I'd like to check if the user also has another role. So, I create a new middleware:
// middleware/is-consultant.js
export default function (context) {
let user = context.store.getters['auth/user']
if ( user.role !== 'consultant' ) {
return context.redirect('/errors/403')
}
}
And in my .vue file:
middleware: [ 'is-admin', 'is-consultant' ]
Unfortunately, when I do that, if I visit the route with an administrator role, it does not work anymore.
Can you tell me how I can create a middleware that checks multiple roles with Nuxt.js?
Thank you!
The idea is that every page has its authority level. Then in middleware you can compare your current user authority level with the current page authority level, and if it's lower redirect the user. It's very elegant solution that was proposed by Nuxt.js creator. GitHub issue.
<template>
<h1>Only an admin can see this page</h1>
</template>
<script>
export default {
middleware: 'auth',
meta: {
auth: { authority: 2 }
}
}
</script>
Then in your middleware/auth.js:
export default ({ store, route, redirect }) => {
// Check if user is connected first
if (!store.getters['user/user'].isAuthenticated) return redirect('/login')
// Get authorizations for matched routes (with children routes too)
const authorizationLevels = route.meta.map((meta) => {
if (meta.auth && typeof meta.auth.authority !== 'undefined')
return meta.auth.authority
return 0
})
// Get highest authorization level
const highestAuthority = Math.max.apply(null, authorizationLevels)
if (store.getters['user/user'].details.general.authority < highestAuthority) {
return error({
statusCode: 401,
message: 'Du måste vara admin för att besöka denna sidan.'
})
}
}
You can use this feature in Nuxt
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
The scope can be anything you want e.g Consultant, Editor etc.
Check the documentation
Updated
Since you are using Laravel
You can have a role column in your user table
e.g
$table->enum('role', ['subscriber', 'admin', 'editor', 'consultant', 'writer'])->default('subscriber');
Then create a API resource, check the documentation for more
To create a user resource, run this artisan
php artisan make:resource UserResource
Then in your resource, you can have something like this
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'gender' => $this->gender,
'country' => $this->country,
'avatar' => $this->avatar,
'role' => $this->role,
];
}
Then you can import it to your controller like this
use App\Http\Resources\UserResource;
You can get the resource like this
$userdata = new UserResource(User::find(auth()->user()->id));
return response()->json(array(
'user' => $userdata,
));
In Nuxt
To do authentication in Nuxt
Install nuxt auth and axios
Using YARN : yarn add #nuxtjs/auth #nuxtjs/axios
Or using NPM: npm install #nuxtjs/auth #nuxtjs/axios
Then register them in your nuxtconfig.js
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth',
],
In your nuxtconfig.js, add this also
axios: {
baseURL: 'http://127.0.0.1:8000/api'
},
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/login', method: 'post', propertyName: 'access_token' },
logout: { url: '/logout', method: 'post' },
user: { url: '/user', method: 'get', propertyName: false }
},
tokenRequired: true,
tokenType: 'Bearer',
globalToken: true
// autoFetchUser: true
}
}
}
The URL been the endpoints
Check Documentation for more
To restrict certain pages in Nuxt to Specific User.
> Create a middlweare e.g isadmin.js
Then add this
export default function ({ $auth, redirect }) {
if (!$auth.hasScope('admin')) {
return redirect('/')
}
}
Then go to the Page, add the middleware
export default {
middleware: 'isadmin'