Unable to broadcast multiple messages on socket io - javascript

I'm writing a program where I want to broadcast messages using socket IO. This is for a chat interface.
Here is my code
socket.on('send-chatMessage', message => {
var gotNewMsg = false;
sendChatMessage(message, chatMessagesJSON, function (resp, err) {
console.log('resp from send chat --->');
console.log(JSON.stringify(resp));
console.log('err');
console.log(err);
gotNewMsg = true;
socket.broadcast.emit('chat-message', {
message: message,
name: users[socket.id]
});
if (gotNewMsg) {
buildChatResponse(chatMessagesJSON, function (resp, err) {
console.log('resp from send chat');
console.log(JSON.stringify(resp));
console.log('err');
console.log(err);
socket.broadcast.emit('chat-message', {
message: JSON.stringify(resp.messages[0].type),
name: 'Agent'
});
});
}
})
});
Here My first
socket.broadcast.emit('chat-message', {
message: message,
name: users[socket.id]
});
is working fine but my second one
if (gotNewMsg) {
buildChatResponse(chatMessagesJSON, function (resp, err) {
console.log('resp from send chat');
console.log(JSON.stringify(resp));
console.log('err');
console.log(err);
socket.broadcast.emit('chat-message', {
message: JSON.stringify(resp.messages[0].type),
name: 'Agent'
});
});
}
is not working as expected. I'm able to enter the if and also print the result. The only thing failing is broadcasting.
Here is my broadcast handlers.
socket.on('chat-message', data => {
appendMessage(`${data.name}: ${data.message}`);
})
function appendMessage(message) {
const messageElement = document.createElement('div');
messageElement.innerText = message;
messageContainer.append(messageElement);
};
please let me know where am I going wrong.

Related

Handling errors in Express.js in service / controller layers

I am writing an application in Express.js with a separate controller layer and a service layer. Here is my current code:
user.service.js
exports.registerUser = async function (email, password) {
const hash = await bcrypt.hash(password, 10);
const countUser = await User.countDocuments({email: email});
if(countUser > 0) {
throw ({ status: 409, code: 'USER_ALREADY_EXISTS', message: 'This e-mail address is already taken.' });
}
const user = new User({
email: email,
password: hash
});
return await user.save();
};
exports.loginUser = async function (email, password) {
const user = await User.findOne({ email: email });
const countUser = await User.countDocuments({email: email});
if(countUser === 0) {
throw ({ status: 404, code: 'USER_NOT_EXISTS', message: 'E-mail address does not exist.' });
}
const validPassword = await bcrypt.compare(password, user.password);
if (validPassword) {
const token = jwt.sign({ email: user.email, userId: user._id }, process.env.JWT_KEY, { expiresIn: "10s" });
return {
token: token,
expiresIn: 3600,
userId: user._id
}
} else {
throw ({ status: 401, code: 'LOGIN_INVALID', message: 'Invalid authentication credentials.' });
}
};
user.controller.js
exports.userRegister = async function (req, res, next) {
try {
const user = await UserService.registerUser(req.body.email, req.body.password);
res.status(201).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
exports.userLogin = async function (req, res, next) {
try {
const user = await UserService.loginUser(req.body.email, req.body.password);
res.status(200).json({ data: user });
} catch (e) {
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
}
}
The code works, but requires some corrections. I have a problem with error handling. I want to handle only some errors. If another error has occurred, the 500 Internal Server Error will be returned.
1) Can I use "throw" object from the service layer? Is this a good practice?
2) How to avoid duplication of this code in each controller:
if(!e.status) {
res.status(500).json( { error: { code: 'UNKNOWN_ERROR', message: 'An unknown error occurred.' } });
} else {
res.status(e.status).json( { error: { code: e.code, message: e.message } });
}
3) Does the code require other corrections? I'm just learning Node.js and I want to write the rest of the application well.
Yes, you can throw errors from service layer, it is good practice to catch errors with try/catch block in controller
I handle this with a custom error middleware, just use a next function in a catch block.
catch (e) {
next(e)
}
Example of error middleware (for more info check docs, fill free to move a middleware to file)
app.use(function (err, req, res, next) {
// err is error from next(e) function
// you can do all error processing here, logging, parsing error messages, etc...
res.status(500).send('Something broke!')
})
From my point of view it looks good. If you looking for some best practice and tools, try eslint (with AirBnb config for example) for linting, dotenv for a environment variables management, also check Node.js Best Practice
i want to give you an example:
this code in your controller
findCar(idCar)
} catch (error) {
switch (error.message) {
case ErrorConstants.ELEMENT_NOT_FOUND('LISTING'): {
return {
response: {
message: ErrorMessages.ELEMENT_NOT_FOUND_MESSAGE('LISTING'),
},
statusCode,
}
}
default: {
return {
response: {
message: ErrorMessages.UNKNOWN_ERROR_MESSAGE,
},
statusCode,
}
}
}
}
and this code in your service
findCar: async listingId => {
try {
if (some condition) {
throw new Error(ErrorConstants.ELEMENT_NOT_FOUND('LISTING'))
}
return { ... }
} catch (error) {
console.error(error.message)
throw new Error(ErrorConstants.UNKNOWN_ERROR)
}
},
controller is going to catch the service's errors

