loglevel-plugin-remote keeps sending the same request to the API continuously - javascript

I have a React application that uses loglevel-plugin-remote, as well as a custom API endpoint to receive the logs for error monitoring.
My log configuration is
import log from 'loglevel';
import remote from 'loglevel-plugin-remote';
const customJSON = log => ({
message: log.message,
level: log.level.label,
stacktrace: log.stacktrace
});
if (process.env.REACT_APP_SEND_LOGS === 'true') {
const apiPath = `${process.env.REACT_APP_API_PATH}/log`;
remote.apply(log, {
format: customJSON,
url: apiPath,
stacktrace: {
depth: 10
}
});
}
export default log;
And an example call is
import log from '../utils/logger';
...
const getUserData = () => {
return axios({url: userApi, method: 'GET', headers: authContext.authorizationHeaders })
.then(response => response.data)
.catch(error => {
log.error(error);
})
};
However, when the log gets called once, it will continually send requests to the API until I finally refresh the page.

The problem appeared to be with loglevel-plugin-remote which only checks to see if there's a 200 success status code response from the API, or else it will keep trying indefinitely.
My API, however, was returning a 201 message which I thought made more sense for creating a log entry. But when I changed the response to 200, I no longer had the issue. It appears to be an open issue with the repo, but hopefully, this helps others if you're seeing the same thing.

Related

Uncaught (in promise); Thrown error in fetch() not being caught

