I'm trying to configure the backend API on my app and here's the code to send a request:
static async xhr(endpoint, args, method) {
const url = `${API_SERVER}${endpoint}`;
let formBody = [];
for (let property in args) {
let encodedKey = encodeURIComponent(property);
let encodedValue = encodeURIComponent(args[property]);
formBody.push(encodedKey + "=" + encodedValue);
}
formBody = formBody.join("&");
let options = Object.assign({ method: method }, args ? { body: formBody } : null );
try {
let headers = {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
};
accessToken = 'bdi8HD8js91jdoach7234h';
if(accessToken != null)
headers['Authorization'] = accessToken;
options.headers = headers;
return fetch(url, options).then( resp => {
console.log(resp);
let json = resp.json();
if(resp.status >= 200 && resp.status < 300) {
if (resp.ok) {
return json
}
} else {
return {};
}
return json.then(err => {throw err});
});
} catch (error) {
return null;
}
}
Note: I debugged and found that the headers are correctly getting added to the options variable, but for some reason, the server isn't receiving the Authorization header.
I used Postman to send the exact same request with the exact same headers and I'm getting the correct response via it. I have no idea what's wrong, except it would only be so if the headers aren't getting sent in the first place.
Can someone please tell me what am I doing wrong? Thanks!
The headers option has to be an instance of Headers. You can transform your current headers object to a Headers instance by passing it to its constructor like this:
const headers = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
});
Note that I also replaced let with const since that variable is not going to be reassigned.
To change a header or add a new header to that Headers instance, you can use the set method. Instead of headers['Authorization'] = accessToken you'd do ...
headers.set('Authorization', accessToken)
Related
I have the following two functions described in my Cloudflare worker code. However, when I run the code and these functions get called I get a TypeError: Cannot reconstruct a Request with a used body -
async function getApiV2Token(env: Env): Promise<string> {
let API_URL = await getApiUrl(env)
let API_CLIENT_ID = await getApiClientId(env)
let API_CLIENT_SECRET = await getApiClientSecret(env)
let apiUrl = `${API_URL}/auth`
let data = `client_id=${API_CLIENT_ID}&client_secret=${API_CLIENT_SECRET}`
let response, responseJSON
try {
response = await postData(apiUrl, data)
responseJSON = await response.json()
} catch (e) {
console.log(`Error: ${e}.`)
return ""
}
return responseJSON.auth_token
}
async function postData(url: string, data: string): Promise<Response> {
let request = new Request(url, {
method : 'POST',
body : data,
headers: {
'User-Agent': 'cloudflare-worker',
'Content-Type': 'application/x-www-form-urlencoded'
},
redirect : 'manual'
})
let cache = caches.default
let cachedResponse = await cache.match(request.url)
if (cachedResponse) {
return cachedResponse
}
console.log("checked cache")
let response = await fetch(request)
console.log("Requested new token")
let newResponseForExpiresIn = response.clone()
let newResponseForExpiresInJSON = await newResponseForExpiresIn.json()
let expires_in = newResponseForExpiresInJSON.expires_in
let newResponseForHeader = response.clone()
let newResponseToCache = new Response(newResponseForHeader.body, newResponseForHeader)
newResponseToCache.headers.set('Cache-Control', `max-age=${expires_in - API_TOKEN_GRACE_PERIOD}`)
cache.put(request.url, newResponseToCache)
return response
}
The line it fails at is let response = await fetch(request) (found this because "checked cache" is logged but not "Requested new token".
Here's what I've tried -
newRequest = request.clone() and then fetching that instead.
creating an identical request with new Request() and then fetching that.
same as above but using slice() to copy data
I also looked at Cloudflare Worker TypeError: One-time-use body but because I'm using the Cloudflare Worker Modules I don't have access to event. Any suggestions?
EDIT: I logged request.bodyUsed before the offending line and it logs false
I was able to fix this by constructing the request within fetch, not sure why.
const response = await fetch(url, {
method : 'POST',
body : data,
headers: {
'User-Agent': 'cloudflare-worker',
'Content-Type': 'application/x-www-form-urlencoded'
}
})
The problem I'm having is accessing the value within the return statement in the flask route (resp["login"]). I'm trying to use this value in a javascript file and use it as a query parameter. But whenever i try the console.log() in the javascript file I get a promise object. But I am not able to find where I could find the value coming in from the Flask app. I thought it would be within the response object below but no such luck.
#app.route('/route', methods=['GET', 'POST'])
#cross_origin(supports_credentials=True)
def handle_callback():
if request.method == 'POST':
payload = {
blahhh
}
headers = {'Accept': 'application/json', 'Access-Control-Allow-Origin': '*'}
req = requests.post(token_url, params=payload, headers=headers)
# make another request after this using access token with updated header field
resp = req.json()
if 'access_token' in resp:
oauthHeader = "token " + resp['blahhh']
headers = {'Authorization': oauthHeader}
access_token_url = 'https://blahhh.com'
r = requests.get(url=access_token_url, headers=headers)
resp = r.json()
return resp["login"]
else:
return "error", 404
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const codeGit = urlParams.get('code')
const sub = {codeGit};
const res = fetch('http://localhost:4000/route', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'code': codeGit
},
credentials: 'include'
}).then(response => {
if(response.status == 200){
console.log('Success! ' + response.json() )
}
}).catch(error => {
console.log('error with access token req!')
})
console.log(res)
response.json() return a promise
Return value: A Promise that resolves to a JavaScript object. This object could be anything that can be represented by JSON — an object, an array, a string, a number...
.then(response => {
if(response.status == 200){
return response.json();
} else {
// handle this somehow
}
}).then(json => {
console.log('Success! ' + JSON.stringify(json))
}).catch(error => {
console.log('error with access token req!')
})
So I have a service worker for fetch:
self.addEventListener('fetch', (event) => {
const requestProcessor = (idToken) => {
let req = event.request;
// For same origin https requests, append idToken to header.
if ((self.location.protocol === 'https:' ||
self.location.hostname === 'localhost') &&
idToken) {
// Clone headers as request headers are immutable.
const headers = new Headers();
for (let entry of req.headers.entries()) {
headers.append(entry[0], entry[1]);
}
// Add ID token to header.
headers.append('Authorization', self.location.origin === getOriginFromUrl(event.request.url) ? `Bearer ${idToken}` : idToken);
try {
req = new Request(req.url, {
method: req.method,
headers: headers,
mode: self.location.origin === getOriginFromUrl(event.request.url) ? 'same-origin' : req.mode,
credentials: req.credentials,
cache: req.cache,
redirect: req.redirect,
referrer: req.referrer,
body: req.body,
bodyUsed: req.bodyUsed,
context: req.context
});
} catch (e) {
console.error(e);
}
}
return fetch(req);
};
event.respondWith(getIdToken().then(requestProcessor));
});
It is being called in another file like so:
export const makePostRequest = (url = '', params = {}) => {
return fetch(url, {
method: 'POST',
body: JSON.stringify(params),
headers: {
'Content-type': 'application/json'
}
}).then((res) => res).catch((err) => console.log(err));
};
For some reason, the req.body is always undefined inside of the service worker. Furthermore, it looks like the fetch request happens twice. When I put a breakpoint and step through the code, I can see that nothing from the fetch is being picked up by the service worker. I don't understand.
Okay, so this isn't obvious. So after some research this solved my issue:
self.addEventListener('fetch', (event) => {
if (getOriginFromUrl(event.request.url) === 'https://app.example.com') {
const requestProcessor = (idToken) => {
let newRequest = null;
// For same origin https requests, append idToken to header.
if ((self.location.protocol === 'https:' || self.location.hostname === 'localhost') && idToken) {
try {
newRequest = new Request(event.request, {
headers: new Headers({
...event.request.Headers,
'Content-Type': 'application/json',
Authorization: 'Bearer ' + idToken,
})
})
} catch (e) {
console.log(e);
}
}
return fetch(newRequest);
};
/* Fetch the resource after checking for the ID token.
This can also be integrated with existing logic to serve cached files
in offline mode.*/
event.respondWith(getIdToken().then(requestProcessor, requestProcessor));
}
});
I also had to set the mode:
export const makePostRequest = (url = '', params = {}) => {
return fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(params)
}).then((res) => res).catch((err) => console.log(err));
};
There were two issues:
By default the header's mode was set to no-cors. According to a previous SO answer, I had to set the mode to cors to allow for non-basic headers which would also include body.
The other issue had to do with the headers being immutable. This had to be changed to copy properly.
The Request object will implement methods like .blob().
await req.blob()
I am working on a react application, where i am checking for the availability of token in local storage, based on the token existence i need to set the headers.
I have tried by initially initializing the JavaScript object outside the loop and then set the headers in the if else condition.
getAllTopics() {
const token = localStorage.getItem('authKey');
var config = {};
if(token){
const URL = API_URL + `api/get-home-ideas-auth`;
var config = {
'Accept' : 'application/json',
'Authorization' : `Bearer ` + token
}
} else {
const URL = API_URL + `api/get-home-ideas`;
var config = {
'Accept' : 'application/json'
}
}
axios.get(URL, {headers : config})
.then(res => {
if (res.data && res.data.status === 1) {
const topics = res.data.data;
console.log(topics);
this.setState({ topics: topics, showloader:false});
}
})
.catch(e => {console.error(e); throw e;});
}
I am getting error Cannot GET /function URL()[nativecode]
This is a scoping issue, the problem is you initialize a new config variable inside the if-else blocks instead of referencing the one already defined outside of the scope. The new config variable is not accessible outside the private if-else scope. The outer config is never actually updated.
Just refer to the original config like so:
getAllTopics() {
const token = localStorage.getItem('authKey');
var config = {};
var URL = '';
if(token){
URL = API_URL + "api/get-home-ideas-auth";
config = {
'Accept' : 'application/json',
'Authorization' : `Bearer ${token}`
}
} else {
URL = API_URL + "api/get-home-ideas";
config = {
'Accept' : 'application/json'
}
}
axios.get(URL, {headers : config})
.then(res => {
if (res.data && res.data.status === 1) {
const topics = res.data.data;
console.log(topics);
this.setState({ topics: topics, showloader:false});
}
})
.catch(e => {console.error(e); throw e;});
}
getAllTopics() {
const token = localStorage.getItem('authKey');
const URL = API_URL + `api/get-home-ideas-auth`;
var config = {
'Accept' : 'application/json',
...(token && {'Authorization' : `Bearer ` + token})
}
axios.get(URL, {headers : config})
.then(res => {
if (res.data && res.data.status === 1) {
const topics = res.data.data;
console.log(topics);
this.setState({ topics: topics, showloader:false});
}
})
.catch(e => {console.error(e); throw e;});
}
Although already answered, the most clean way to do this is through interceptors:
/**
* Create an Axios Client with defaults
*/
const client = axios.create({
baseURL: API.BASE_URL,
});
/*
* Request interceptor
* Useful for refreshing token before to make a new request and get 401 responses
*/
client.interceptors.request.use(
config => {
const originalRequest = _.cloneDeep(config);
// Using lodash _.set() we avoid undefined keys in path
_.set(originalRequest, 'headers.Authorization', getAuth());
return originalRequest;
},
err => Promise.reject(err),
);
How to update an axios instance while intercept a response using data from this response without second request? New token can be received in any response after any request. Last received token should be used in any new request.
const request = (axios as any).create({
baseURL: mainConfig.apiBaseUrl,
headers: {
'Content-Type': 'application/json',
},
});
// Check token relevance and update if not relevance.
request.interceptors.response.use(response => {
if (response.headers.token !== undefined) {
response.config.headers.token = response.headers.token;
}
return response;
});
Here, you can't set it for further requests like this.
You should use globalStorage or any other store for storing this token.
import Store from "Store";
const {token} = Store;
const request = (axios as any).create({
baseURL: mainConfig.apiBaseUrl,
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
});
// Set token
request.interceptors.response.use(response => {
const {token} = response.headers;
if (token) {
Store.setToken(token);
}
return response;
}