Hello guys i'm struggling to find a solution on how to send a notification to a specific user
with web-push and and serviceWorker
here's the code
(working but everyone can see the notification)
server :
#Post('/notifications/subscribe')
notif(#Body() body: any) {
const subscription = body;
console.log(subscription);
globals.payload = subscription;
const payload = JSON.stringify({
email: subscription.email,
title: 'Hello!',
body: 'It workssss.',
});
webpush
.sendNotification(subscription, payload)
.then((result) => console.log(result))
.catch((e) => console.log(e.stack));
}
serviceWorker
self.addEventListener("push", (event) => {
const data = event.data.json();
const options = {
body: data.body,
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
Related
I wan't to capture a new paypal subscription from frontend in my backend and give response with the needed data for mongodb.
If I add a body with capture_type: 'OUTSTANDING_BALANCE' (I found that in the manual) I'm getting this error.
So I'm not sure either it's just a wrong body or i totally mess up something else in the backend but so far I can't capture the subscription even so I get a subscription Id from
createSubscription Controller
PayPalScriptProvider
<PayPalScriptProvider options={initialOptions}>
<PayPalSubscriptionButton/>
</PayPalScriptProvider>
PayPal Button
{isPending ? <LoadingMedium /> : null}
<PayPalButtons
createSubscription={(data, actions) => {
return axios
.post(
'/api/subscription',
)
.then((response) => {
return response.data.id;
});
}}
onApprove={(data, actions) => {
axios
.post(`/api/subscription/${data.subscriptionID}/capture`)
.then(() => {
axios
.patch(
`/api/activesubscription`,
{
id: activeSub[0]?._id,
subscriptionID: data.subscriptionID,
}
)
});
});
}}
/>
Route for createSubscription
router.route('/subscription').post(async (req, res) => {
const searchPlan = await SubscriptionAmount.find();
console.log(searchPlan[0]?.subscriptionAmount);
const subscription = await paypalFee.createSubscription(
searchPlan[0]?.subscriptionAmount
);
res.json(subscription);
});
Router for onApprove
router.post('/subscription/:subscriptionID/capture', async (req, res) => {
const { subscriptionID } = req.params;
console.log('subscriptionID', subscriptionID);
const captureData = await paypalFee.captureSubscription(subscriptionID);
console.log('captureData', captureData);
res.json(captureData);
});
createSubscription Controller
async function createSubscription(planId) {
const accessToken = await generateAccessToken();
const url = `${base}/v1/billing/subscriptions`;
const response = await fetch(url, {
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
intent: 'subscription',
plan_id: planId,
}),
});
const data = await response.json();
console.log('data', data);
return data;
}
captureSubscription Controller
async function captureSubscription(subscriptionId) {
const accessToken = await generateAccessToken();
const url = `${base}/v1/billing/subscriptions/${subscriptionId}/capture`;
const response = await fetch(url, {
method: 'post',
body: JSON.stringify({
// capture_type: 'OUTSTANDING_BALANCE',
}),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
});
const data = await response.json();
console.log('data', data);
return data;
}
I'm getting this logs for my data in captureSubscription if I do not pass a body in my captureSubscription Controller:
captureData {
name: 'INVALID_REQUEST',
message: 'Request is not well-formed, syntactically incorrect, or violates schema.',
details: [
{
location: 'body',
issue: 'MISSING_REQUEST_BODY',
description: 'Request body is missing.'
}
]
}
With body I'm getting this error
captureData {
name: 'UNPROCESSABLE_ENTITY',
message: 'The requested action could not be performed, semantically incorrect, or failed business validation.',
details: [
{
issue: 'ZERO_OUTSTANDING_BALANCE',
description: 'Current outstanding balance should be greater than zero.'
}
],
}
ZERO_OUTSTANDING_BALANCE
There is no outstanding balance to capture. An outstanding balance occurs when payments are missed due to failures.
For ordinary (non-outstanding) subscription payments, no captures can be triggered. Subscriptions will capture automatically on the schedule you specify in the plan, that is the point of subscriptions.
I am building a comments section onto a Node/Express app for family reunions. I first wrote it all on the server side, but then ran into the issue where I was unable to update the DOM after posting the comment without refreshing the page.
My research yielded that I could use AJAX or the fetch API to do this, client-side.
I'm using some client-side JavaScript to post comments. I have a route for the POST request:
router.post('/:reunionId', isAuth, reunionController.postComment);
The controller code is:
exports.postComment = (req, res, next) => {
const commentText = req.body.newComment;
const reunionId = req.body.reunionId;
const foundReunion = Reunion.findById(reunionId)
.populate({
path: 'comments',
options: { sort: { createdAt: -1 } },
})
.then((reunion) => {
console.log(reunion);
const comment = new Comment({
_id: new mongoose.Types.ObjectId(),
text: commentText,
reunionId: new mongoose.Types.ObjectId(reunionId),
userId: req.user._id,
});
foundReunion.comments.push(comment);
comment.save();
foundReunion.save();
console.log('Operation completed successfully');
return foundReunion;
})
.catch((error) => {
const newError = new Error(error);
newError.httpStatusCode = 500;
return next(newError);
});
};
And the client-side code:
const commentForm = document.getElementById('comment-form');
const commentInput = document.getElementById('newComment');
const commentsContainer = document.getElementById('allComments');
let commentText = document.getElementById('newComment').value;
const reunionId = document.getElementById('reunionId').value;
const csrfToken = document.getElementById('csrf').value;
commentForm.addEventListener('submit', handleCommentSubmit, false);
commentInput.addEventListener('change', (event) => {
commentText = event.target.value;
});
async function handleCommentSubmit(event) {
event.preventDefault();
console.log('Someone clicked the comment submit button...');
console.log(csrfToken); // This works.
console.log(reunionId); // This works.
console.log(commentText); // This works.
const url = `http://localhost:3006/reunions/${reunionId}`;
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'X-CSRF-Token': csrfToken,
},
body: { // This is not working.
reunionId,
commentText,
},
})
.then((response) => {
const d = response.comment.createdAt.getDate();
const m = monthNames[response.comment.createdAt.getMonth()];
const y = response.comment.createdAt.getFullYear();
const commentDiv = document.createElement('div');
commentDiv.classList.add('comments-container');
const commentP = doucment.createElement('p');
commentP.classList.add('comment-header-text');
const email = response.comment.userId.email;
const hr = document.createElement('hr');
commentP.textContent = `On ${m}+ ' ' +${d}+ ', ' +${y}, ${email} wrote:`;
commentDiv.appendChild(commentP);
commentDiv.appendChild(commentText);
commentDiv.appendChild(hr);
commentsContainer.appendChild(commentDiv);
})
.catch((error) => console.log(error));
The client makes the POST request, properly passes the csrf token, but the server cannot read the reunionId or commentText from the body of the request. I get Reunion.findOne({ null }) in the server logs.
I am simply not sure what Content-Type to declare, whether I need to at all, or how to pass the two pieces of data I need in the body of the call to fetch.
Thanks very much in advance.
The body of a post must always be a string. What you are missing is you need to JSON.strigify your object and them make add the content-type header to specify that the body is application/json:
fetch(url, {
method: 'POST',
credentials: 'include',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
reunionId,
commentText,
}),
})
Its possible for me to send a notification to the reciever: idTo which is a string in the database. However, is it possible to use the array-field instead?: participants and send a notification to everyone in the array?
I store my users with their respective tokens at this path in firebase: Users->{userId}:
I've tried changing:
const idTo = doc.idTo
admin.firestore().collection('users').where('uid', '==', idTo).get().then(querySnapshot => {
to:
const participants = doc.participants
admin.firestore().collection('users').where('uid', 'arrayContains', participants).get().then(querySnapshot => {
Full code:
exports.sendNotification = functions.firestore
.document('messages/{roomId1}/room/{message}/message/{messageId}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom
const idTo = doc.idTo
const contentMessage = doc.message
// Get push token user to (receive)
admin.firestore().collection('users').where('uid', '==', idTo).get().then(querySnapshot => {
querySnapshot.forEach(userTo => {
console.log(`Found user to: ${userTo.data().uid}`)
if (userTo.data().pushToken) {
// Get info user from (sent)
admin.firestore().collection('users').where('uid', '==', idFrom).get().then(querySnapshot2 => {
querySnapshot2.forEach(userFrom => {
console.log(`Found user from: ${userFrom.data().uid}`)
const payload = {
notification: {
title: `${userFrom.data().name}`,
body: contentMessage,
badge: '1',
sound: 'default',
clickAction: 'FLUTTER_NOTIFICATION_CLICK',
// badge: '1'
},
data: {
title: '',
content: '',
image: '',
uploader: '',
type: 'chat',
},
}
// Let push to the target device
admin.messaging().sendToDevice(userTo.data().pushToken, payload).then(response => {
return console.log('Successfully sent message:', response)
}).catch(error => {
console.log('Error sending message:', error)
})
})
return console.log('failed')
}).catch(error => {
console.log('Error sending message:', error)
})
} else {
console.log('Can not find pushToken target user')
}
})
return console.log('error: invalid path')
}).catch(error => {
console.log('Error sending message:', error)
})
return null
})
I'm thinking maybe I need to loop over the array for each of the users and somehow execute the push notification. Any ideas are welcome
var messageToSend=req.body.messageToSend;
var message = { //this may vary according to the message type (single recipient, multicast, topic, et cetera)
registration_ids:regTokens , // Id List if more then one recipent
//to : regTokens, //use if One recipient
data: { //This is only optional, you can send any data
score: '',
time: ''
},
notification:{
body : messageToSend
}
};
console.log(message);
fcm.send(message, function(err, response){
if (err) {
console.log("Something has gone wrong!",err);
} else {
console.log("Successfully sent with response: ", response);
}
});
The notification options that I am passing to my notifications are not passing to the notification and I am getting a default notification. (Title is the website, body is "The site has been updated in the background").
Service worker is an adapted create-react-app service worker.
Also, the console.log statements in the push event handler are not passing to the browser. Why is this?
The push event listener is directly after the load event listener in the CRA Service Worker
Web-Push API Call to create a web-push notification:
router.post('/:userid', auth, async (req, res) => {
try {
const user = await User.findById(req.params.userid);
user.pushSubscriptions.forEach((sub) => {
if (sub === null) {
return;
}
webpush.setVapidDetails(
'mailto:contact#email.com',
config.get('vapidPublic'),
config.get('vapidSecret')
);
const options = {
endpoint: sub.endpoint,
expirationTime: sub.expirationTime,
keys: {
p256dh: sub.keys.p256dh,
auth: sub.keys.auth,
},
};
console.log(options.endpoint);
webpush
.sendNotification(
options,
JSON.stringify({
title: 'NotifTitle',
body: 'Body',
})
)
.catch((error) => console.log(error));
});
return res.status(200).json({ msg: 'Notification Sent' });
} catch (error) {
console.log(error);
return res.status(500);
}
});
Push listener in sw.js:
window.addEventListener('push', (event) => {
console.log('Notification Recieved', event);
//Fallback data
let data = {
title: 'TestABC',
body: '123456',
};
if (event.data) {
data = JSON.parse(event.data.text());
}
//Notification options
var options = {
body: data.body,
icon: '../public/logo192.png',
image: '../public/logo192.png',
};
event.waitUntil(
console.log(options),
navigator.serviceWorker.registration.showNotification(
data.title,
options
)
);
});
Thanks
try to convert data like this
data = event.data.json();
you can read more here
I would like to update my dashboard if there are any changes from backend as notification in Facebook.
I have two pages:
A page for the user sending a request message
A page for the user profile where the user can see all the request messages
If there is a new request message, the user needs to refresh the user profile in order to see the new message. I want the new message to be displayed without refreshing the page. Here is my code:
In a message page
state = {
team: {
message: 'Hi! I would like to join in your team! Please accept my request',
invitation_message: 'Hi! I would like to invite you to join in my team.',
email: '',
},
}
// Invite user to a team
handleInvite = event => {
event.preventDefault();
const userObject = JSON.parse(localStorage.getItem('user'));
const jwt = userObject.jwt;
const config = {
headers: { 'Authorization': `bearer ${jwt}` },
};
api
.post('/teammembers', {
team: this.state.teaminfo,
profile: responseData.data[0],
status: "invited",
message: this.state.team.invitation_message,
}, config)
.then(response => {
this.setState({
success_message: true,
})
console.log('Success', response);
})
.catch(err => {
console.log('An error occurred:', err);
});
}
In a user profile page
export class UserProfile extends React.Component {
import socketIOClient from "socket.io-client";
state = {
invited_teams:[],
endpoint: "myurl"
}
componentDidMount() {
const { endpoint } = this.state;
//Very simply connect to the socket
const socket = socketIOClient(endpoint);
socket.on('request', (data) => {
this.setState({ state: data.requests });
});
if (localStorage.getItem('userData')) {
const userObject = JSON.parse(localStorage.getItem('user'));
api
.get(`/profiles/?user=${userObject.user.id}`)
.then(responseData => {
this.setState({
invited_teams: responseData.data
})
}
}
}
Could anyone help me to solve this problem?
Use socket.IO library. You can set a listener on new request and then update the state.
socket.on('request' , (data) => {
this.setState({state: data.requests});
});