React native firebase handling payment flow - javascript

I am integrating payment api, I'm using firebase cloud functions as my backend and react-native in frontend, so right now i have the following code:
In react-native side:
const PaymentScreen = () => {
setLoading(true);
const onPay = () => {
const onPaymentCall = firebase.functions().httpsCallable('onPayment');
onPaymentCall(productsData)
.then(res => {
setLoading(false);
if (res?.data?.statusCode === 200) {
const paymentReceivedData = {
transactionId: res?.data?.transactionId,
paymentDate: new Date().now(),
totalPrice: res?.data?.totalPrice
}
navigator.navigate('ReceiptScreen', { paymentReceivedData });
}
})
}
return (
<TouchableOpacity onPress={onPay}>PAY</TouchableOpacity>
)
}
firebase function to handle payment:
export const onPayment = functions
.runWith({ timeoutSeconds: 300 })
.https.onCall(async (data: any, context: any) => {
if (!context.auth) {
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
try {
const paymentResult = await paymentThroughApi(data);
await admin.firestore().collection('wallet').add({...paymentResult});
const response = {
statusCode: 200,
totalPrice: paymentResult.totalPrice,
transactionId: paymentResult.transactionId,
}
return response;
} catch (error) {
const response = {
statusCode: 400,
message: 'Payment unsuccessful',
transactionId: null,
}
return response;
}
So the question is, how can I handle long-time response/timeout error/network-loss etc in react-native side, like how should I make it more robust? especially handling long-time responses, timeout errors, payment failures etc? Which packages should I use? like NetInfo to check internet connectivity but what is the best way to make payment flow robust?
So can anyone guide me or suggest any code snippets to add on to my current code?
Thank you!

I don't think there is one definite answer to your question, But here is what I think:
For managing network connection status in React Native you can use
NetInfo for information about network connection and Axios to make network requests to a public API as per documentation and for more information check with this Stackoverflow Link.
For timeout errors you can check the Article from Dan Abromov, which states Making setInterval Declarative with React Hooks and with this link
There are different answers for long-time responses in react-native.

Related

Logout user when jwt expires in react/redux

I am using localstorage to store a jwt in my react/redux app for authentication. I am trying to have the user get logged out if their token is expired. One way I have gotten this to work would be to use my authMiddleware.js on the backend to send the error.message and set the payload equal to an error variable, and then in a useEffect if the error is jwt expired I run my logout function (which just clears the localstorage) and reset the error to null. Like the following:
authMiddleware.js:
const jwt = require("jsonwebtoken");
const User = require("../models/user");
const protect = async (req, res, next) => {
let token = req.body.token;
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = await User.findById(decoded.id).select("-password");
next();
} catch (error) {
res.status(400).json({ message: error.message });
}
};
module.exports = { protect };
Portfolio.slice:
export const getCurrentHoldings = createAsyncThunk(
"/portfolio/getCurrentHoldings",
async (value, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token;
const userID = thunkAPI.getState().auth.user._id;
const newObj = {
token: token,
userID: userID,
};
let url = `http://localhost:3001/api/portfolio/getCurrentHoldings`;
const response = await axios.post(url, newObj);
console.log("New request ran in getCurrentHoldings");
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
return thunkAPI.rejectWithValue(message);
}
}
);
const initialState = {
error: null,
status: "idle",
holdings: null,
jwtError: null,
};
export const portfolioSlice = createSlice({
name: "portfolio",
initialState,
reducers: {
reset: (state) => initialState,
},
extraReducers(builder) {
builder
.addCase(getCurrentHoldings.pending, (state, action) => {
state.status = "loading";
})
.addCase(getCurrentHoldings.fulfilled, (state, action) => {
state.status = "success";
state.holdings = action.payload;
console.log(state.holdings);
})
.addCase(getCurrentHoldings.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
state.jwtError = action.payload;
})
},
});
Portfolio.js:
useEffect(() => {
if (jwtError == "jwt expired") {
dispatch(logout());
dispatch(reset());
}
}, [jwtError]);
The problem with this solution is I have multiple slices that I would need to add a similar variable for each and the useEffect would grow and start looking like:
useEffect(() => {
if (jwtError == "jwt expired") {
dispatch(logout());
dispatch(reset());
}
if (jwtError1 == "jwt expired") {
dispatch(logout());
dispatch(reset1());
}
if (jwtError2 == "jwt expired") {
dispatch(logout());
dispatch(reset2());
}
}, [jwtError, jwtError1, jwtError2]);
Thus this solution is not scalable, one way I thought to fix this was having some of the slices access data from another slice so at least the useEffect would be reduced to the original size and be scalable but I found that reducers only have access to the state they own Thus looking into this problem more I found a couple of posts related to this and I got suggestions to either 1. use cookies instead of localstate 2. Use middleware and 3. use instance.interceptors
Now one question I had for all of the above solutions is if this issue should be solved on the frontend, backend, or both? Since the middleware and instance.interceptors solution looks like its solved on the frontend. I would like to know if this is a security risk and if you should also use a backend middleware aswell.
I also would like to know if using cookies instead of useState is just a best practice, but either way I would like to implement this with localstorage also.
And finally I would like a best practices for how this should be done with redux in react and what the code might look like with my setup.
Update:
The solution I am trying currently is redux middleware and I am unable to decode the token on the frontend, installing jsonwebtoken in the react project results in a an error: Module not found: Error: Can't resolve 'crypto' in myfile. As far as I know I will need this library on the frontend if I am to decode it as suggested in middleware link.
Thus looking into this problem more I found a couple of posts related to this and I got suggestions to either 1. use cookies instead of localstate 2. Use middleware and 3. use instance.interceptors
The suggestions you got are great, I would definitely use an http-only cookie to store the token (safer because separated from the JS runtime, no malicious js code can ever see it) and a redux middleware and an axios interceptor.
The solution I am trying currently is redux middleware and I am unable to decode the token on the frontend, installing jsonwebtoken in the react project results in a an error: Module not found: Error: Can't resolve 'crypto' in myfile. As far as I know I will need this library on the frontend if I am to decode it as suggested in middleware link.
If you're using https://www.npmjs.com/package/jsonwebtoken, this seems to be a Node.js-only implementation, not meant for the browser. Looking at JWT Verify client-side? suggests that https://github.com/auth0/jwt-decode should be sufficient for you in the browser.
If you don't go with a http-only cookie based solution, there is a more elegant solution: You can decode the JWT, read the expiration time, and then schedule a function to run a few seconds before the expiration time (via setInterval) that refreshes the token. If this fails, the function can dispatch an action that logs the user out and resets the redux state to what you need it to be. This is a more proactive solution, as you don't need to wait until a request to the backend fails because of an expired token - after all you know when it will expire.

