Push notification in existing expo project - react-native

I have an existing app in expo create in react-native. In that app I have to add expo push notification functionality. I have created the NotificationController using below link:
https://docs.expo.io/versions/v32.0.0/guides/push-notifications
I now need to know where I have to place this controller. I am very new in mobile development and just need help about the correct placement of this controller so that we can show this to the customer.

Once you have created your push notification service , either this can be a function in a single file in side your services directory (create if it doesn't exists) , or a component.
Then import that function inside your main app.js and use it inside componentDidMount lifecycle function. This is just a sample code and I'm sure this can be improved further, However this is enough to get started.
push_notification.js
import {Permissions, Notifications} from 'expo';
import { ActivityIndicator, AsyncStorage} from 'react-native';
export default async () => {
try{
let previousToken = await AsyncStorage.getItem('pushToken');
if(previousToken){
return;
}else{
let {status} = await Permissions.askAsync(Permissions.NOTIFICATIONS);
console.log(status);
if(status !== 'granted'){
console.log("Don't like to receive push");
}
let token = await Notifications.getExpoPushTokenAsync();
return token;
}
} catch(err){
console.log(err);
}
};
App.js
import PushNotification from "../services/push_notifications";
import axios from 'axios';
async componentDidMount(){
let tokenFromStorage = await zlAsyncStorage.getItem('pushToken');
console.log('token from storage',tokenFromStorage);return;
let token = await PushNotification();
AsyncStorage.setItem('pushToken',token);
console.log("here");
//Save the token in couch db
await axios({
url:"http://192.168.8.148:5984/mycompany",
method: 'post',
timeout: 1000,
headers: {
'Accept-Encoding' : 'gzip, deflate',
'Content-Type':'application/json',
'Authorization':'Basic YTph'
},
data: {
user: "cheran",
tokenReceived : token,
},
auth: {
username: 'a',
password: 'a'
},
}).then(function(response){
//console.log(response);
});
}

Related

Voximplant push notifications - React Native

I have successfully managed to get remote messages from Firebase console to send messages when my app is in the QUIT state, FOREGREOUND state and in BACKGROUND state. So all the Firebase setup is working PERFECTLY in my React Native app (Android).
I managed to get Firebase Push Notifications working very well, using the latest Firebase messaging library.
The Version 5 'react-native-firebase' is deprecated and no longer supported, we have to use Version 6 '#react-native-firebase/messaging' for messaging and 'react-native-push-notification' (for the channel stuff and notification). This is good for the future in all React Native work, as the old version is becoming obsolete and Firebase isn't supporting it.
The google-services.json works fine and the push certificate is also successfully uploaded in the Voximplant panel.
I can already make calls when both Voximplant users are on the app (i.e. app states are in the FOREGROUND).
The MAIN reason for needing the push, is to allow another Voximplant user to receive a call from the app QUIT and BACKGROUND state.
What seems to be missing?
Here's my code:
package.json
"#react-native-firebase/app": "^15.2.0",
"#react-native-firebase/messaging": "^15.2.0",
"react-native-voximplant": "^1.28.0",
index.js
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import messaging from '#react-native-firebase/messaging';
import PushBackground from './manager/PushBackground';
//Set handler
messaging().setBackgroundMessageHandler(PushBackground);
// NOT USING (but still works with pure firebase incoming push notifications)
//messaging().setBackgroundMessageHandler(async remoteMessage => {
// console.log('Message handled in the background!', remoteMessage);
//});
AppRegistry.registerComponent(appName, () => App);
App.js
import React, { useState, useEffect } from "react";
import { requestUserPermission, NotificationListener, CancelNotifications } from "./manager/PushManager";
function App() {
useEffect(() => {
//Firebase messaging/notifications
requestUserPermission(); //gets "fcmtoken"
NotificationListener(); //sets up firebase listeners for (QUIT, FOREGROUND, BACKGROUND App states)
CancelNotifications(); //Cancels notifications after received
}, []);
return(...all UI stuff...)
export default App;
}
loginScreen.js
import React, { Component, useEffect, useState, useContext } from "react";
import { Voximplant } from 'react-native-voximplant';
import { APP_NAME, ACC_NAME } from "../../constant/constants";
import AsyncStorage from '#react-native-async-storage/async-storage';
const LoginScreen = ({ navigation }) => {
const voximplant = Voximplant.getInstance();
useEffect(() => {
const connect = async() => {
const status = await voximplant.getClientState();
if(status == Voximplant.ClientState.DISCONNECTED){
await voximplant.connect();
console.log("Status: Voximplant Client DISCONNECTED: " + status);
}
else if (status == Voximplant.ClientState.LOGGED_IN) {
console.log("Status: Voximplant Client LOGGED IN: " + status);
}
const signIn = async () => {
try{
const fqUsername = `${username}#${APP_NAME}.${ACC_NAME}.voximplant.com`;
console.log(fqUsername);
let authResult = await voximplant.login(fqUsername, password);
const loginTokens = authResult.tokens;
console.log("Log In Successful. Token " + JSON.stringify(loginTokens, null, 2));
//We have fcmtoken (firebase) from App.js loading up, so now register it with Voximplant cloud
const fcmToken = await AsyncStorage.getItem("fcmtoken");
//Register it with Voximplant
if(fcmToken != null){
voximplant.registerPushNotificationsToken(fcmToken);
}
//Set fqUsername for Voximplant
await AsyncStorage.setItem("fqUsernameKey", fqUsername);
//Set accessToken for Voximplant
await AsyncStorage.setItem('accessTokenKey', authResult.tokens.accessToken);
}
catch(e) {
console.log("signIn issue: "+ e);
Alert.alert("Already Logged In", `Error code: ${e.code}`);
}
}
return(...UI stuff...)
}
export default LoginScreen;
PushBackground.js
'use strict';
import AsyncStorage from '#react-native-async-storage/async-storage';
import { Voximplant } from 'react-native-voximplant';
export default async (message) => {
console.log('PushBackground android: notification: ' + JSON.stringify(message));
const username = await AsyncStorage.getItem("fqUsernameKey"); //THIS IIS SET IN LOGINSCREEN.JS
const accessToken = await AsyncStorage.getItem("fcmtoken"); //THIS IS SET IN PUSHMANAGER.JS
const client = Voximplant.getInstance();
await client.loginWithToken(username, accessToken);
await client.handlePushNotification(message.data);
return Promise.resolve();
};
PushManager.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import messaging from "#react-native-firebase/messaging";
import PushNotification, {Importance} from 'react-native-push-notification';
async function getFCMToken() {
let fcmtoken = await AsyncStorage.getItem("fcmtoken");
console.log(fcmtoken, "old token")
if(!fcmtoken){
try{
const fcmtoken = await messaging().getToken();
if(fcmtoken) {
console.log(fcmtoken, "new token");
await AsyncStorage.setItem("fcmtoken", fcmtoken);
}
}
catch (error) {
console.log(error, "error in fcmtoken")
}
}
}
//Create Push Notification channel
async function createVoximplantChannel() {
PushNotification.createChannel( //Haven't tried with Voximplant
{
channelId: "voximplant_channel_id", // "channel-id", // (required)
channelName: "Incoming call channel", //"My channel", // (required)
channelDescription: "Incoming call received", //"A channel to categorise your notifications", // (optional) default: undefined.
playSound: true, // (optional) default: true
soundName: "default", // (optional) See `soundName` parameter of `localNotification` function
importance: 5, // (optional) default: 4. Int value of the Android notification importance. importance: 2 <- SILENT NOTIFICATION CHANNEL & importance: 5 <- POPUP NOTIFICATION CHANNEL
vibrate: false, // (optional) default: true. Creates the default vibration patten if true.
},
(created) => console.log(`createChannel returned '${created}'`) // (optional) callback returns whether the channel was created, false means it already existed.
);
}
export async function requestUserPermission() {
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('Authorization Status: ', authStatus);
getFCMToken();
createVoximplantChannel();
}
}
export const NotificationListener = () => {
//******** Notification from BACKGROUND state app ********
messaging().onNotificationOpenedApp((remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId, //Perhaps "voximplant_channel_id" ?
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
//******** Notification from QUIT state app ********
messaging()
.getInitialNotification()
.then( (remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId, //Perhaps "voximplant_channel_id" ?
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
//******** Notification from FOREGROUND state app ********
messaging().onMessage(async (remoteMessage) => {
PushNotification.localNotification({
channelId: "voximplant_channel_id", //remoteMessage.notification.android.channelId,
message: remoteMessage.notification.body,
title: remoteMessage.notification.title,
//bigPictureUrl: remoteMessage.notification.android.imageUrl,
//smallIcon: remoteMessage.notification.android.imageUrl,
vibrate: true,
data: remoteMessage.data,
})
});
}
//Cancel notifications (so hopefully no overlapping notificatons)
export const CancelNotifications = () => {
PushNotification.cancelAllLocalNotifications();
};

Getting the Plaid Link to Work in my Create React App with Auth0

I had started a project a little while ago and have been busy lately so I have not been able to work on it. I am out of practice with web development because I had recently joined the military. Right now the project consists of a create-react-app app with auth0 integrated. What I am trying to do is get the plaid link integrated into the page it takes you after logging in using auth0. I am requesting help on what code from the plaid docs I use in order for this to work. Their documentation is a little confusing to me, maybe because I'm so out of practice. Any help would be much much appreciated.
https://github.com/CollinChiz/SeeMyCash
Have you taken a look at the Quickstart at https://github.com/plaid/quickstart/? It contains a full React implementation that does this. Here's the relevant excerpt:
// APP COMPONENT
// Upon rendering of App component, make a request to create and
// obtain a link token to be used in the Link component
import React, { useEffect, useState } from 'react';
import { usePlaidLink } from 'react-plaid-link';
const App = () => {
const [linkToken, setLinkToken] = useState(null);
const generateToken = async () => {
const response = await fetch('/api/create_link_token', {
method: 'POST',
});
const data = await response.json();
setLinkToken(data.link_token);
};
useEffect(() => {
generateToken();
}, []);
return linkToken != null ? <Link linkToken={linkToken} /> : <></>;
};
// LINK COMPONENT
// Use Plaid Link and pass link token and onSuccess function
// in configuration to initialize Plaid Link
interface LinkProps {
linkToken: string | null;
}
const Link: React.FC<LinkProps> = (props: LinkProps) => {
const onSuccess = React.useCallback((public_token, metadata) => {
// send public_token to server
const response = fetch('/api/set_access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ public_token }),
});
// Handle response ...
}, []);
const config: Parameters<typeof usePlaidLink>[0] = {
token: props.linkToken!,
onSuccess,
};
const { open, ready } = usePlaidLink(config);
return (
<button onClick={() => open()} disabled={!ready}>
Link account
</button>
);
};
export default App;

Trying to consume api using react-native

I created an api with laravel and now i want to be able to consume it using React-native but it's not working at all and I have no clue about it. Here are some code that i think can be helpful to help you try to help me.
That's my api.js file.
import {create} from 'apisauce';
import { AsyncStorage } from 'react-native';
const api = create({
baseURL:'http://127.0.0.1:8000/api',
});
api.addAsyncRequestTransform( request => async () => {
const token = await AsyncStorage.getItem('#PetFinder:token');
if(token) {
request.headers['Authorization'] = `Bearer ${token}`;
}
request.headers['Content-Type'] = `application/json`;
request.headers['accept'] = `application/json`
});
And here is my Login page
import React from 'react';
import { View, Button } from 'react-native';
import api from '../../services/api'
export default function Login() {
async function handleSubmit()
{
const response = await api.post('/login', {
email: 'nowahmst#gmail.com',
password:'123456',
});
}
return (
<View><Button onPress={handleSubmit} title="Login" > </Button></View>
);
}
Back-End is running, I'm able to get the response using postman or insomnia and my route is set to be post as well the api request.

relay subscription onNext not triggered on react-native

I am a subscription setup but onNext is not getting triggered I am not sure why since this is my first time implementing subscription and docs was not much help with the issue.
Here are the code implementations:
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../network';
const subscription = graphql`
subscription chatCreatedSubscription{
chatCreated{
id
initiate_time
update_time
support_id
category_id
email
name
}
}
`;
function chatCreated(callback) {
const variables = {};
requestSubscription(environment, {
subscription,
variables,
onNext: () => {
console.log("onNext");
callback()
},
updater: () => {
console.log("updater");
}
});
}
module.exports = chatCreated;
and here is my network for the subscription
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import Expo from "expo";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { WebSocketLink } from 'apollo-link-ws';
import { execute } from 'apollo-link';
import accessHelper from "../helper/accessToken";
const networkSubscriptions = async (operation, variables) => {
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient("ws://localhost:3000/graphql",
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
execute(new WebSocketLink(subscriptionClient), {
query: operation.text,
variables,
});
}
}
const network = Network.create(fetchQuery, networkSubscriptions);
const store = new Store(new RecordSource());
const environment = new Environment({
network,
store
});
export default environment;
the subscription is called in a componentDidMount method on a component it executes but the onNext method inside the subscription is never triggered when new information is added to what the subscription is listening to.
so i figured out that my issue was the network js not being setup properly and the version of subscription-transport-ws. i added version 0.8.3 of the package and made the following changes to my network file:
const networkSubscriptions = async (config, variables, cacheConfig, observer) => {
const query = config.text;
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient(`ws://${api}/graphql`,
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
subscriptionClient.subscribe({ query, variables }, (error, result) => {
observer.onNext({ data: result })
})
return {
dispose: subscriptionClient.unsubscribe
};
}
}
i hope this helps you if you get stuck with the same issue as mine.

How can I import a redux saga yield into my detox + jest test file. I need gain access to data stored in the redux store in my test setup

This is a react-native application and I am currently writing some end-to-end testing.
A token is stored in the redux store shown below and I am testing the login functionality using detox/jest. I need to detect if the token exists in the store in my login.spec.js . If the token exists I want to wipe it from the store so the user is not logged in automatically when i reload the app to take the user back to another scene. The main function in question is the refreshUserToken() and line:-
const { refresh_token } = yield select(token);
Here is the redux saga file User.js located at:-MyApp/App/Sagas/User.js
import { call, put, takeEvery, select } from "redux-saga/effects";
import Config from "MyApp/App/Config";
import API from "MyApp/App/Services/API";
import { when } from "MyApp/App/Helpers/Predicate";
import Credentials from "MyApp/App/Helpers/Credentials";
import ActionCreator from "MyApp/App/Actions";
const appendPayload = payload => {
return {
...payload,
// Removed because no longer needed unless for testing purposes.
// username: Config.TEST_USERNAME,
// password: Config.TEST_PASSWORD,
client_id: Config.CLIENT_ID,
client_secret: Config.CLIENT_SECRET,
};
};
const token = state => state.token;
const user = state => state.user;
const attemptUserLogin = function*(action) {
const { payload } = action;
const login = "/oauth/token";
const grant_type = "password";
const loginPayload = appendPayload(payload);
action.payload = {
...loginPayload,
grant_type,
};
yield attemptUserAuthorisation(login, action);
};
const attemptUserRegister = function*(action) {
const register = "/api/signup";
const { payload } = action;
yield Credentials.save(payload);
yield put(ActionCreator.saveUserCredentials(payload));
yield attemptUserAuthorisation(register, action);
};
const refreshUserToken = function*(action) {
const login = "/oauth/token";
const grant_type = "refresh_token";
const { refresh_token } = yield select(token);
action.payload = {
...action.payload,
grant_type,
refresh_token,
};
yield attemptUserAuthorisation(login, action);
};
const watchExampleSaga = function*() {
yield takeEvery(ActionCreator.AUTO_USER_LOGIN, autoUserLogin);
yield takeEvery(ActionCreator.USER_LOGIN, attemptUserLogin);
yield takeEvery(ActionCreator.USER_REGISTER, attemptUserRegister);
yield takeEvery(ActionCreator.USER_REFRESH_TOKEN, refreshUserToken);
};
export default watchExampleSaga;
Here is my detox/jest spec file located at:-MyApp/App/e2e/login.spec.js
describe('Login Actions', () => {
it('Should be able to enter an email address', async () => {
await element(by.id('landing-login-btn')).tap()
const email = 'banker#dovu.io'
await element(by.id('login-email')).tap()
await element(by.id('login-email')).replaceText(email)
});
it('Should be able to enter a password', async () => {
const password = 'secret'
await element(by.id('login-password')).tap()
await element(by.id('login-password')).replaceText(password)
});
it('Should be able to click the continue button and login', async () => {
await element(by.id('login-continue-btn')).tap()
await waitFor(element(by.id('dashboard-logo'))).toBeVisible().withTimeout(500)
// If token exists destroy it and relaunch app. This is where I need to grab the token from the redux saga!
await device.launchApp({newInstance: true});
});
})
This is how I handled a similar scenario:
in package.json scripts:
"start:detox": "RN_SRC_EXT=e2e.tsx,e2e.ts node node_modules/react-native/local-cli/cli.js start",
In my detox config:
"build": "ENVFILE=.env.dev;RN_SRC_EXT=e2e.tsx,e2e,ts npx react-native run-ios --simulator='iPhone 7'",
That lets me write MyFile.e2e.tsx which replaces MyFile.tsx whilst detox is running
In the test version of that component I have buttons which are tapped in the tests and the buttons dispatch redux actions
Looks like this actually cant be done unless someone can give me a solution other than mocking the state which still wouldn't work in this case my app checks for real states to auto login.
I did get to the stage of creating a new action getUserToken and exporting that into my jest file. However the action returns undefined because the jest file requires a dispatch method like in containers.js. If anyone could provide me with a method of this using jest I would be very happy.