i have a pwa who works fine. I make it with workbox.
But i want to try something.
I want to push notification when : the request ( who was hold in IndexeDB -> thanks of BackgroundSyncPlugin ) have an error ( like Error 500 ). The request send, is not the probleme of BackgroundSyncPlugin, but a probleme with my HTTP request. And i want to warn user that the request wasn't work
here a part of my service worker :
const bgSyncPlugin = new BackgroundSyncPlugin('myQueueName', {
maxRetentionTime: 0.1 * 60, // mins,
onSync: async({ queue }) => {
let entry;
while ((entry = await queue.shiftRequest())) {
try {
await fetch(entry.request);
console.error("Replay successful for request", entry.request);
} catch (error) {
console.error("Replay failed for request", entry.request, error);
// Put the entry back in the queue and re-throw the error:
await queue.unshiftRequest(entry);
throw error;
}
}
console.log("Replay complete!");
showNotification();
}
});
registerRoute(
/http:\/\/localhost:8000/,
new NetworkFirst({
plugins: [bgSyncPlugin]
}),
'POST'
);
I just want to know the status code of my request
Any help :)
Edit : i want to get Body in header2
You should be able to change this line:
await fetch(entry.request);
to
const response = await fetch(entry.request);
// Check response.status, and do something, e.g. throw an
// Error when it's above 500:
if (response.status > 500) {
// Throwing an Error will trigger a retry.
throw new Error('Status: ' + response.status);
}
// This assumes that your response from the server is JSON,
// and has an error field which might be set if the request
// failed for some reason, regardless of the HTTP status code.
const responseJSON = await response.json();
if (responseJSON.error) {
throw new Error('Failed request: ' + responseJSON.error);
}
Related
I've created an app and try to register for webhooks, and then fetch the list of all webhooks.
I use this code for this (/server/middleware/auth.js):
const webhook = new Webhook({ session: session });
webhook.topic = "products/update";
webhook.address = "https://api.service.co/items/update";
webhook.format = "json";
console.log("registering products/update");
try {
await webhook.save({
update: true,
});
} catch (error) {
console.log(error);
}
const webhookSecond = new Webhook({ session: session });
webhookSecond.topic = "products/create";
webhookSecond.address = "https://api.service.co/items/webhooks";
webhookSecond.format = "json";
console.log("registering products/create");
try {
await webhookSecond.save({
update: true,
});
} catch (error) {
console.log(error);
}
console.log("getting all webhooks");
try {
let webhooks = await Webhook.all({
session: session,
});
console.log(webhooks);
} catch (error) {
console.log(error);
}
Everything works fine for a development store. However, when I try to launch this script on a third-party customer store, then I get this error:
HttpRequestError: Failed to make Shopify HTTP request: FetchError: invalid json response body at https://shopname.myshopify.com/admin/api/2022-04/webhooks.json reason: Unexpected end of JSON input
The app permissions/scopes are: read_checkouts, read_orders, read_inventory, read_products, read_customers
I got this error 3 times, even for Webhook.all.
Could you please tell me what can cause this error, and how could it be fixed?
This error was caused by the lack of access provided by the owner of the store to my collaborator developer account. Manage settings access was required.
I am trying to implement a token refresh into my vue.js application. This is working so far, as it refreshes the token in the store on a 401 response, but all I need to do is get the interceptor to retry the original request again afterwards.
main.js
axios.interceptors.response.use(
response => {
return response;
},
error => {
console.log("original request", error.config);
if (error.response.status === 401 && error.response.statusText === "Unauthorized") {
store.dispatch("authRefresh")
.then(res => {
//retry original request???
})
.catch(err => {
//take user to login page
this.router.push("/");
});
}
}
);
store.js
authRefresh(context) {
return new Promise((resolve, reject) => {
axios.get("auth/refresh", context.getters.getHeaders)
.then(response => {
//set new token in state and storage
context.commit("addNewToken", response.data.data);
resolve(response);
})
.catch(error => {
reject(error);
});
});
},
I can log the error.config in the console and see the original request, but does anyone have any idea what I do from here to retry the original request? and also stop it from looping over and over if it fails.
Or am I doing this completely wrong? Constructive criticism welcome.
You could do something like this:
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
const refreshToken = window.localStorage.getItem('refreshToken');
return axios.post('http://localhost:8000/auth/refresh', { refreshToken })
.then(({data}) => {
window.localStorage.setItem('token', data.token);
window.localStorage.setItem('refreshToken', data.refreshToken);
axios.defaults.headers.common['Authorization'] = 'Bearer ' + data.token;
originalRequest.headers['Authorization'] = 'Bearer ' + data.token;
return axios(originalRequest);
});
}
return Promise.reject(error);
});
Implementation proposed by #Patel Pratik is good but only handles one request at a time.
For multiple requests, you can simply use axios-auth-refresh package. As stated in documentation:
The plugin stalls additional requests that have come in while waiting
for a new authorization token and resolves them when a new token is
available.
https://www.npmjs.com/package/axios-auth-refresh
#Patel Pratik, thank you.
In react native, I've used async storage and had custom http header, server needed COLLECTORACCESSTOKEN, exactly in that format (don't say why =)
Yes, I know, that it shoud be secure storage.
instance.interceptors.response.use(response => response,
async error => { -----it has to be async
const originalRequest = error.config;
const status = error.response?.status;
if (status === 401 && !originalRequest.isRetry) {
originalRequest.isRetry = true;
try {
const token = await AsyncStorage.getItem('#refresh_token')
const res = await axios.get(`${BASE_URL}/tokens/refresh/${token}`)
storeAccess_token(res.data.access_token)
storeRefresh_token(res.data.refresh_token)
axios.defaults.headers.common['COLLECTORACCESSTOKEN'] =
res.data.access_token;
originalRequest.headers['COLLECTORACCESSTOKEN'] =
res.data.access_token;
return axios(originalRequest);
} catch (e) {
console.log('refreshToken request - error', e)
}
}
if (error.response.status === 503) return
return Promise.reject(error.response.data);
});
Building on #Patel Praik's answer to accommodate multiple requests running at the same time without adding a package:
Sorry I don't know Vue, I use React, but hopefully you can translate the logic over.
What I have done is created a state variable that tracks whether the process of refreshing the token is already in progress. If new requests are made from the client while the token is still refreshing, I keep them in a sleep loop until the new tokens have been received (or getting new tokens failed). Once received break the sleep loop for those requests and retry the original request with the updated tokens:
const refreshingTokens = useRef(false) // variable to track if new tokens have already been requested
const sleep = ms => new Promise(r => setTimeout(r, ms));
axios.interceptors.response.use(function (response) {
return response;
}, async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// if the app is not already requesting a new token, request new token
// i.e This is the path that the first request that receives status 401 takes
if (!refreshingTokens.current) {
refreshingTokens.current = true //update tracking state to say we are fething new tokens
const refreshToken = localStorage.getItem('refresh_token')
try {
const newTokens = await anAxiosInstanceWithoutInterceptor.post(`${process.env.REACT_APP_API_URL}/user/token-refresh/`, {"refresh": refreshToken});
localStorage.setItem('access_token', newTokens.data.access);
localStorage.setItem('refresh_token', newTokens.data.refresh);
axios.defaults.headers['Authorization'] = "JWT " + newTokens.data.access
originalRequest.headers['Authorization'] = "JWT " + newTokens.data.access
refreshingTokens.current = false //update tracking state to say new
return axios(originalRequest)
} catch (e) {
await deleteTokens()
setLoggedIn(false)
}
refreshingTokens.current = false //update tracking state to say new tokens request has finished
// if the app is already requesting a new token
// i.e This is the path the remaining requests which were made at the same time as the first take
} else {
// while we are still waiting for the token request to finish, sleep for half a second
while (refreshingTokens.current === true) {
console.log('sleeping')
await sleep(500);
}
originalRequest.headers['Authorization'] = "JWT " +
localStorage.getItem('access_token');
return axios(originalRequest)
}
}
return Promise.reject(error);
});
If you don't want to use a while loop, alternatively you could push any multiple request configs to a state variable array and add an event listener for when the new tokens process is finished, then retry all of the stored arrays.
I have a controller method
// ... inside a controller class
#get('/error', {})
async error() {
throw new Error("This is the error text");
}
The response I'm getting from this error front-end is:
{
"error": {
"statusCode": 500,
"message": "Internal Server Error"
}
}
What I would like the error to be is:
{
"error": {
"statusCode": 500,
"message": "This is the error text"
}
}
How do I return an error from a controller in Loopback 4?
Hello from the LoopBack team 👋
In your controller or repository, you should throw the Error exactly as shown in your question.
Now when LoopBack catches an error, it invokes reject action to handle it. The built-in implementation of reject logs a message via console.error and returns an HTTP response with 4xx/5xx error code and response body describing the error.
By default, LoopBack hides the actual error messages in HTTP responses. This is a security measure preventing the server from leaking potentially sensitive data (paths to files that could not be opened, IP addresses of backend service that could not be reached).
Under the hood, we use strong-error-handler to convert Error objects to HTTP responses. This module offers two modes:
Production mode (the default): 5xx errors don't include any additional information, 4xx errors include partial information.
Debug mode (debug: true): all error details are included on the response, including a full stack trace.
The debug mode can be enabled by adding the following line to your Application constructor:
this.bind(RestBindings.ERROR_WRITER_OPTIONS).to({debug: true});
Learn more in our docs: Sequence >> Handling errors
Alternatively, you can implement your own error handler and bind it as the sequence action reject. See Customizing sequence actions in our docs.
export class MyRejectProvider implements Provider<Reject> {
constructor(
#inject(RestBindings.SequenceActions.LOG_ERROR)
protected logError: LogError,
#inject(RestBindings.ERROR_WRITER_OPTIONS, {optional: true})
protected errorWriterOptions?: ErrorWriterOptions,
) {}
value(): Reject {
return (context, error) => this.action(context, error);
}
action({request, response}: HandlerContext, error: Error) {
const err = <HttpError>error;
const statusCode = err.statusCode || err.status || 500;
const body = // convert err to plain data object
res.statusCode = statusCode;
res.setHeader('Content-Type', 'application/json; charset=utf-8');
res.end(JSON.stringify(body), 'utf-8');
this.logError(error, statusCode, request);
}
}
If you just want to show error message, you just extend Error object and throw it like below. (Loopback documentation didn't mention this anyway)
Avoid using 5xx error and use 4xx error to show some important thing to user is best practice and so that Loopback4 was implemented like this.
class NotFound extends Error {
statusCode: number
constructor(message: string) {
super(message)
this.statusCode = 404
}
}
...
if (!await this.userRepository.exists(id)) {
throw new NotFound('user not found')
}
For my situation, I found a catch in my sequence.ts file. Inside the catch, it checked if the error had a status code of 4xx, and if not, it just returned a anonymous 500.
Here's the code I was looking for to do the logic:
// sequence.ts
...
} catch (err) {
console.log(err);
let code: string = (err.code || 500).toString();
if (code.length && code[0] === '4') {
response.status(Number(code) || 500);
return this.send(response, {
error: {
message: err.message,
name: err.name || 'UnknownError',
statusCode: code
}
});
}
return this.reject(context, err);
}
...
Here's how you tell it what to do:
// ... inside a controller class
#get('/error', {})
async error() {
throw {
code: 400,
message: "This is the error text",
name: "IntentionalError"
}
}
To throw custom validation error I use this method:
private static createError(msg: string, name?: string): HttpErrors.HttpError {
const error = new HttpErrors['422'](msg);
error.name = name ?? this.name;
return error;
}
Catch error examples here are for defaultSequence, overriding the handle method.
But nowdays app template uses MiddlewareSequence.
So here is the example, how tomodify the response in middleware sequence, you can use this example:
import { Middleware, MiddlewareContext } from '#loopback/rest';
export const ErrorMiddleware: Middleware = async (middlewareCtx: MiddlewareContext, next) => {
// const {response} = middlewareCtx;
try {
// Proceed with next middleware
return await next();
} catch (err) {
// Catch errors from downstream middleware
// How to catch specific error and how to send custom error response:
if (HttpErrors.isHttpError(err) || (err as HttpErrors.HttpError).statusCode) {
const code: string = (err.statusCode || 500).toString();
if (code.length && code[0] === '4') {
response.status(Number(code) || 500);
return response.send({
error: {
message: err.message,
name: err.name || 'UnknownError',
statusCode: code
}
});
}
}
throw err;
}
};
And register the middleware in application.ts
this.middleware(ErrorMiddleware);
I use the following function to Post a object of a given class.
public Post<T>(object: T, url: string, httpOptions: {}): Observable<T> {
return this.httpClient.post<T>(`${environment.apiEndpoint}` + url, object, httpOptions)
.pipe(
catchError(this.handleError)
);
}
This function is called in all the service that wants to post something. Like this.
public addEquipment(equipment: Equipment): Observable<Equipment> {
return this.shared.Post<Equipment>(equipment, this.url, this.header);
}
addEquipment is then executed within the component that uses that service. Like this.
this.equipmentService.addEquipment(result)
.subscribe((data: any) => { this.alertService.success(data) }, (error: any) => this.alertService.error(error));
The problem is when the API returns a error (that I can see includes a error message, in the network tab) it tells me that there is no body in the response. The API returns a HttpResult where the error message is added to the response field.
return new HttpResult { StatusCode = HttpStatusCode.Conflict, Response = "Error message"}
I use the following function to handle the errors.
private handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message);
}
else {
console.log(error);
console.error(
`Backend returned code ${error.status}, ` +
`body was: ${error.error}`);
}
console.log(error);
return throwError(
error.error)
};
It is Angular 6 and a ServiceStack API.
All suggestions would be appreciated.
FYI it's preferable to return structured error responses in ServiceStack which you can do with:
HttpError.Conflict("Error message");
Which will let you catch it when using ServiceStack's TypeScript ServiceClient with:
try {
var response = await client.post(request);
} catch (e) {
console.log(e.responseStatus.message);
}
But from this answer for handling errors with Angular HTTP Client it suggests the error body should be accessible with:
this.httpClient
.get("data-url")
.catch((err: HttpErrorResponse) => {
// simple logging, but you can do a lot more, see below
console.error('An error occurred:', err.error);
});
I'm missing something basic in the docs. When I get an API validation error, I'm returning a status code and message. It appears that React-Admin is translating the status code to a generic HTTP error code.
My error response.
{"error":
{"statusCode":422,
"name":"Error",
"message":"User with same first and last name already on team."}
}
When my API response with that response, I'm seeing "Unprocessable Entity" in the notification box. I'm using SimpleForm.
I know the status code is being recognized because I've changed the 422 and it shows the corresponding HTTP error description.
In the docs it says to throw and error in your data provider. I've moved that the Simple Rest data provider into my project and have tried throwing errors are various places, but nothing changes on the client.
https://marmelab.com/react-admin/DataProviders.html#error-format
If you have customized error from your API, I'd appreciated any hints you can give. Thx.
Here is the actual error processing:
When a fetch is triggered (usually coming from the data provider), if an error happen, it is caught and transformed into an HttpError and re-thrown (source)
In the process, the HTTP Error message becomes either the json.message or the response statusText. It's here that a 422 HTTP Error becomes Unprocessable Entity (source)
Then the error is caught again at a higher level to be transformed into a redux action. (source)
Finally, the error is transformed into a notification containing the error message.
So, in order to customize your error message, you can easily do that from your custom provider by catching the error in the first place, customizing the error message, and send it again:
const dataProvider = (type, resource, params) => new Promise((resolve, reject) => {
if (type === 'GET_LIST' && resource === 'posts') {
return fetch(...args)
.then(res => res.json())
.then(json => {
if (json.error) {
// The notification will show what's in { error: "message" }
reject(new Error(json.error.message));
return;
}
resolve(json);
});
}
// ...
});
in Backend, I structure the response as
res.json({status:400,message:"Email Address is invalid!"})
In Client side, modify the convertHTTPResponse in dataprovider as:
const convertHTTPResponse = (response, type, resource, params) => {
const { headers, json } = response;
switch (type) {
case GET_LIST:
case GET_MANY_REFERENCE:
if(json.status === 200){
if (!headers.has('content-range')) {
throw new Error('The Content-Range header is missing in the HTTP Response.);
}
return {
data: json.docs,
total: parseInt(
headers
.get('content-range')
.split('/')
.pop(),
10
),
};
}else{
throw new Error(json.message)
}
default:
if(json.status === 200){
return { data: json.docs };
}else{
throw new Error(json.message)
}
}