how to catch error in redux action and display the error message in UI

I have a redux action called academyRedirect.js which is invoked whenever an user is redirected to the path /user/academy when he pushes the 'academy' button in my main page.
export const getAcademyAutoLogin = () => {
axios.get(`/user/academy`)
.then((response) => {
window.location.replace(response.data); // this is replaced by an URL coming from backend
})
.catch((error) => {
reject(error.response);
});
return null;
};
Whenever the user is not allowed to access the academy (for not having credentials) or whenever i get an error 500 or 404, i need to display a modal or something to inform the user that an error occurred while trying to log into the academy. Right now im not being able to do it, the page just stays blank and the console output is the error.response.
Any help is appreciated
Redux Store
export const messageSlice = createSlice({
name: 'message',
initialState: { isDisplayed: false, errorMessage: ''},
reducers: {
displayError(state, action) {
state.isDisplayed = true
state.errorMessage = action.message
},
resetErrorState() {
state.isDisplayed = false
state.errorMessage = ''
},
}
})
export const messageActions = messageSlice.actions;
Inside the component:-
const Login = () => {
const errorState = useSelector(globalState => globalState.message)
const onClickHandler = async => {
axios.get(`/user/academy`)
.then((response) => { window.location.replace(response.data) })
.catch((error) => {
dispatch(messageActions.displayError(error))
});
}
return (
{errorState.isDisplayed && <div>{errorState.errorMessage}</div>}
{!errorState.isDisplayed &&<button onClick={onClickHandler}>Fetch Data </button>}
)
}
Maybe this is of help to you
You can try to add interceptor to your axios.
Find a place where you create your axios instance and apply an interceptor to it like this
const instance = axios.create({
baseURL: *YOUR API URL*,
headers: { "Content-Type": "application/json" },
});
instance.interceptors.request.use(requestResolveInterceptor, requestRejectInterceptor);
And then, in your requestRejectInterceptor you can configure default behavior for the case when you get an error from your request. You can show user an error using toast for example, or call an action to add your error to redux.
For second case, when you want to put your error to redux, its better to use some tools that created to make async calls to api and work with redux, for example it can be redux-saga, redux-thunk, redux-axios-middleware etc.
With their docs you would be able to configure your app and handle all cases easily.

Issue with seting up a pub/ sub implementation with Pubnub

