It mess me up... I'm frustrated.
How to use the 'validToken' variable to add it to the auth line for headers? It catches the error message (fetchHeaders func)...
I can't understand why the 'axios' authentification doesn't work for auth request (returns 'headers fetched with error!'), but works if I set validToken hardcoded..
It returns me validToken correctly for template...
Pls help!
Thx in advance!
#App.vue
<script>
import axios from 'axios';
const FormData = require('form-data');
const API_URL = "https://my_api_path.com/";
let data = new FormData();
data.append('username', 'my_username');
data.append('password', 'my_password');
let config = {
method: 'post',
url: `${API_URL}/login`,
data: data
}
let validToken = ""
export default {
data() {
return {
validToken: "",
headers: []
}
},
methods: {
async userLogin() {
try {
await axios(config)
.then((resp) => {
this.validToken = resp.data.access_token;
});
Token = this.validToken;
} catch(err) {
console.log(err)
}
},
async fetchHeaders() {
try {
let config = {
headers: {
Authorization: `Bearer ${validToken}`
}
}
const resp = await axios.get(`${API_URL}/headers/`,
config
)
this.headers = resp.data;
} catch (err) {
console.error("headers fetched with error!");
}
}
},
mounted() {
this.userLogin(),
this.fetchHeaders()
}
}
</script>
Fixed according the #EstusFlask recommendation.
'userLogin' func moved to mounted:
async mounted() {
await axios(config)
.then((resp) => {
validToken = resp.data.access_token
});
}
Related
I got following error at file.getSignedUrl. I have other function to copy the file and create new file on Cloud Storage. Why this function need permission and where do I need to set?
Error: The caller does not have permission at Gaxios._request (/layers/google.nodejs.yarn/yarn_modules/node_modules/gaxios/build/src/gaxios.js:129:23) at runMicrotasks () at processTicksAndRejections (node:internal/process/task_queues:96:5) at async Compute.requestAsync (/layers/google.nodejs.yarn/yarn_modules/node_modules/google-auth-library/build/src/auth/oauth2client.js:368:18) at async GoogleAuth.signBlob (/layers/google.nodejs.yarn/yarn_modules/node_modules/google-auth-library/build/src/auth/googleauth.js:662:21) at async sign (/layers/google.nodejs.yarn/yarn_modules/node_modules/#google-cloud/storage/build/src/signer.js:103:35) { name: 'SigningError' }
const functions = require("firebase-functions");
const axios = require("axios");
const { Storage } = require("#google-cloud/storage");
const storage = new Storage();
// Don't forget to replace with your bucket name
const bucket = storage.bucket("projectid.appspot.com");
async function getAlbums() {
const endpoint = "https://api.mydomain.com/graphql";
const headers = {
"content-type": "application/json",
};
const graphqlQuery = {
query: `query Albums {
albums {
id
album_cover
}
}`,
};
const response = await axios({
url: endpoint,
method: "post",
headers: headers,
data: graphqlQuery,
});
if (response.errors) {
functions.logger.error("API ERROR : ", response.errors); // errors if any
} else {
return response.data.data.albums;
}
}
async function updateUrl(id, url) {
const endpoint = "https://api.mydomain.com/graphql";
const headers = {
"content-type": "application/json",
};
const graphqlQuery = {
query: `mutation UpdateAlbum($data: AlbumUpdateInput!, $where:
AlbumWhereUniqueInput!) {
updateAlbum(data: $data, where: $where) {
id
}
}`,
variables: {
data: {
album_cover: {
set: url,
},
},
where: {
id: id,
},
},
};
const response = await axios({
url: endpoint,
method: "post",
headers: headers,
data: graphqlQuery,
});
if (response.errors) {
functions.logger.error("API ERROR : ", response.errors); // errors if any
} else {
return response.data.data.album;
}
}
const triggerBucketEvent = async () => {
const config = {
action: "read",
expires: "03-17-2025",
};
const albums = await getAlbums();
albums.map((album) => {
const resizedFileName = album.id + "_300x200.webp";
const filePath = "images/albums/thumbs/" + resizedFileName;
const file = bucket.file(filePath);
functions.logger.info(file.name);
file.getSignedUrl(config, function (err, url) {
if (err) {
functions.logger.error(err);
return;
} else {
functions.logger.info(
`The signed url for ${resizedFileName} is ${url}.`
);
updateUrl(album.id, url);
}
} );
});
};
exports.updateResizedImageUrl = functions.https.onRequest(async () => {
await triggerBucketEvent();
});
I need to add Service Account Token Creator role for App Engine default service account.
I have a separate fetch request function that logins user and saves auth token to localStorage, then my data request fetch should be send with that saved token bearer, but data fetch doesn't wait for token and receives Unauthorized access code.
My data request fetch looks like this :
// to check for fetch err
function findErr(response) {
try {
if (response.status >= 200 && response.status <= 299) {
return response.json();
} else if (response.status === 401) {
throw Error(response.statusText);
} else if (!response.ok) {
throw Error(response.statusText);
} else {
if (response.ok) {
return response.data;
}
}
} catch (error) {
console.log("caught error: ", error);
}
}
const token = JSON.parse(localStorage.getItem("token"));
// actual fetch request
export async function getData() {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
My fetch requests are in a different js file, I'm importing them in my components like this:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
importGetDataFunc();
}, []);
return (
<div>
</div>
)
}
export default getInfo
Now when I go to the getInfo component after login at first fetch request returns 401, but after I refresh the page fetch request goes with token bearer and data gets returned. My problem is that I don't know how to make getData() fetch request to wait until it gets token from localStorage or retry fetch request on 401 code. I tried to implement if statement like this
useEffect(() => {
if(token){
importGetDataFunc();
}
}, []);
where useEffect would check if token is in localStorage and only then fire fetch request, but it didn't work. Any help on how I can handle this would be greatly appreciated.
You are close. You need to add token as a dependency to your useEffect. Also, you need to move your token fetching logic into your component.
Something like this should work:
import React, { useState, useEffect } from "react";
function getInfo() {
const [info, setInfo] = useState()
const token = JSON.parse(localStorage.getItem("token"));
const importGetDataFunc = async () => {
const data = await getData();
setInfo(data);
};
useEffect(() => {
if(token) {
importGetDataFunc(token);
}
}, [token]);
return (
<div>
</div>
)
}
export default getInfo
You can also modify your importGetDataFunc to receive the token as a parameter.
const importGetDataFunc = async (token) => {
const data = await getData(token);
setInfo(data);
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${token}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
What actually helped me is to make a function to check for a token inside get fetch request, like this:
export const findToken = () => {
const token =localStorage.getItem("token")
return token;
};
export async function getData(token) {
const url = `${URL}/data`;
var obj = {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + `${findToken()}`,
},
};
const data = await fetch(url, obj)
.then((response) => findErr(response))
.then((result) => {
return result.data;
});
return data;
}
If I put my config as the second argument, my cancellation token (third arg) gets ignored. However I need the Authorization header because this get request will be (but isn't yet!) Behind authentication middleware on my node API. So my question is: Where do I put my config?
const config = {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
};
const getBoards = async (cancelToken) => {
try {
if (localStorage.getItem("token") == null) {
throw new Error();
}
const response = await Axios.get("/boards", {
cancelToken: cancelToken.token,
});
setBoards(response.data);
} catch (e) {}
};
useEffect(() => {
const request = Axios.CancelToken.source();
getBoards(request);
return () => {
request.cancel();
};
}, []);
pass them in the same object.
const response = await Axios.get("/boards", {
headers: {},
cancelToken: cancelToken.token,
});
I have made a wrapper for fetch function for my API calls in react-native. I dont want to pass JWT token everytime that I make an API call, so I thought that fetching it inside wrapper will fix it for me, but I cannot get it to work because of async nature...
useFetch.js
// import qs from "querystring";
import { getUserAuthToken } from "../storage";
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const fetchAuthToken = getUserAuthToken();
const useFetch = (baseURL, authHeader = null) => {
console.log(fetchAuthToken);
**//Cannot get this token in time for the API call ^**
const defaultHeader = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader ? authHeader :fetchAuthToken,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = body;
return fetch(url, options);
};
const get = async (endpoint) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
export default useFetch;
storage.js
import AsyncStorage from "#react-native-community/async-storage";
export const getUserAuthToken = async () => {
try {
const userToken = await AsyncStorage.getItem("userAuthToken");
return userToken;
} catch (e) {
console.log("error");
}
};
exportAPI.js
import useFetch from "./fetch";
const LOCAL_IP = "192.168.0.131";
export const authAPI = (header) => useFetch(`http://${LOCAL_IP}:8000`, header);
export const activityAPI = useFetch(`http://${LOCAL_IP}:8000`);
Steps.js
import React, { useEffect, useState } from "react";
import { Text, Platform } from "react-native";
import { CardXLarge } from "../../../components/Cards/";
import fitnessKitApis from "../../../utilities/fitnessKits";
import { activityAPI } from "../../../utilities/apis";
const StepCard = (props) => {
const fetchStepsFromFitnessKits = async () => {
if (Platform.OS === "android") {
await fitnessKitApis.historicSteps().then((res) => {
setSteps(res);
});
} else {
await fitnessKitApis.historicSteps((result) => setSteps(result));
}
};
const [steps, setSteps] = useState(0);
useEffect(() => {
fetchStepsFromFitnessKits();
const requestParams = { date: new Date(), steps };
const { data, statusCode, error } = activityAPI.get(
"/v1/user/steps/",
requestParams,
);
// console.log(data, statusCode, error);
}, [steps]);
return (
<CardXLarge>
<Text>{steps}</Text>
</CardXLarge>
);
};
export default StepCard;
I know I can pass authHeader from component but that will result in adding 2-3 lines of code in every component which is not super convenient.
Thanks
If you don't want to use async/await in a function to get items from asyncStorage. You can use either callback or promise in place of async/await.
Callback:
AsyncStorage.getItem('data1', (error, data1) => {
// perform your logic here.
});
Promise:
AsyncStorage.getItem('data1').then(data1=>{
// perform your logic here.
}).catch(error=>{
// handle error
})
I have personally used callback for getItem and worked perfectly. I have not tested the promise version but I expect to do the same work as callback.
...
const fetchAuthToken = getUserAuthToken();
...
Your getUserAuthToken is an asynchronous function and here it is not being awaited. To guarantee that asynchronous call is finished you have to await it or use callbacks as #HungrySoul suggested.
You can't await something outside of an asynchronous function.
Solution that I would suggest is creating a class UseFetch and passing the arguments through the constructor. The argument being here the JWT token that you are getting from the AsyncStorage.
Also, another thing that can be done and is a good practice - use redux for managing the state and keeping the JWT token. You might look into that. It will take a bit longer but it will make your code more elegant.
Edit: Or, you might try something like this.
Keep in mind that you have to wait for promises to resolve before you use what was promised.
Here we are using a closure. You will have to pass an argument (which is an async function) to the useFetchBuilder. That function will be awaited and provide the JWT. You can use the getUserAuthToken for that purpose.
Keep in mind that you have to use await or wait for the promise to resolve before using this function. Problem might be somewhere else in your code - maybe the life cycle methods.
I hope this helped.
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const useFetchBuilder = async (userTokenProvider) => {
const userToken = await userTokenProvider();
return (baseURL, authHeader = null) => {
const defaultHeader = {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader ? authHeader : userToken,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = body;
return fetch(url, options);
};
const get = async (endpoint) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
}
export default useFetchBuilder;
I moved getUserAuthToken function down to each request method function in useFetch function where I can await for the response. Then it all worked perfectly.. Also I could have use getUserAuthToken but usingAsyncStorage.getItem seems much cleaner
modified fetch.js
// import qs from "querystring";
import AsyncStorage from "#react-native-community/async-storage";
const responseChecker = async (response) => {
let error = "";
let data = {};
let statusCode = null;
if (!response.ok) {
error = "Something went wrong";
statusCode = response.status;
} else {
statusCode = response.status;
data = await response.json();
}
return { statusCode, error, data };
};
const useFetch = (baseURL, authHeader = null) => {
const defaultHeader = {
Accept: "application/json",
// "Content-Type": "application/x-www-form-urlencoded",
"Content-Type": "application/json",
key: "1c419c7e-3a34-49f0-9192-b48d4534dff3",
Authorization: authHeader,
};
const customFetch = (
url,
method = "GET",
body = false,
headers = defaultHeader,
) => {
const options = {
method,
headers,
credentials: "include",
};
if (body) options.body = JSON.stringify(body);
return fetch(url, options);
};
const get = async (endpoint) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "GET");
return responseChecker(response);
};
const post = async (endpoint, body = {}) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "POST", body);
return responseChecker(response);
};
const put = async (endpoint, body = {}) => {
await AsyncStorage.getItem("userAuthToken").then((result) => {
defaultHeader.Authorization = result;
});
const url = `${baseURL}${endpoint}`;
const response = await customFetch(url, "PUT", body);
return responseChecker(response);
};
return {
get,
post,
put,
};
};
export default useFetch;
I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
Repeating because Stackoverlow ask me to more describe text
I am working on refresh token. I faced some problems with context api store. Store gives me old value.
Please look at refreshToken method there is comment explaining error. I dont't understant why if I console.log(store) React return me old value not new value.
import React, { useState, createContext, useEffect } from 'react';
import {MainUrl, ApiUrl} from '../config';
export const StoreContext = createContext();
export const StoreProvider = props => {
const getToken = () => localStorage.getItem("token");
const initState = () => ({
token: getToken(),
isAuth: false,
userRole: "",
userName: "",
userGroupId: null,
mainUrl: MainUrl,
apiUrl: ApiUrl,
})
const [store, setStore] = useState(initState());
const getUserInfo = async () => {
if (getToken()) {
try {
const apiConfig = {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}get-me`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update Context API
setStore({
...store,
userRole: responseJson.role,
userName: responseJson.name,
userGroupId: responseJson.group_id,
isAuth: true,
})
} else if(response.status === 401) {
await refreshToken();
// Here I want call this function with refreshed token but React gives old token, although I updated token in refreshToken method
getUserInfo();
} else {
throw new Error(`Возникла ошибка во получения информации об пользователе. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
}
const logout = async (routerHistory) => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.apiUrl}logout`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Remove token from localstorage
localStorage.removeItem("token");
// Reset Context API store
setStore(initState());
// Redirect to login page
routerHistory.push("/");
} else if(response.status === 401) {
await refreshToken();
logout(routerHistory);
} else {
throw new Error(`Возникла ошибка во время выхода. Ошибка сервера: ${responseJson.error}`);
}
} catch (error) {
console.log(error);
}
}
const refreshToken = async () => {
try {
const apiConfig = {
method: "GET",
headers: {
"Accept": "application/json",
"Authorization": `Bearer ${store.token}`,
},
};
const response = await fetch(`${store.mainUrl}refresh-token`, apiConfig);
const responseJson = await response.json();
if (response.ok) {
// Update token in local storage
localStorage.setItem("token", responseJson.token);
// Update Context API
setStore({
...store,
userRole: 'some new role',
token: responseJson.token,
})
// Here I expect that userRole and token are changed but if I console.log(store) I get old token and null for userRole
console.log(store);
} else {
throw new Error(`Возникла ошибка во время обновления токена`);
}
} catch (error) {
throw error;
}
}
useEffect(() => {
// Get user info
getUserInfo();
}, [])
return(
<StoreContext.Provider value={[store, setStore, logout, getUserInfo]}>
{props.children}
</StoreContext.Provider>
);
}
Your setStore() function from useState() hook is an async function, hence you don't get the updated value from your console.log() call (just after the setStore()) immediately.
Also, there's no facility of providing a callback as the second argument to setStore() function, where you can log the updated state.
However, you can move your console.log(store) call inside a useEffect() hook, which triggers after every state update and ensures that you'll get the updated state.
useEffect(() => {
console.log(store);
})
So, as far as the title of your question "React Context API not updating store" is concerned, it's actually updating. It's just you are logging it before it is updated.
Hope this helps!