I've already read tons of resources to try to help me on this. This gist did not solve it for me (https://github.com/github/fetch/issues/203#issuecomment-266034180). It also seemed like this (JavaScript Promises - reject vs. throw) would be my answer but it is not. Also this (Error thrown in awaited Promise not caught in catch block) and this (errors not being thrown after promise).
I'm developing a project using a Yii2 PHP server-side solution, and Vue frontend solution. The project has several resources (lessons, media, etc) and REST API endpoints on the server-side that all are used the same. My dev work would benefit from me creating a re-usable API client class (in native JS - not anyting Vue related). I created an 'abstract' class that I 'extend' for each resource and use its functions for the CRUD operations.
I'd like to set up some middleware functions that are going to process the response from the API so that will be handled in the same fashion after every request I make so that I don't have to reproduce that processing code in the Vue apps and components that are using those API client classes.
The code is using the native JS fetch() function. I'm using .then() and .catch() in the functions as needed to process responses and control the flow.
My problem is that I have a function to process the API response, and in it I throw an error if I receive a non-200 response. I've implemented .catch() blocks in several places but I always get an error "Uncaught (in promise)" regardless of putting catch() calls everywhere.
When a user starts watching a video, I make an API call to my server to update a status on a user_media record. So, in the Vue component, I use my UserMedia helper class to create() a resource on the server and implement a then() and catch() on that. When there is an error server-side, I expect the catch() to catch that error and handle it. But, I just get the error "Uncaught (in promise)" as if I'm not trying to catch the error at all.
In the code, I am using updateWatchedStatus() in the vimeo video component, that calls the UserMediaApi.create() which calls YiiApiHelper.request() which calls YiiApiHelper.processRestResponse() where the error is thrown. I've tried implementing catch() blocks all over the place but it's never caught.
CLEARLY, I don't understand something about either fetch(), promises, or catching errors. But I can't figure it out. It seems like the only way around this is to have to write a bunch more code to try to compensate. Any help is appreciated. Even if I'm going about this all wrong and should be doing it someway else entirely.
The full code for that can be seen here:
YiiApiHelper.js https://pastebin.com/HJNWYQXg
UserMediaApi.js https://pastebin.com/9u8jkcSP
Vimeo Video Vue Component https://pastebin.com/4dJ1TtdM
For brevity, here's what's important:
Generic API Helper:
const request = function(resource, options){
return fetch(resource, options)
.then(response => Promise.all([response, response.json()]));
}
const resourceUrl = function(){
return this.autoPluralizeResource ?
this.resourceName+'s' :
this.resourceName;
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options)
.then(this.processRestResponse);
}
const processRestResponse = function([response, body]){
if(!response.ok){
if(response.status == 422){
if(Array.isArray(body)){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
throw {
name: response.status,
message: messages.join("\n")
}
}
}
throw {
name: response.status,
message: (body.message) ?
body.message :
response.statusText
}
}
return Promise.all([response, body]);
}
export default {
baseUrl: '',
resourceName: '',
autoPluralizeResource: true,
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};
UserMedia helper:
import YiiApiHelper from './../../yiivue/YiiApiHelper.js';
export default {
...YiiApiHelper,
baseUrl: window.location.origin+'/media/api/v1',
resourceName: 'user-media',
autoPluralizeResource: false
}
VimeoVideo.js:
let updateWatchedStatus = function(watchedStatusId) {
if(!props.userMedia){
// --- User has no record for this media, create one
return UserMediaApi.create({
media_id: props.media.id,
user_id: props.userId,
data: {
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
// --- User has a record, update the watched status in the data
let data = {
...userMedia.value.data,
[Helper.WATCHED_STATUS_KEY]: watchedStatusId
}
return UserMediaApi.update(props.media.id+','+props.userId, {
data: data
}).then(([response, body]) => {
context.emit('userMediaUpdated', {userMedia: body});
return body;
}).catch(YiiApiHelper.handleErrorResponse);;
}
Figured out and fixed this a while ago and figured I should come back in case it helps anyone.
Wrapping the request in a promise, and passing its resolve/reject into promises returned was the solution.
The code below isn't complete but it's enough to illustrate what had to be done to get this working as intended:
const request = function(resource, options){
return new Promise((resolve, reject) => {
return fetch(resource, options)
.then(response => {
if(
options &&
options.method == "DELETE" &&
response.status == 204
){
// --- Yii2 will return a 204 response on successful deletes and
// --- running response.json() on that will result in an error
// --- "SyntaxError: Unexpected end of JSON input" so we will just
// --- avoid that by returning an empty object
return Promise.all([response, JSON.stringify("{}"), resolve, reject])
}
// --- Include resolve/reject for proper error handling by response processing
return Promise.all([response, response.json(), resolve, reject])
}).then(this.processRestResponse)
});
}
const create = function(postData, options){
const url = new URL(this.baseUrl+'/'+this.resourceUrl());
if(!options){
options = {};
}
options = {
method: 'POST',
body: JSON.stringify(postData),
...options,
}
if(!options.headers){
options.headers = {};
}
options.headers = {
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
"Content-Type": "application/json",
...options.headers
}
return this.request(url, options);
}
const processRestResponse = function([response, body, resolve, reject]){
// --- If the response is okay pass it all through to the function
// --- that will be handling a response
if(response.ok){
return resolve([response, body]);
}
// --- If there are validation errors prepare them in a string
// --- to throw a user friendly validation error message
if(
response.status == 422 &&
Array.isArray(body)
){
let messages = [];
body.forEach(validationError => {
messages.push(validationError.message);
})
return reject({
name: response.status,
message: messages.join("\n")
})
}
// --- If there is another error just provide the status text
// --- as a message (Yii provides this)
return reject({
name: response.status,
message: (body.message) ?
body.message :
response.statusText
})
}
export default {
baseUrl: '',
resourceUrl: resourceUrl,
request: request,
create: create,
processRestResponse: processRestResponse,
handleErrorResponse: handleErrorResponse
};

Being new to react native, implementing api integration but returning html

I am integrating api in react native but the response comes in html while it runs fine on postman. Here is my code
const params = new URLSearchParams();
params.append('userNameOrEmailAddress', 'abc#123.pqr');
params.append('password', '123456');
console.log("params", params);
axios.post(
apiUrl,
params,
{
headers: {
"Content-Type": "application/x-www-form-urlencoded"}
}).then(response => {
console.log("response", response.data);
const { targetUrl } = response.data;
console.log("target", targetUrl);
props = targetUrl;
console.log("props", props);
Alert.alert(
"App",
"User success",
[
{
text: "Ok",
onPress: () =>
navigation.navigate('MainView' , {
url: targetUrl
})
},
],
{
cancelable: true,
onDismiss: () =>
Alert.alert(
"This alert was dismissed by tapping outside of the alert dialog."
),
}
)
}).catch(error => {
console.log("error", error);
// const errors = {};
// this.setState({
// errors: errors,
// });
});
};
I have tried many methods but changes here don't work as desired, The api works fine sometimes but at other times, it returns html. Any help in this case will be really relieving.
The issue with this was expo app cache. Actually when I run this on a different device with new expo installed, it worked. That sounds strange, but it happened. Now every time I run into this issue, I uninstall expo and reinstall, it works. Don't know the exact issue but this solution, might be a temporary one, is working for me.
Update:
Resolved this issue by using anti-forgery token. There is an extra security required on this so got the token and passed in every api to resolve the same.

Authenticated requests after sign in with React Query and NextAuth

I'm having troubled sending an authenticated request to my API immediately after signing in to my Nextjs app using NextAuth. The request that is sent after signing in returns data for and unauthenticated user.
I believe the issue is that React Query is using a previous version of the query function with an undefined jwt (which means its unauthenticated). It makes sense because the query key is not changing so React Query does not think it's a new query, but, I was under the impression that signing in would cause loading to be set to true temporarily then back to false, which would cause React Query to send a fresh request.
I've tried invalidating all the queries in the app using queryClient, but that did not work. I've also used React Query Devtools to invalidate this specific query after signing in but it still returns the unauthenticated request. Only after refreshing the page does it actually send the authenticated request.
// useGetHome.js
const useGetHome = () => {
const [session, loading] = useSession();
console.log(`session?.jwt: ${session?.jwt}`);
return useQuery(
'home',
() => fetcher(`/home`, session?.jwt),
{
enabled: !loading,
},
);
}
// fetcher
const fetcher = (url, token) => {
console.log(`token: ${token}`);
let opts = {};
if (token) {
opts = {
headers: {
Authorization: `Bearer ${token}`,
},
};
}
const res = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${url}`, opts);
if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}
return res.json();
}
// Home.js
const Home = () => {
const { data: home_data, isLoading, error } = useGetHome();
...
return(
...
)
}
Attached is the console immediately after signing in. You can see the the session object contains the jwt after signing in, but in the fetcher function it is undefined.
console after signing in
Any help here is appreciated. Is there a better way to handle authenticated requests using React Query and NextAuth? Thank you!
I have tried a similar situation here and struggled the same thing but the enabled property worked fine for me and it is good to go right now.
https://github.com/maxtsh/music
Just check my repo to see how it works, that might help.

get data from dropdown for dynamic call api

So I use redux-hooks for state management, so in my application there is a dropdown, the value will be used to call api dynamically, in the action creator I made it like this
export const getFetchCasesSelectedCountry = (country) => (dispatch) => {
return (
axios.get(`https://covid19.mathdro.id/api/countries/${country}`).then((result) => {
let confirmed = result.data.confirmed.value;
let recovered = result.data.recovered.value;
let death = result.data.deaths.value;
dispatch({
type: GET_SELECTED_COUNTRY,
payload: {
countryConfirmed: confirmed,
countryRecovered: recovered,
countryDeath: death,
}
})
})
)
}
but i got this
error
how to get the value from the dropdown so that it can be entered into the action creator? is it possible? sorry if my question is hard to understand.
There can be various reasons for 404 issue:
it can be networking issue - I mean that requested URL is not accessible from your environment. I see from the code that you doing GET request so to test networking you can just open your browser with the URL that is being used in the action. https://covid19.mathdro.id/api/countries/USA for example.
Code that calls getFetchCasesSelectedCountry provides some weird country value that can result in a 404 error
Nevertheless the code that you've posted, does not handle errors that can arise from axios call (404 for example) so your store won't be aware of errors that can happen, hence component that is connected to the store also won't be aware of such problems.
From my experience, usual approach to handle such things with redux is to introduce more states, that will store error info:
// this is purely an example
// let say that you have such state
const state = {
loading: false,
hasError: false,
errorMessage: '',
data: null,
}
// reducer
function stateReducer(state, action) {
switch(action.type) {
case GET_SELECTED_COUNTRY_LOAD:
// set here 'loading' to true - started request execution
// return new state
case GET_SELECTED_COUNTRY_LOADED:
// set here 'loading' to false - got response
// set here data that came with action payload
// return new state
case GET_SELECTED_COUNTRY_FAILED:
// set here 'loading' to false - got error response or failed
// sethere 'errorMessage' that came with action payload
// return new state
}
}
// you'll have to create 2 new action creators to handle GET_SELECTED_COUNTRY_LOAD // and GET_SELECTED_COUNTRY_FAILED
// now your code should look like this
const getFetchCasesSelectedCountry = (country) => (dispatch) => {
return (
dispatch({ type: GET_SELECTED_COUNTRY_LOAD });
axios.get(`https://covid19.mathdro.id/api/countries/${country}`)
.then((result) => {
// do some stuff with result
dispatch({
type: GET_SELECTED_COUNTRY_LOADED,
payload: { /* useful data here */ }
});
).catch(err => {
dispatch({
type: GET_SELECTED_COUNTRY_FAILED,
payload: { /* useful error data here */ }
});
})
}
So whenever error happens component that is connected to store will be able to handle it (at least show errorMessage that is can get from the store)
The HTTP error 404, or more commonly called "404 error", means that the api you are trying to use could not be found on the server. This is a client-side incident which means either the endpoint has been deleted or moved, and the URL has not been modified accordingly, or that you have misspelled the URL.
read this for more information

Cancel old Promise.resolve if same method called multiple times with given timeframe

I am using Promise and axios in react to call POST api and fetch list of records.
Issue is when multiple API calls triggered then any one which response last is getting is updating state.
Where i want to use only last called API response.
Exp : call API 3 times with different postbody, in case first call response take time than 2nd & 3rd then callback is using response of 1st call to setstate, instead i want to forget 1 and second call and consider last call response only.
Following is example
Common File
const apiService = axios.create({
baseURL: 'https://example.com/api/,
});
function post(postData) {
return Promise.resolve(apiService.post('https://example.com/api/getuserlist', postData, {
headers: {'Authorization': `Bearer sdfsdfsdf-cvdfs`}
}));
}
Service File
static getUsers(postData) {
return post(postData);
}
React Component
function getUsersList = (Filters )=>{
getUsers({ "Filters": Filters }).then((response) => {
this.setState({ users: response.data})
})
}
Problem is when getUsersList get called multiple time whichever is last response is getting set to users state, where last call should be users list.
It's not yet possible to actually cancel promises in JavaScript (though there is a proposal for it).
However, it is possible to implement the functionality you want in React. Consider something like this:
// state shape:
// {
// loading: boolean
// apiPromise: null | Promise<{ data: User[] }>
// users: User[]
// }
getUsersList = async (Filters) => {
// raw promise - don't await it yet
const apiPromise = getUsers({ Filters })
this.setState({ apiPromise, loading: true })
// _here_ we await it
const response = await apiPromise
// check if it's the most recent API request we made
if (this.state.apiPromise === apiPromise) {
this.setState({ users: response.data, loading: false })
} else {
console.log("stale val discarded:", users)
}
}
CodeSandbox demo - simplified example with mocked API and single val rather than list of users. Try clicking the button many times in quick succession and watch the console output.
Using CPromise wrapper the code might look like this See the live demo:
const CPromise = require('c-promise2');
const axios= require('axios');
// Let's wrap axios get method to the CPromise
function get(url){
return new CPromise((resolve, reject, {onCancel})=>{
axios.get(url, {
cancelToken: new axios.CancelToken(function executor(cancel) {
onCancel(cancel)
})
}).then(resolve, reject);
});
}
let chain= null;
function makeRequest(url){
chain && chain.cancel();
chain= get(url).then((response)=> {
console.log(`Response ${JSON.stringify(response.data)}`);
}, function (err) {
console.warn(`Error: ${err}`);
}
);
}
// some endpoint with a delay of 3 seconds for a response
const url= "https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=3s";
makeRequest(url);
// make the same request again, abort the previous
setTimeout(()=> makeRequest(url), 1000);
But since the package is in early beta stage, you can use the plain cancellation token, provided by axios See Axios documentation

Categories

Resources