First off let me start by saying I am a web developer who is very new to the Blockchain space so I apologize in advance if I am missing something obvious. With that being said I am having issues implementing a broadcast transaction method into a cryptocurrency project I am working on. Every time I am able to successfully make transaction requests to my API and I am able to see the correct transaction pool on the main dev network, however my peer network does not see any new transactions until I restart it. I know this means my broadcast transaction implementation is missing something but I am not sure what I need to fix it.
please refer to the following images
Here is the code snippets for my pubsub implementation using PUBNUB
const CHANNELS = {
TEST: "TEST",
BLOCKCHAIN: "BLOCKCHAIN",
TRANSACTION: "TRANSACTION"
};
class PubSub {
constructor({blockchain, transactionPool}) {
this.blockchain = blockchain;
this.transactionPool = transactionPool;
this.pubnub = new PubNub(credentials); // defined above but omitted for obvious reasons
this.pubnub.subscribe({channels: Object.values(CHANNELS)}); // defined in channels object
this.pubnub.addListener(this.listener());
};
listener() {
return {
message: messageObject => {
const { channel, message } = messageObject;
console.log(`Message received. Channel: ${channel}. Message: ${message}`);
const parsedMessage = JSON.parse(message);
switch(channel) {
case CHANNELS.BLOCKCHAIN:
this.blockchain.replaceChain(parsedMessage);
break;
case CHANNELS.TRANSACTION:
this.transactionPool.setTransaction(parsedMessage);
break;
default:
return;
}
}
};
}
publish({channel, message}) {
this.pubnub.unsubscribe(channel, () => {
this.pubnub.publish(channel, message, () => {
this.pubnub.subscribe(channel);
});
});
}
broadcastChain() {
this.publish({
channel: CHANNELS.BLOCKCHAIN,
message: JSON.stringify(this.blockchain.chain)
})
}
broadcastTransaction(transaction) {
this.publish({
channel: CHANNELS.TRANSACTION,
message: JSON.stringify(transaction)
});
}
};
And here is the snippets for where the broadcastTransaction method is called
const pubsub = new PubSub({blockchain, transactionPool});
let transaction = transactionPool.existingTransaction({inputAddress: wallet.publicKey}); // Creates global binding
// Sends transaction to the network
app.post("/api/transact", (req, res) => {
const {amount, recipient} = req.body;
try {
if(transaction) {
transaction.update({senderWallet: wallet, recipient, amount });
} else {
transaction = wallet.createTransaction({recipient, amount});
}
} catch(error) {
return res.status(400).json({type: "error", message: error.message});
};
transactionPool.setTransaction(transaction);
pubsub.broadcastTransaction(transaction); // Calls broadcastTransaction from pubsub class (does not work for peers)
res.json({type: "success", transaction});
});
I tried to be as specific as possible but If I missed anything please let me know. Thank you in advance!
Thank you for the input. logging helped me realize that my dev and peer ports were not subscribed when starting up the nodes. I added the following method to the PubSub class and was able to sync both the dev and peer ports
subscribeToChannels() {
this.pubnub.subscribe({
channels: [Object.values(CHANNELS)]
});
}

Firebase Callable Function context is undefined

I have written a firebase Http callable cloud function based on the tutorial here: https://www.youtube.com/watch?v=3hj_r_N0qMs from the firebase team. However, my function is unable to verify the custom claims on a user (me) as 'context.auth' is undefined
I've updated firebase, firebase tools, firebase-functions and admin SDK to the latest versions.
My functions/Index.ts file
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp()
export const addAdmin = functions.https.onCall((data, context) => {
if (context.auth.token.admin !== true) {
return {
error: 'Request not authorized'
};
}
const uid = data.uid
return grantAdminRole(uid).then(() => {
return {
result: `Request fulfilled!`
}
})
})
async function grantAdminRole(uid: string): Promise<void> {
const user = await admin.auth().getUser(uid);
if (user.customClaims && (user.customClaims as any).admin === true) {
console.log('already admin')
return;
}
return admin.auth().setCustomUserClaims(user.uid, {
admin: true,
}).then(() => {
console.log('made admin');
})
}
My app.component.ts code
makeAdmin() {
var addAdmin = firebase.functions().httpsCallable('addAdmin');
addAdmin({ uid: '[MY-USER-ID]' }).then(res => {
console.log(res);
})
.catch(error => {
console.log(error)
})
}
The function executes well if I don't try to access 'context' and I can add a custom claim to this user. However if I try to access context.auth I find the error:
Unhandled error TypeError: Cannot read property 'token' of undefined"
The error message is telling you that context.auth doesn't have a value. As you can see from the API documentation, auth will be null if there is no authenticated user making the request. This suggests to me that your client app does not have a signed-in user at the time of the request to the callable function, so make sure that is the case before invoking the function. If you allow the case where a callable function can be invoked without a signed in user, you will need to check for that case in your function code by checking context.auth before doing work on behalf of that user.
Turns out I wasn't properly integrating AngularFire Functions. I found the solution to my problem here: https://github.com/angular/angularfire2/blob/master/docs/functions/functions.md
I changed my client component code to the following:
import { AngularFireFunctions } from '#angular/fire/functions';
//other component code
makeAdmin() {
const callable = this.fns.httpsCallable('addAdmin');
this.data$ = callable({ uid: '[USERID]' })
.subscribe(resp => {
console.log({ resp });
}, err => {
console.error({ err });
});
}