Retry to send mail if not sent

I am using nodemailer to send emails through my node app. Sometimes Email does not work and throws an error until I try twice or thrice. I want my program to try again and again until the mail is successfully sent.
Here's my code:
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
res.status(500).json({
message: "Mail not sent",
error
});
} else {
res.status(200).json({message: "Mail Sent", response: info.response});
}
});
How can I use the same function inside my if block?
Wrap sendMail in a function that returns a Promise
const promiseWrapper = mailOptions => new Promise((resolve, reject) => {
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
reject(error);
return;
}
resolve(info);
});
then in your route make the handler an async function and loop how many time that you want, then check if info exists if it does send 200 if not send 500
app.post('/sendmail', async (req, res) => {
let info;
let error;
for (let i = 0; i < 3; i++) {
try {
info = await promiseWrapper(mailOptions);
break;
} catch (e) {
error = e;
}
}
info
? res.status(200).json({ message: "Mail Sent", response: info.response })
: res.status(500).json({ message: "Mail not send", error }));
});
You can first separate the retry logic in a different file, so you can use it in various places.
Newer versions of nodemailer support promises for transporter.sendMail
// retry.js
// retries a function, called asynchronously, n amount of times
export async function retry(fn, n) {
for (let i = 0; i < n; i++) {
try {
return await fn()
} catch (err) {
console.log(err)
}
}
}
And pass the transporter function to the retry logic, with the amount of times you want to retry (in this example: 3)
import {retry} from '../utils/retry'
// ...
app.post('/sendmail', async (req, res) => {
try {
retry(
() =>
transporter.sendMail({
// your configuration
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
}),
3 // max retries
)
} catch (err) {
console.log(err)
// failed max retry times
res.sendStatus(500)
}
res.sendStatus(200)
})
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
var i;
for(i = 0; i <= 1; i++) {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
res.status(500).json({
message: "Mail not sent",
error
});
i = 0;
} else {
i = 2;
res.status(200).json({message: "Mail Sent", response: info.response});
}
});
}
Try the code above will run the function again and again if the error occur and it will exit the loop if no error occur.
Wrap it into a function and call in this way:
const mailOptions = {
from: from,
to: client.email,
subject: 'Your Photos are ready',
html: mailTemplate
};
function sendMail(mailOptions) {
transporter.sendMail(mailOptions, function(error, info){
if (error) {
return sendMail(mailOptions)
} else {
return res.status(200).json({message: "Mail Sent", response: info.response});
}
});
}
return sendMail(mailOptions);

node mqtt server not propagating publishings

in this sample project i am trying to subscribe to a channel and publish into it.
However i never get back the message sent to the broker.
This is the server:
const mqttServer = require("mqtt-server");
const servers = mqttServer(
{
mqtt: "tcp://localhost:1883",
mqttws: "ws://localhost:1884",
},
{
emitEvents: true, // default
},
client => {
console.log("client connected!");
// console.log(client)
client.connack({
returnCode: 0,
});
client.on("data", data => {
console.log("got data: ");
console.log(data);
client.puback({messageId:data.packetId, ...data})
});
client.on("error", err => {
console.log(err);
});
},
);
servers.listen(_ => {
console.log("listening!");
});
servers.close(_ => {
console.log("close!");
});
And this is the client:
const mqtt = require("mqtt");
const client = mqtt.connect("mqtt://localhost:1883");
client.on("connect", ack => {
console.log("connected!");
// console.log(ack);
client.subscribe("/test", err => {
console.log(err);
});
client.on("message", (topic, message) => {
console.log(topic);
// message is Buffer
console.log(message.toString());
});
setInterval(_ => {
client.publish("/test", "Hello mqtt");
}, 3000);
});
client.on("error", err => {
console.log(err);
});
Any thoughts on what am i missing?
Turns out that using aedes broker the scenario on the sample project worked just fine!

Review my API Controller

