I am trying to implement push notifications with react and nodejs using service workers.
I am having problem while i am showing notification to the user.
Here is my service worker code:
self.addEventListener('push', async (event) => {
const {
type,
title,
body,
data: { redirectUrl },
} = event.data.json()
if (type === 'NEW_MESSAGE') {
try {
// Get all opened windows that service worker controls.
event.waitUntil(
self.clients.matchAll().then((clients) => {
// Get windows matching the url of the message's coming address.
const filteredClients = clients.filter((client) => client.url.includes(redirectUrl))
// If user's not on the same window as the message's coming address or if it window exists but it's, hidden send notification.
if (
filteredClients.length === 0 ||
(filteredClients.length > 0 &&
filteredClients.every((client) => client.visibilityState === 'hidden'))
) {
self.registration.showNotification({
title,
options: { body },
})
}
}),
)
} catch (error) {
console.error('Error while fetching clients:', error.message)
}
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
console.log(event)
if (event.action === 'NEW_MESSAGE') {
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (clients.openWindow) {
clients
.openWindow(event.notification.data.redirectUrl)
.then((client) => (client ? client.focus() : null))
}
}),
)
}
})
When new notification comes from backend with a type of 'NEW_MESSAGE', i get the right values out of e.data and try to use them on showNotification function but it seems like something is not working out properly because notification looks like this even though event.data equals to this => type = 'NEW_MESSAGE', title: 'New Message', body: , data: { redirectUrl: }
Here is how notification looks:
Thanks for your help in advance.
The problem was i assigned parameters in the wrong way.
It should've been like this:
self.registration.showNotification(title, { body })
I have an api request and for error statement. I want to return message from payload.
But message object can change depending on error. For example, payload object can be
{ message: 'Not Authorized', type: 'service.not_authorized' }
or
{
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired.
}
]
}
I can't create interfaces for all possible payloads.
In JS, basically I can write this :
if(payload && payload.message){
return payload.message
}
if(payload && payload.errors){
const message = payload.errors.length>0 ? payload.errors[0].detail : 'Error'
return message;
}
Now I can try to do this with Typescript
import axios from "axios";
interface IApiError{
category:string,
code:string,
detail:string
}
const CustomError=<T>(e:T):string=>{
let message="";
if(axios.isAxiosError(e)){
const payload = e.response?.data; // payload:unknown
if(payload){
if(typeof payload === 'object'){
if(payload.hasOwnProperty('message')){
message=payload.message;
// Error : Property 'message' does not exist on type 'object'.
}
else if(payload.hasOwnProperty('errors')){
const errorsArray:IApiError[]=payload.errors;
message=errorsArray[0].detail;
// Error : Property 'errors' does not exist on type 'object'.
}
}
}
}
return message;
}
How can I solve this?
Edit
I found this topic. But according this we should create interfaces and typeguard for every possible payload option.
I guess, I found a solution. Based on this answer I created a function.
// Basically it takes 3 arguments
// o -> Object that we don't know what is look like
// prop -> The name of prop that we are looking for
// type -> A variable with the same type with our prop
// Because I didn't find to send type to method in typescript
// (for example string,number,boolean)
const getProp = (o: unknown, prop: string, type:any): any => {
// Checks whether the object contains the requested prop
const p = (o as any)[prop]
// If this field instance of requested type
if (typeof p === typeof type) {
return p;
}
return undefined;
}
Let's test this for different scenarios.
let obj:unknown={ message: 'Not Authorized', type: 'service.not_authorized' }
let obj2:unknown={
"errors": [
{
"category": "AUTHENTICATION_ERROR",
"code": "UNAUTHORIZED",
"detail": "Authorization code is expired."
}
]
}
// In first object I looking for message field and it should be string
let res=getProp(obj,"message","");
res ? console.log(res) : console.log("Not Found");
// console result -> Not Authorized
// In second object I'm intentionally sending the wrong type
// There is a field with name 'errors' but it's type is array
res=getProp(obj2,"errors","");
res && res.length>0 ? console.log(res) : console.log("Not Found");
// console result -> Not Found
// I send true types
res=getProp(obj2,"errors",[]);
res && res.length>0 ? console.log(res[0].detail) : console.log("Not Found");
// console result -> Authorization code is expired.
Problem
Using 18next.t function to translate key, is getting me the generic sequelize unique constraint error message instead of defined custom message
Environment
sequelize#5.22.4
i18next#21.3.3
Model definition candidate.js
...
module.exports = (sequelize, DataTypes) => {
const Candidate = sequelize.define('Candidate', {
status: {
type: DataTypes.ENUM,
values: [
...
],
},
docTin : {
...
unique: {
args: 'candidates_unique_doctin_company_unity',
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
},
...
Result:
docTin must be unique
Expected:
{Custom error message located on lang.json}
The solution that i founded was prototyping the main create function on model definition, to allow use functions on unique msg properties
...
const orgCreate = Candidate.create;
Candidate.create = function(){
return orgCreate
.apply(this, arguments)
.catch(err => {
const uniqueErrorName = 'SequelizeUniqueConstraintError'
if (err.name === uniqueErrorName) {
err.errors = err.errors.map(e => ({
...e,
message: typeof e.message === 'function' ? e.message() : e.message
}))
}
throw err;
});
...
replacing
get msg() { return i18next.t('invalid-candidate-unique-doc-tin') }
for
msg: () => i18next.t('invalid-candidate-unique-doc-tin')
Im tring to catch more details when error accuring.
when the error is accuring im using node api server to catch the error and save in log file.
i simulated a Network Error and tring to get many details as possible.
when i console.log the error in frontend im getting this:
withFormHandler.js:28 Uncaught Error: Error: Network Error
at Object.componentDidUpdate (withFormHandler.js:28)
...
but i cant send this information using my api.
all im getting in the server side is an empty object.
so how can i catch and send many details as possible about the error and write it to the log file?
this is my ErrorBoundry component:
class ErrorBoundary extends React.Component {
state = {
hasError: false,
error: { message: "", stack: "" },
info: { componentStack: "" },
};
static getDerivedStateFromError = (error) => {
return { hasError: true };
};
componentDidCatch = (error, info) => {
this.setState({ error, info });
axios
.get(`http://localhost:5001/api/logs/error`, {
params: {
error: error.message,
details: info,
// details:error ---> this is also not give me information i need
},
})
.catch(() => {});
};
render() {
const { hasError, error, info } = this.state;
const { children } = this.props;
return hasError ? <ErrorComponent message={error.message} /> : children;
}
}
this is the server side handle:
router.get("/error", (req, res) => {
const errorMessage = req.query.error;
const details = req.query.details; -----> return an empty object :(
const logFile = "./logs/debug.log";
if (errorMessage) {
let error = errorMessage + "\r\n" + details;
fs.appendFile(logFile, error, function (err) {
// callback or something
});
}
});
How can I return multiple error messages like this ?
"errors": [
{
"message": "first error",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"somePath"
]
},
{
"message": "second error",
"locations": [
{
"line": 8,
"column": 9
}
],
"path": [
"somePath"
]
},
]
On my server, if I do throw('an error'), it returns.
"errors": [
{
"message": "an error",
"locations": [
{
}
],
"path": ["somePath"]
}
]
I would like to return an array of all the errors in the query.
How can I add multiple errors to the errors array ?
Throw an error object with errors:[] in it. The errors array should have all the errors you wanted to throw together. Use the formatError function to format the error output. In the below example, I am using Apollo UserInputError. You can use GraphQLError as well. It doesn't matter.
const error = new UserInputError()
error.errors = errorslist.map((i) => {
const _error = new UserInputError()
_error.path = i.path
_error.message = i.type
return _error
})
throw error
new ApolloServer({
typeDefs,
resolvers,
formatError: ({ message, path }) => ({
message,
path,
}),
})
//sample output response
{
"data": {
"createUser": null
},
"errors": [
{
"message": "format",
"path": "username"
},
{
"message": "min",
"path": "phone"
}
]
}
Using ApolloServer I've found multiple errors will be returned when querying an array of items and an optional field's resolver errors.
// Schema
gql`
type Foo {
id: ID!
bar: String # Optional
}
type Query {
foos: [Foo!]!
}
`;
// Resolvers
const resolvers = {
Query: {
foos: () => [{ id: 1 }, { id: 2 }]
}
Foo: {
bar: (foo) => {
throw new Error(`Failed to get Foo.bar: ${foo.id}`);
}
}
}
// Query
gql`
query Foos {
foos {
id
bar
}
}
`;
// Response
{
"data": {
"foos": [{ id: 1, bar: null }, { id: 2, bar: null }]
},
"errors": [{
"message": "Failed to get Foo.bar: 1"
}, {
"message": "Failed to get Foo.bar: 2"
}]
}
If Foo.bar is not optional, it will return just the first error.
If you want to return many errors, at once, I would recommend MultiError from VError which allows you to represent many errors in one error instance.
You would need to catch the errors without the throw statement because you don't want to interrupt your process. Instead, you can create an array called errors and .push() the errors into it. When you see fit, near the end of your process, you can check to see if there are errors inside the errors array. If there are, you can display them or handle them as you wish
// example
var errors = [];
doSomething(function(err,res){
if(err){
errors.push(err);
}
console.log("we did a thing");
doSomethingElse(function(err,res2){
if(err){
errors.push(err);
};
console.log("we did another thing");
// check and throw errors
if(errors.length > 0){
throw errors;
}
});
});
You can use the GraphQL Error Function, I have a example with TypeScript:
function throwError(message: string, path: any) {
throw new GraphQLError(
message,
[],
{body: '', name: ''},
undefined,
[path]
)
}
And then I just call the function as many times as needed.
The JavaScript constructor looks like:
constructor(
message: string,
nodes?: $ReadOnlyArray<ASTNode> | ASTNode | void,
source?: ?Source,
positions?: ?$ReadOnlyArray<number>,
path?: ?$ReadOnlyArray<string | number>,
originalError?: ?Error,
extensions?: ?{ [key: string]: mixed },
): void;
Check the graphql-js gitHub:
https://github.com/graphql/graphql-js/blob/master/src/error/GraphQLError.js#L22
Looks like the question it is not about to show many exceptions but about to show all the stack trace of the error. When one error is thrown up, the execution will not receive or throw up other error. In some languages, you can nativally set the parent exception to the current exception, but it is not the case of javascript, so far I can tell and looking to the docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error and https://nodejs.org/api/errors.html#errors_error_propagation_and_interception. You will need to create your own error class, what it is not that hard.
If the problem it is show trace
The stack trace in Javascript it is a string! What it is good if you just want to put it into some log but bad if you want to make a more meaningful reading structure, like a json.
If what you want to do it is really show the stack trace, probably you are going to need to convert the stack trace of the Error object into an array, using something like this:
https://github.com/stacktracejs/error-stack-parser and then put this array inside of your error object.
After that, you can just save that object into your database. You still will be watching just one error, but you are going to have all the "location", "line", "path" of it trace, that sounds to me what you are looking for.
If the problem it is to show the parent errors message and trace
If you want to keep the parent Error of some trace, you will probably need to create your own error class.
/**
* Class MyError extends Error but add the parentError attribute
*/
function MyError(message, parentError ) {
this.message = message;
this.stack = Error().stack;
this.parentError = parentError;
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.name = "MyError";
function a() {
b();
}
function b() {
try {
c();
} catch ( e ) {
throw new MyError( "error on b", e );
}
}
function c() {
d();
}
function d() {
throw new MyError("error on d");
}
function showError( e ) {
var message = e.message + " " + e.stack;
if ( e.parentError ) {
return message + "\n" + showError( e.parentError );
}
return message;
}
try{
a();
} catch ( e ) {
console.log(showError( e ));
}
If the problem it is show many errors messages and trace
If you want to keep many errors into a big package, for validation feedback, for example, you may extend the error class to create a package of errors. I created one simple example of each one of this classes.
/**
* Class MyErrorPackage extends Error
* but works like a error package
*/
function MyErrorPackage(message, parentError ) {
this.packageErrors = [];
this.message = "This package has errors. \n";
this.isValid = true;
this.stack = Error().stack;
this.parentError = parentError;
this.addError = function addError( error ) {
this.packageErrors.push( error );
this.isValid = false;
this.message += "PackageError(" + this.packageErrors.length + "): " + error.stack + error.stack + "\n";
};
this.validate = function validate() {
if( ! this.isValid ) {
throw this;
}
};
}
MyErrorPackage.prototype = Object.create(Error.prototype);
MyErrorPackage.prototype.name = "MyErrorPackage";
function showError( e ) {
var message = e.message + " " + e.stack;
if ( e.parentError ) {
return message + "\n" + showError( e.parentError );
}
return message;
}
function showPackageError( e ) {
var message = e.message + " " + e.stack;
if ( e.parentError ) {
return message + "\n" + showError( e.parentError );
}
return message;
}
try{
var p = new MyErrorPackage();
try {
throw new Error("error 1");
} catch( e1 ) {
p.addError(e1);
}
try {
throw new Error("error 2");
} catch( e2 ) {
p.addError(e2);
}
try {
throw new Error("error 3");
} catch( e3 ) {
p.addError(e3);
}
p.validate();
} catch ( e4 ) {
console.log(showError( e4 ));
}