Using classes (not class components) in react to improve design architecture

I have been using React for a few months now and am diving deeper in to application structure and design patterns. Of particular interest to me is handling multiple asynchronous calls that may depend on each other, dependant on the logic of user journeys.
I am currently building an app where I have multiple asynchronous operations that include using local storage, connecting and querying Firebase for phone number authentication and also a WebRTC React Native video calling platform.
In trying to figure out how to handle data received from these apis, I have come across a few examples of code where the authors are using plain classes, not class components to help build their application. My question is, what is this design pattern called and how should I approach using it? What are the best resources to read about its use?
For example, with Firebase phone number authentication a developer I found on Github writes this:
class FirebaseService {
user = ''
uid = ''
userStatusDatabaseRef = ''
userStatusFirestoreRef = ''
constructor() {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
const { uid, phoneNumber} = firebase.auth().currentUser._user
UserStore.user.setUID(uid)
UserStore.user.setPhoneNumber(phoneNumber)
let ref = firebase.database().ref(`/Users/${UserStore.user.uid}`)
ref.on('value', this.gotUserData)
}
this.setListenConnection()
})
}
confirmPhone = async (phoneNumber) => {
const phoneWithAreaCode = phoneNumber.replace(/^0+/,'+972');
return new Promise((res, rej) => {
firebase.auth().verifyPhoneNumber(phoneWithAreaCode)
.on('state_changed', async (phoneAuthSnapshot) => {
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.AUTO_VERIFIED:
await this.confirmCode(phoneAuthSnapshot.verificationId, phoneAuthSnapshot.code, phoneAuthSnapshot)
res(phoneAuthSnapshot)
break
case firebase.auth.PhoneAuthState.CODE_SENT:
UserStore.setVerificationId(phoneAuthSnapshot.verificationId)
res(phoneAuthSnapshot)
break
.......
Here he creates FirebaseService and there is also UserStore referenced. However, I just have a FirebaseService.js file, and pass in a callback to it authorizeWithToken from my container component to store a result to state via Redux in the component.
import firebase from 'react-native-firebase';
export const authorizeFirebase = (phoneNumber, authorizeWithToken) => {
try {
firebase.auth()
.verifyPhoneNumber(phoneNumber)
.on('state_changed', (phoneAuthSnapshot) => {
// console.log(phoneAuthSnapshot);
switch (phoneAuthSnapshot.state) {
case firebase.auth.PhoneAuthState.CODE_SENT: // or 'sent'
console.log('code sent');
break;
case firebase.auth.PhoneAuthState.ERROR: // or 'error'
console.log('verification error');
console.log(phoneAuthSnapshot.error);
break;
case firebase.auth.PhoneAuthState.AUTO_VERIFY_TIMEOUT: // or 'timeout'
console.log('auto verify on android timed out');
break;
// '-------------------------------------------------------------'
case firebase.auth.PhoneAuthState.AUTO_VERIFIED: // or 'verified'
console.log('auto verified on android');
const { verificationId, code } = phoneAuthSnapshot;
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId, code);
firebase.auth().signInWithCredential(credential).then(result => {
const user = result.user;
user.getIdToken().then(accessToken => {
authorizeWithToken(accessToken);
My service is a function, and his is a class, neither of them are components.
To give another example - here is a class 'service' from a code example that is provided by video messaging platform ConnectyCube that I'm using:
import ConnectyCube from 'connectycube-reactnative'
class ChatService {
// Chat - Core
connect(user) {
return new Promise((resolve, reject) => {
if (!user) reject()
ConnectyCube.chat.connect(
{
userId: user.id,
password: user.password,
},
(error, contacts) => {
if (!error && contacts) {
resolve(contacts)
} else {
reject(error)
}
},
)
})
}
disonnect() {
ConnectyCube.chat.disconnect()
}
}
// create instance
const Chat = new ChatService()
// lock instance
Object.freeze(Chat)
export default Chat
I haven't come across classes as services until now but I would really like to know how I should use them and what patterns I should follow. They look super useful and at the moment I have a spaghetti junction of local state, Redux and services that are connected via callbacks and complex async await setups. Any ideas greatly appreciated.

Categories

Resources