I am new to javascript backend and I am currently learning to build a RESTful API using node.js, express.js, sequelize.js with MySQL as my database. I have successfully built a basic Tasks API as a test. I am looking for feedback on if I did this correctly as far as javascript best practices go within one controller. Any feedback will be appreciated.
Current Logic: User can own multiple tasks
Everything works fine. I am authenticating the users using JWT strategy in Passport.js. I am authenticating them at the router level and then I am double-checking their records in db through userid before they are allowed to make any updates or deletes to their own records. Anyways, here is the controller:
'use strict';
var jwt = require('jsonwebtoken');
var config = require('../config'),
db = require('../services/database'),
Task = require('../models/task');
var TaskController = {};
// GET ALL Tasks
TaskController.get = function (req, res) {
if (!req.user.id) {
res.json({ message: 'You are not authorized.' });
} else {
db.sync().then(function () {
return Task.findAll({ where: { userid: req.user.id } }).then(function (result) {
res.status(202).json(result);
});
});
}
}
// POST ONE Task
TaskController.post = function (req, res) {
if (!req.body.task) {
res.json({ message: 'Please provide a task to post.' });
} else {
db.sync().then(function () {
var newTask = {
userid: req.user.id,
task: req.body.task
};
return Task.create(newTask).then(function () {
res.status(201).json({ message: 'Task Created!' });
});
});
}
}
// PUT ONE Task
TaskController.put = function (req, res) {
if (!req.body.task) {
res.json({ message: 'Please provide a task to update.' });
} else {
db.sync().then(function () {
// Find task by task id and user id
Task.find({ where: { id: req.params.id, userid: req.user.id } })
.then(function (task) {
// Check if record exists in db
if (task) {
task.update({
task: req.body.task
}).then(function () {
res.status(201).json({ message: 'Task updated.' });
});
} else {
res.status(404).json({ message: 'Task not found.' });
}
});
});
}
}
// DELETE ONE Task
TaskController.delete = function (req, res) {
if (!req.params.id) {
res.json({ message: 'Please provide a task to delete.' });
} else {
db.sync().then(function () {
Task.find({ where: { id: req.params.id, userid: req.user.id } })
.then(function (task) {
if (task) {
task.destroy({ where: { id: req.params.id } })
.then(function () {
res.status(202).json({ message: 'Task deleted.' });
});
} else {
res.status(404).json({ message: 'Task not found.' });
}
});
});
}
}
module.exports = TaskController;
The TaskController.js looks good but I would suggest moving all the ORM logic (Sequelize) to a file called TaskService.js
Example -
In TaskService.js -
...
exports.delete = function() {
db.sync().then(function () {
Task.find({ where: { id: req.params.id, userid: req.user.id } })
.then(function (task) {
if (task) {
task.destroy({ where: { id: req.params.id } })
.then(function () {
res.status(202).json({ message: 'Task deleted.' });
});
} else {
res.status(404).json({ message: 'Task not found.' });
}
});
});
}
then in TaskController.js -
...
const TaskService = require('./TaskService);
...
TaskController.delete = function(req, res) {
if (!req.params.id) {
res.json({ message: 'Please provide a task to delete.' });
} else {
TaskService.delete();
}
}
One thing I'd like to call out as far as Javascript best practices would be nested promises, which is a bit of an anti-pattern. You lose the power of promise chains when you nest them, in effect creating nested callbacks. Things will start getting weird once you start trying to use .catch() blocks for error handling. A quick refactor with catch blocks might look like this, even though this is still messy because of the conditional based on the whether or not the task exists in the DB:
// PUT ONE Task
TaskController.put = function (req, res) {
if (!req.body.task) {
res.json({ message: 'Please provide a task to update.' });
} else {
db.sync()
.then(function () {
// Find task by task id and user id
// NOTE: we return the promise here so that we can chain it
// to the main promise chain started by `db.sync()`
return Task.find({ where: { id: req.params.id, userid: req.user.id } });
})
.then(function (task) {
// Check if record exists in db
if (task) {
task.update({ task: req.body.task })
.then(function () {
res.status(201).json({ message: 'Task updated.' });
})
.catch(function (updateError) {
// do something with your update error
// catches an error thrown by `task.update()`
});
} else {
res.status(404).json({ message: 'Task not found.' });
}
})
.catch(function (promiseChainError) {
// do something with your promiseChainError
// this catch block catches an error thrown by
// `db.sync()` and `Task.find()`
});
}
}
Alternatively, if you're more comfortable with synchronous style code and have the option of updating your node version to v7+, this is what your functions might look like using async/await:
// PUT ONE Task
TaskController.put = async function (req, res) {
if (!req.body.task) {
res.json({ message: 'Please provide a task to update.' });
} else {
try {
await db.sync();
const task = await Task.find({ where: { id: req.params.id, userid: req.user.id } });
// Check if record exists in db
if (task) {
await task.update({ task: req.body.task });
res.status(201).json({ message: 'Task updated.' });
} else {
res.status(404).json({ message: 'Task not found.' });
}
} catch (error) {
// do some something with your error
// catches all errors thrown by anything in the try block
}
}
}
There's plenty of resources out there about promise chains and handling async methods. Check this one out in particular: http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/

Chrome not receiving fcm messages, but firefox does

I am using FCM to facilitate push messages in a custom service worker. I followed the FCM getting started but am running into issues where the javascript client isn't receiving the sent messages on chrome, but firefox is working as intended.
The messages are being sent from a hosted server, and the messages are sent with no failures and message id's associated with each registered client.
Below is the page script and below that will be relevant service worker code.
page html
<script>
// Initialize Firebase
var config = {
<CONFIG SETTINGS>
};
firebase.initializeApp(config);
var messaging = firebase.messaging();
</script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('/sw.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
messaging.useServiceWorker(registration);
resetUI();
}).catch(function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
const permissionDivId = 'permission_div';
messaging.onTokenRefresh(function () {
messaging.getToken()
.then(function (refreshedToken) {
console.log('Token refreshed.');
setTokenSentToServer(false);
sendTokenToServer(refreshedToken);
resetUI();
})
.catch(function (err) {
console.log('Unable to retrieve refreshed token ', err);
});
});
messaging.onMessage(function (payload) {
console.log("Message received. ", payload);
appendMessage(payload);
});
function resetUI() {
clearMessages();
messaging.getToken()
.then(function (currentToken) {
if (currentToken) {
sendTokenToServer(currentToken);
updateUIForPushEnabled(currentToken);
} else {
console.log('No Instance ID token available. Request permission to generate one.');
updateUIForPushPermissionRequired();
setTokenSentToServer(false);
}
})
.catch(function (err) {
console.log('An error occurred while retrieving token. ', err);
setTokenSentToServer(false);
});
}
function sendTokenToServer(currentToken) {
if (!isTokenSentToServer()) {
console.log('Sending token to server...');
<TOKEN SENT TO SERVER AND STORED>
setTokenSentToServer(true);
} else {
console.log('Token already sent to server so won\'t send it again ' +
'unless it changes');
}
}
function isTokenSentToServer() {
if (window.localStorage.getItem('sentToServer') == 1) {
return true;
}
return false;
}
function setTokenSentToServer(sent) {
window.localStorage.setItem('sentToServer', sent ? 1 : 0);
}
function showHideDiv(divId, show) {
const div = document.querySelector('#' + divId);
if (show) {
div.style = "display: visible";
} else {
div.style = "display: none";
}
}
function requestPermission() {
console.log('Requesting permission...');
messaging.requestPermission()
.then(function () {
console.log('Notification permission granted.');
resetUI();
})
.catch(function (err) {
console.log('Unable to get permission to notify.', err);
});
}
function deleteToken() {
messaging.getToken()
.then(function (currentToken) {
messaging.deleteToken(currentToken)
.then(function () {
console.log('Token deleted.');
setTokenSentToServer(false);
resetUI();
})
.catch(function (err) {
console.log('Unable to delete token. ', err);
});
})
.catch(function (err) {
console.log('Error retrieving Instance ID token. ', err);
});
}
// Add a message to the messages element.
function appendMessage(payload) {
const messagesElement = document.querySelector('#messages');
const dataHeaderELement = document.createElement('h5');
const dataElement = document.createElement('pre');
dataElement.style = 'overflow-x:hidden;'
dataHeaderELement.textContent = 'Received message:';
dataElement.textContent = JSON.stringify(payload, null, 2);
messagesElement.appendChild(dataHeaderELement);
messagesElement.appendChild(dataElement);
}
// Clear the messages element of all children.
function clearMessages() {
const messagesElement = document.querySelector('#messages');
while (messagesElement.hasChildNodes()) {
messagesElement.removeChild(messagesElement.lastChild);
}
}
function updateUIForPushEnabled(currentToken) {
showHideDiv(permissionDivId, false);
}
function updateUIForPushPermissionRequired() {
showHideDiv(permissionDivId, true);
}
</script>
sw.js
self.addEventListener('push', function (event) {
console.log('Service Worker recived a push message', event.data.text());
var notification = event.data.json().notification;
var title = notification.title;
event.waitUntil(
self.registration.showNotification(title, {
body: notification.body,
icon: notification.icon,
data: { url: notification.click_action }
}));
});
Thank you for any help you can give!

Categories

Resources