React Native unable to to set AsyncStorage after Fetch API - javascript

this question has been answered, you can ignore that and click away
i am making my app using react native expo, and after successfully login, my server return an token an i want to store it in the AsyncStorage, this is my code:
import { Alert, AsyncStorage } from "react-native";
export const login_as_student = async (code, password, navigation) => {
return fetch("http://192.168.47.102:5000/users/login/student", {
// Windows + CMD
// Wireless LAN adapter Wi-Fi
method: "POST",
headers: {
"content-type": "application/json; charset=UTF-8",
},
body: JSON.stringify({ code, password }),
})
.then((response) => {
return response.json();
})
.then((json) => {
const { currentUser, token } = json;
console.log(json);
if (token && currentUser) {
AsyncStorage.setItem("token", token)
.then((value) => console.log("token", value))
.catch((error) => console.log(error));
return navigation.navigate("Darshboard");
} else {
console.log(json);
Alert.alert(json.message);
}
})
.catch((err) => {
console.log(err);
Alert.alert(err.message);
});
};
and this is the result:
Object {
"currentUser": Object {
"__v": 0,
"_id": "5f8be6bf0220db23d096ca14",
"activated": false,
"code": "16020503",
"fullname": "Đỗ Xuân An ",
"password": "$2b$10$PQ5EAc3Vkn1WT00uKadYi.ue2UR1lRBfSeF.cXgZps2pqejV4bAUy",
"profileImage": "https://avataaars.io/?accessoriesType=Prescription01&avatarStyle=Circle&clotheColor=Blue02&clotheType=BlazerSwet
er&eyeType=WinkWacky&eyebrowType=FlatNatural&facialHairColor=Platinum&facialHairType=MoustacheFancy&hairColor=Platinum&hatColor=PastelRed&mouthType=Vomit&skinColor=Brown&topType=LongHairMiaWallace",
"role": "student",
"vnumail": "16020503#vnu.edu.vn",
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjoiXCI1ZjhiZTZiZjAyMjBkYjIzZDA5NmNhMTRcIiIsImlhdCI6MTYwMzU1ODYyOSwiZXhwIjoxNjAzNjQ1MDI5fQ.cxOHxddB_ha94ZGXY_XLcCs1MgDJHhZDgbl4GFOzk80",
}
token null
as you can see, i received a json which contains 2 objects is token and currentUser, after that i stored it in the AsyncStorage but it returns null, i am totally newbie with React Native, thank you for help me out and have a good day :)
update:
thanks everyone, i have been figured it out that the AsyncStorage is no longer imported from "react-native", it is now imported from "#react-native-community/async-storage", have a good day

You are missing await before AsyncStorage.setItem()
Modify your code like this:
.then(async (json) => { // Make this async
const { currentUser, token } = json;
console.log(json);
if (token && currentUser) {
await AsyncStorage.setItem("token", token) // add await here
.then((value) => console.log("token", value))
.catch((error) => console.log(error));
return navigation.navigate("Darshboard");
} else {
console.log(json);
Alert.alert(json.message);
}
})

Are you sure you are getting right (non NULL) token and currentUser values after destructuring?
If you are getting null, wrap the statement within parenthesis
({currentUser, token} = json)
Also, an advice, better mask or hide the token value or any other sensitive info before posting :)

Related

Why is the axios post resulting in a network error?

I'm currently building an app using React Native on the frontend and Node.js together with express on the backend.
I have two axios instances, one of which I call the main server and the other the authentication server. With both instances all normal server calls outside of the following file are working expect this one. I'm currently localhosting both servers.
My frontend looks like this:
import axios from 'axios';
import AsyncStorage from '#react-native-async-storage/async-storage';
const BASE_URL = 'URL_PLACEHOLDER'; // <- IP Address of my computer
export const instanceMain = axios.create({
baseURL: `${BASE_URL}:3000`,
timeout: 1000,
});
export const instanceAuth = axios.create({
baseURL: `${BASE_URL}:4000`,
});
// Interception to check if a token refresh is needed
instanceMain.interceptors.request.use(async function (response) {
const accessToken = await AsyncStorage.getItem('accessToken');
const refreshToken = await AsyncStorage.getItem('refreshToken');
await instanceAuth
.post(`/token`, {
accessToken: accessToken,
refreshToken: refreshToken,
})
.then((res) => {
AsyncStorage.setItem('accessToken', res.data.accessToken);
AsyncStorage.setItem('refreshToken', res.data.refreshToken);
})
.catch((error) => {
console.log(error);
});
return response;
});
My backend of the post axios is trying to make looks like this:
app.post('/token', async (req, res) => {
try {
const accessToken = req.body.accessToken;
const refreshToken = req.body.refreshToken;
if (accessToken == null || accessToken == undefined)
return res.sendStatus(401);
// Verifying AccessToken
jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET, (error) => {
// If it is expired this should be executed. I think I'm probably handling this
// the wrong way, but this was the only way I could think of.
if (error) {
// tokenDB is an in-storage json database containing all active refresh tokens
tokenDB.read();
tokenDB.data ||= { tokens: [] };
if (refreshToken == null) return res.sendStatus(401);
// Checking if tokenDB does not contain the refresh token, if so, return 403
if (!tokenDB.data.tokens.includes(refreshToken)) {
return res.sendStatus(403);
}
// Verifying RefreshToken
jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET,
(error, account) => {
// Deleting iat from account, otherwise the new tokens would be the same
// as before
delete account.iat;
if (error) return res.sendStatus(403);
// Generating new Tokens
const newAccessToken = generateAccessToken(account);
const newRefreshToken = generateRefreshToken(account);
// Removing old refreshToken from tokenDB and push newRefreshToken to it
tokenDB.data.tokens = tokenDB.data.tokens.filter(
(token) => token !== refreshToken
);
tokenDB.data.tokens.push(newRefreshToken);
// Result: new Access and Refresh token
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken,
});
tokenDB.write();
}
);
} else {
return res.sendStatus(100);
}
});
} catch (error) {
console.error(error);
}
});
The error message simply says [AxiosError: Network Error]. error.config gives back the following, but I can't do much with it.
{"adapter": [Function xhrAdapter], "baseURL": "IP_PLACEHOLDER:4000", "data": "{\"accessToken\":\"ACCESS_TOKEN_PLACEHOLDER",\"refreshToken\":\"REFRESH_TOKEN_PLACEHOLDER"}", "env": {"Blob": [Function Blob], "FormData": [Function FormData]}, "headers": {"Accept": "application/json, text/plain, */*", "Content-Type": "application/json"}, "maxBodyLength": -1, "maxContentLength": -1, "method": "post", "timeout": 0, "transformRequest": [[Function transformRequest]], "transformResponse": [[Function transformResponse]], "transitional": {"clarifyTimeoutError": false, "forcedJSONParsing": true, "silentJSONParsing": true}, "url": "/token", "validateStatus": [Function validateStatus], "xsrfCookieName": "XSRF-TOKEN", "xsrfHeaderName": "X-XSRF-TOKEN"}
I hope I can find some help here, Thanks.
Use redux and proper async/await function to use. Otherwise, Axios return an error.
enter link description here
recommendation
use better only node.js and express ,axios isn't necesary because express is same

SWR not working properly with async fetch

Recently updated SWR - now for some reason my data is not fetching properly.
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
[`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
apiRequest
);
Using this fetching,
import firebase from "./firebase";
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
console.log("error", response);
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
// Check if a indexDb database exists
export function indexedDbdatabaseExists(dbname, callback) {
const req = window.indexedDB.open(dbname);
let existed = true;
req.onsuccess = function () {
req.result.close();
if (!existed) window.indexedDB.deleteDatabase(dbname);
callback(existed);
};
req.onupgradeneeded = function () {
existed = false;
callback(existed);
};
}
Now I'm looking at this StackOverflow thread,
useSWR doesn't work with async fetcher function
And thinking I'll just remake the fetcher to be without Async. I'm just wondering why this has stopped working though in general, and if I can just keep my existing codebase.
The error is a 400 message, it only happens with this expressions API call which takes longer to load due to the amount of data I think,
xxxx/dashboard/expression/get-expression-analytics?startTime=1648183720488&endTime=1650865720488 400 (Bad Request)
with error log
These calls are working fine, they have substantly less data though.
const { data: overall, error: psychometricError } = useSWRImmutable(
`dashboard/psychometric/get-psychometric-home?starttime=infinite`,
apiRequest
);
const { data: sentimentData, error: sentimentError } = useSWRImmutable(
[`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`, startDate, endDate],
fetchSentiment
);
Made an update to the fetch call to be more readable and specifically about the URL pathway.
import firebase from './firebase';
// Create an Error with custom message and code
export function CustomError(code, message) {
const error = new Error(message);
error.code = code;
return error;
}
export async function expressionsRequest(path, method = 'GET') {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
return fetch(`/api/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
})
.then((response) => {
if (!response.ok) {
throw `Server error: [${response.status}] [${response.statusText}] [${response.url}]`;
}
return response.json();
})
.then((receivedJson) => {
if (receivedJson.status === 'error') {
// Automatically signout user if accessToken is no longer valid
if (receivedJson.code === 'auth/invalid-user-token') {
firebase.auth().signOut();
}
throw new CustomError(receivedJson.code, receivedJson.message);
} else {
return receivedJson.data;
}
})
.catch((err) => {
console.debug('Error in fetch', err);
throw err;
});
}
Additionally, this is what the lambda function (using next API folder) looks like,
const requireAuth = require('../../_require-auth');
const { db } = require('../../_sql');
export default requireAuth(async (req, res) => {
const { uid: id } = req.user;
const startTime = Math.round(req.query.startTime * 0.001);
const endTime = Math.round(req.query.endTime * 0.001);
const parameters = [id, startTime, endTime];
//sql injection definitely possible here, need to work out better method of dealing with this.
const sqlText = `SELECT a,b,c,d,e,f,g,h,i FROM tablename WHERE a=$1 AND i BETWEEN $2 AND $3;`;
try {
const { rows } = await db.query(sqlText, parameters);
return res.status(200).json({
code: 0,
data: rows,
});
} catch (error) {
return res.status(200).json({
code: 0,
message: 'Error occurred in getting tablename',
error,
});
}
});
using postman with the same query, i.e.,
curl --location --request GET 'http://localhost:3000/api/dashboard/expression/get-expression-analytics?startTime=1648387240382&endTime=1651069240382' \
--header 'Authorization: Bearer xxxx' \
--data-raw ''
Successfully returns a response with data attached.
Based on your first code blocks, the startDate value is getting passed into the fetcher as method, and the endDate value is getting passed into the fetcher as data. This is based on the useSWR docs about passing in an array for the key argument: https://swr.vercel.app/docs/arguments#multiple-arguments
If the code you provided is correct, I'd assume the 400 is coming from trying to pass in a random value for the method option for fetch.
This should be fixed by only passing the API endpoint path into useSWR instead of an array:
const { data: expressionsData, error: expressionsError } = useSWRImmutable(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);

Cookie error while trying to authenticate for data scraping

I'm trying to scrape some data from truepush website, but first it needs to be authenticated. So here is what I'm doing:
const loginUrl = 'https://app.truepush.com/api/v1/login'
let loginResult = await axios.get(loginUrl)
.then(({ headers }, err) => {
if (err) console.error(err);
return headers['set-cookie'][0];
})
.then((cookie, err) => {
if (err) console.error(err);
const splitByXsrfCookieName = cookie.split("XSRF-TOKEN=")[1]
return splitByXsrfCookieName.split(';')[0];
}).then(xsrfToken => {
return axios.post(loginUrl, {
headers: {
"Content-Type": "application/json",
"X-XSRF-TOKEN": xsrfToken
}
})
}).then(res => console.log(res))
It throws xrsfToken on second then response and when I try to login in third response with that xsrf token, it shows me this error:
{
"status_code": "XSRF-ERROR",
"status": "ERROR",
"message": "Cross domain requests are not accepting to this endpoint. If you cleared the cookies, please refresh your browser."
}
I'm not sure what wrong I'm doing :(
The main issue is that the call also requires the original cookie to be sent. You need to keep the original cookie your get from set-cookie header and pass it in cookie header in the second call like cookie: originalCookie. Also in your code, there is no body sent in the POST call.
The following code reproduces the login :
const axios = require("axios");
const originalUrl = 'https://app.truepush.com';
const loginUrl = 'https://app.truepush.com/api/v1/login';
const email = "your-email#xxxxxx";
const password = "your-password";
(async () => {
await axios.get(originalUrl)
.then(({ headers }, err) => {
if (err) console.error(err);
const cookie = headers['set-cookie'][0];
return {
cookie: cookie,
xsrfToken: cookie.split("XSRF-TOKEN=")[1].split(";")[0]
};
})
.then((data, err) => {
if (err) console.error(err);
return axios.post(loginUrl, {
"email": email,
"password": password,
"keepMeLoggedIn": "yes"
}, {
headers: {
"X-XSRF-TOKEN": data.xsrfToken,
"cookie": data.cookie
}
})
})
.then(res => console.log(res.data))
})();
Output:
{
status_code: 'SUCCESS',
status: 'SUCCESS',
message: 'Login Successful',
data: {
id: 'xxxxxxxxxxxxxxxxxxx',
name: 'xxxxx',
email: 'xxxxxxx#xxxxxx'
}
}
Note that both cookie and xsrfToken are consumed by the second promise

How to get accessToken of React Native GoogleSignIn?

I am not able to get accessToken, i need it in my backend api.
When i try this,
googleLogin = () => {
GoogleSignin.signIn()
.then((data) => {
console.log("TEST "+JSON.stringify(data));
var postData = {
access_token: data.accessToken,
code: data.idToken,
};
let axiosConfig = {
headers: {
'Content-Type': 'application/json',
"Accept": "application/json",
}
};
....
//Backend api axios call
....
})
.then((user) => {
console.log("TEST G LOGIN 1 "+JSON.stringify(user))
})
.catch((error) => {
console.log("....."+JSON.stringify(error))
});
}
got this response, it doesn't inculde
accessToken
{
"scopes": ["https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/drive.readonly"],
"serverAuthCode": "4/jwE5LLLLLLa7-f33333jmMD2V978oyp44444eb9yGe_qhHnkXXXXXXXLFXKZlQdDzlH-iIJx_gzqlLLLLLL3Q0PP0",
"idToken": "...v65a4MCC-ZUQmys_wf_DoCOBJEMuI........",
"user": {
"photo": "https://lh3.googleusercontent.com/-tLLLLLyeS0KE/AAAMMMMAAAI/AAAAAAAAAAA/ACHi3reMhihoeTe_6NjL666666EUVU82Q/s96-c/photo.jpg",
"email": "test#gmail.com",
"familyName": "tech",
"givenName": "test",
"name": "testtech",
"id": "11688888817288868"
}
}
as per documentation
getTokens() Resolves with an object containing { idToken: string,
accessToken: string, } or rejects with an error. Note that using
accessToken is discouraged.
So, i tried this in
GoogleSignin.Sign({
....
var gettoken = GoogleSignin.currentUserAsync(data.user).then((token) => {
console.log('USER token', token);
}).done();
...
})
it got error and also tried const token = GoogSignIn.getTokens(), it return null.
package.json info
{
...
"react": "16.8.3",
"react-native": "0.59.9",
"react-native-firebase": "5.3.1",
"react-native-google-signin": "^2.0.0"
...
}
Please suggest what would be procedure to get accessToken.
Finally i get accessToken.
Step 1:-
I deleted all the generated clidenId in goolge developer console (keep only web application clientId as i used in my web project) and also deleted android app in firebase project.
Step 2:-
Created new android app in firebase and download google-services.json and paste it in android/app/google-services.json
Step 3:-
Copied the clidenId from this part of google-services.json
...
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "xxxxxxx-xxxxx.apps.googleusercontent.com", //<--- copied this clientID
"client_type": 3
},
{
"client_id": "XXXXXXXXXX-fBBBBBBBBBBBBBBBBBBBBBBBBpugnhrade.apps.googleusercontent.com",
"client_type": 2,
"ios_info": {
"bundle_id": "com.projectios"
}
}
]
}
}
...
and paste in
GoogleSignin.configure({
webClientId: 'paste it here',
});
Step 4:-
This is the code to get accessToken
(But this code was not working in my previous google-services.json file)
googleLogin = () => {
GoogleSignin.signIn()
.then((data) => {
console.log("TEST " + JSON.stringify(data));
const currentUser = GoogleSignin.getTokens().then((res)=>{
console.log(res.accessToken ); //<-------Get accessToken
var postData = {
access_token: res.accessToken,
code: data.idToken,
};
let axiosConfig = {
headers: {
'Content-Type': 'application/json',
"Accept": "application/json",
}
};
-----
backend api call
-----
});
})
.then((user) => {
console.log("TEST G LOGIN 1 " + JSON.stringify(user))
})
.catch((error) => {
console.log("....." + JSON.stringify(error))
});
}

Not getting data in fetch even if the status is 200 in react

I am having below problem with the fetch function:
React code:
componentDidMount() {
this.userService.getLoggedInUser()
.then(user => {
this.setState({user: user});
console.log(this.state.user);
})
}
This the course service file code:
getLoggedInUser(){
const USER_API_URL = API_URL + "/api/profile";
return fetch(USER_API_URL, {
headers : {
'Content-Type' : 'application/json'
},
method : "POST"
}).then(response => response.clone()).then(data => {
console.log(data);
return data;
}).catch(function (err) {
console.log(err)
});
}
I am just trying to get the logged in user from the server. While using postman to do the same, I am getting the output as expected.
Server Code:
#PostMapping("/api/loggedInUser")
public Faculty getLoggedInUser(HttpSession session){
return (Faculty)session.getAttribute("currentUser");
}
Class in the server is defined as:
#RestController
#CrossOrigin(origins = "http://localhost:3000", allowCredentials ="true")
public class UserService {
In postman, I am getting the below output:
{
"id": 100,
"username": "bird",
"password": "bird",
"firstName": "Alice",
"lastName": "Kathie"
}
But in the react app, I am getting in the console as:
Response {type: "cors", url: "http://localhost:8080/api/profile", redirected: false, status: 200, ok: true, …}
But there is no data body to return or parse. I am not sure what I am doing wrong here. I have tried changing the then method in the fetch to various types, like response.clone().json() etc, but, in most cases, I am getting the output as "promise rejected, unexpected end of json input".
How can I solve this problem?
Thanks
Looks like the error is in how you are handling your response:
}).then(response => response.clone()).then(data => {
The data in your second .then() isn't returning the fetch response, it is returning the details of the fetch itself. In .then(response => you probably want to do:
.then(response => {
return response.json()
}
It isn't clear what you are trying to do with response.clone(), as this typically creates a clone of the response for use with caching or something -- what are you trying to do with the clone?
If you're using it in a cache function maybe you could:
.then(response => {
someCacheFunction(response.clone())
return response.json()
}
or if you are setting it to a pre-defined variable for some use:
var responseClone;
... // code omitted
.then(response => {
responseClone = response.clone()
return response.json()
}
Found the answer. Main problem was with the cookies. While fetching, we need to make sure following is set:
getLoggedInUser = () => {
const USER_API_URL = API_URL + "/api/profile";
return fetch(USER_API_URL, {
headers : {
'Content-Type' : 'application/json'
},
method : "POST",
'credentials': 'include'
}).then(response => {
console.log(response);
return response.json()
}).catch(function (err) {
console.log(err)
});
}
"credentials":"include" is necessary so that the browser is accepting cookies and the same cookie is used to retrieve the data from the server.
I've found that fetch is unreliable. Try axios instead. See https://axios-http.com/docs/intro for info, or run npm i axios and add it to your project with import axios from 'axios', then call axios.get(YOUR_URL).
Probably too old a thread by now, but maybe this will help a little.

Categories