I have a Node server which consumes messages from a RabbitMQ queue and forwards them to a React frontend as a socket.io event. In the frontend, I have a button click which sends a socket.io event back to the Node server.
Currently, the Node server only logs the receipt of the socket.io event. In addition to logging, I would like to send a message ack to the RabbitMQ server upon receipt of the socket.io event.
The logging is working fine, but I've been struggling with the message acknowledgement part.
My node server looks like this:
server.js
const io = require('./socket');
const amqp = require('amqplib/callback_api');
const CONFIG = require('./config.json');
amqp.connect(`amqp://${CONFIG.host}`, (err, connection) => {
if (err) {
throw err;
}
connection.createChannel((err, channel) => {
if (err) {
throw err;
}
const queue = CONFIG.queueName;
channel.assertQueue(queue, {
durable: true
});
console.log(` [*] Waiting for messages in ${queue}.`);
channel.consume(queue, function(msg) {
console.log(' [x] Request received from RabbitMQ: %s', msg.content.toString());
io.client.emit('sendReview', msg.content.toString());
}, {
noAck: false
});
})
});
socket.js
const io = require('socket.io')();
const port = process.env.PORT || 8000;
module.exports = {
client : any = io.on('connection', (client) => {
console.log(' [*] New client connected with ID: ' + client.id);
client.on('reportReview', (msg) => {console.log(` [x] Response received from browser: ${msg}`)});
client.on('disconnect', () => console.log(` [*] User ${client.id} disconnected.`));
})
};
io.listen(port);
console.log(`Listening on port ${port}`);
My frontend looks like this:
App.js
import React, { Component } from "react";
import * as API from './api';
export default class App extends Component {
constructor(props, context) {
super(props, context);
this.state = {
data: ["Whoops - no reviews available"],
};
this.updateReview = this.updateReview.bind(this);
this.onMessageReceived = this.onMessageReceived.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
API.reportClick(this.state.data[0]);
this.updateReview()
}
updateReview() {
const newArray = this.state.data.slice(1);
if (newArray.length === 0) {
this.setState({data: ["Whoops - no reviews available"]})
} else {
this.setState({data: newArray})
}
}
onMessageReceived(msg) {
console.log(`Request for review received: ${msg}`);
const updatedData = this.state.data.concat(msg);
this.setState({data: updatedData});
if (this.state.data[0] === "Whoops - no reviews available") {
this.updateReview()
}
}
componentDidMount() {
API.subscribe(this.onMessageReceived)
}
render() {
return (
<div className="App">
<p className="App-intro">
Click to confirm review #: {this.state.data[0]}
</p>
<button onClick={this.handleClick}>Click</button>
</div>
);
}
}
Api.js
import clientSocket from 'socket.io-client';
const socket = clientSocket('http://localhost:8000');
function subscribe(onMessageReceived) {
socket.on('sendReview', onMessageReceived);
}
function reportClick(msg) {
socket.emit('reportReview', msg);
}
export { reportClick, subscribe };
As far as I understand, in order to send a message ack I would have to call channel.ack(msg); somewhere on the Node server. However, I am not sure how to pass the channel object to the io module? I have also tried having the socket.io code in server.js so I would have access to the channel object but have not been able to get this to work, either - I have not been able to get the amqp connection and socket.io connection to work together other than using my current approach of having an io module.
Any help would be very much appreciated.
I ended up getting it to work by having the socket code in server.js like this:
const io = require('socket.io')();
function socketIOHandler(callback) {
io.on('connection', (socket) => {
socket.on('error', function(err) {
console.log(err.stack);
});
callback(socket);
});
}
var amqpConn = null;
// start amqp connection to rabbit mq
function start() {
amqp.connect(`amqp://${CONFIG.host}`, (err, connection) => {
if (err) {
throw err;
}
amqpConn = connection;
// start consume worker when connected
startWorker();
});
}
function startWorker() {
socketIOHandler((socket) => {
amqpConn.createChannel((error, channel) => {
... <---- all the bits as before
socket.on('msgSent', (msg) => {
channel.ack(msg);
});
})
});
io.listen(port);
}
start();
Related
I'm trying to create a router for every websocket event action.
Let's user is on the chat room, I want the url be ws://localhost:3001/chat but for the other event, let's say vote should be smth like this ws://localhost:3001/vote.
I found this example: const ws = new ws.Server({server:httpServer, path:"/chat"}), but this is a generic one, and doesn't aplly to different event actions.
Here is my structure. Hopefully will make more sense
As you see there I have two event actions: chat and vote and my goal is to create a route for each action types.
Socket.ts
import { IncomingMessage } from 'http';
import internal from 'stream';
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws, request) => {
...
ws.on('message', (data: string) => {
try {
switch (action) {
case 'chat':
wss.clients.forEach(each(client)=> {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
break;
case 'vote':
wss.clients.forEach((player) => {
if (client.readyState === WebSocket.OPEN) {
player.client.send(JSON.stringify(voteMessage));
}
});
break;
default: {
throw new Error('Unknown message type.');
}
}
} catch (error) {
ws.send(`Error: ${error.message}`);
}
});
});
const socketUpgrade = (
request: IncomingMessage,
socket: internal.Duplex,
head: Buffer
) =>
wss.handleUpgrade(request, socket, head, (ws) =>
wss.emit('connection', ws, request)
);
export { socketUpgrade };
app.ts
import express from 'express';
import { socketUpgrade } from './Socket';
const app = express();
const port = 3001;
const server = app.listen(port, () => {
console.log(`listening on port ${port}`);
});
server.on('upgrade', socketUpgrade);
Any help will be appreciated
I am using socket_io_client package in a flutter app for chatting, when I emit an event from my side to the server side it works well, however I don't receive an event from the server side
Here is the server side code using Node JS and it uses Socket package ^4.4.0
const io = require('socket.io')(6600, {cors: {original: '+'}});
io.on('connection', (socket) => {
console.log('user connected');
socket.on('userid', (userId) => {
console.log(userId);
socket.userID = userId;
console.log(socket.userID);
socket.join(socket.userID);
// console.log(io.sockets.adapter.rooms[socket.userID].length);
});
// send all sockets of the same user to the same room
socket.on('sendmessage', ({content, senderId, receiverId}) => {
console.log(content);
console.log(senderId);
console.log(senderId === socket.userID);
console.log(receiverId);
socket.to(receiverId).to(senderId).emit('receivemessage', {
content,
senderId,
receiverId,
});
});
socket.on('disconnect', async () => {
const matchingSockets = await io.in(socket.userID).allSockets();
const isDisconnected = matchingSockets.size === 0;
if (isDisconnected) {
console.log('user disconnected');
}
});
and here is my flutter code I use socket_io_client of version ^2.0.0-beta.4-nullsafety.0 , I can send the 'sendmesssage' event to the server, but I don't receive 'receivemessage' event
class SocketIO extends ChangeNotifier {
///Socket object
IO.Socket socket;
///Connects to socket server using [userId]
void connectToServer(BuildContext context, String userId) {
socket = IO.io(
'http://10.0.2.2:6600',
IO.OptionBuilder()
.setTransports(['websocket']) // for Flutter or Dart VM
.disableAutoConnect() // disable auto-connection
.build());
socket = socket.connect();
print('connection id: ' + userId);
socket.onConnect((_) {
print('connect');
socket.emit('userid', userId);
socket.on( // not working
'receivemessage',
(data) => () {
print('message recieved');
Provider.of<Messaging>(context, listen: false).receiveMessage(
context,
data['content'],
data['senderId'],
data['receiverId']);
});
});
socket.onDisconnect((_) => {print('disconnect')});
}
void sendMessage(String senderId, String receiverId, String text) {
print(socket.connected);
socket.emit('sendmessage',
{'content': text, 'senderId': senderId, 'receiverId': receiverId});
print('sender id: $senderId');
}
void disconnect() {
socket.off('privateMessage');
socket.disconnect();
}
}
I can't find the problem at all
Did you try this? Also I changed (data) => () {} to (data) {}
//...
socket.onConnect((_) {
print('connect');
socket.emit('userid', userId);
});
//not inside onConnect callback
socket.on('receivemessage',
(data) {
print('message recieved');
Provider.of<Messaging>(context, listen: false).receiveMessage(
context,
data['content'],
data['senderId'],
data['receiverId']);
});
socket.onDisconnect((_) => {print('disconnect')});
}
//...
How do can I fetch data from MQTT to Vue app, I've established a properly working connection and I can console log the data but I'm not able to load the data to component's data property.
created() {
client.on("connect", function() {
console.log("MQTT Connected");
client.subscribe("#", function(err) {
console.log(err);
});
});
client.on("message", (topic, message) => {
console.log("topic:", topic);
console.log(message.toString());
this.mqttData = JSON.parse(message.toString());
});
},
data() {
return {
mqttData: {}
}
};
Whenever I try to log the mqttData in console it seems to be a empty object. When I printed this inside of the client.on function I've got the correct Vue instance with all of it's fields and methods. This really bothers me because I can access the Vue object but I cannot modify it's contents.
Maybe try this in the "mounted" lifecycle hook. Here's an example of something I use that's listening to a websocket. It should be similar implementation to your application
mounted() {
let connection = new WebSocket('wss://somesocket.net/ws')
connection.onmessage = (event) => {
console.log("Data received!")
console.log(event.data)
const data = JSON.parse(event.data)
this.ws_data = data
}
connection.onopen = (event) => {
console.log("Connected! Waiting for data...")
}
},
This is how I did it using vue-mqtt package.
export default {
data () {
return {
sensor: ''
}
},
mqtt: {
/** Read incoming messages from topic test*/
'test' (data) {
this.sensor = data
console.log(data)
}
},
created () {
},
async mounted () {
this.$mqtt = await this.$mqtt
this.$mqtt.publish('test', 'hello from vuemqtt yo !!!!')
this.$mqtt.subscribe('test')
}
}
I'm using socket io in two places in the app:
emiting offers on the main page that everyone can see
emiting chat messages only between two users based on order_id
I was able to set up first use case but not the second. When creating a new message, response status is 500 after hitting the socket part in the controller.
index.js
const serverIO = server.listen(
port,
console.log(`Listening on Port ${port}`)
);
const io = require("./socket").init(serverIO);
io.on("connection", (socket) => {
socket.join("some room");
console.log("cient connected");
});
socket.js
let io;
module.exports = {
init: (httpServer) => {
io = require("socket.io")(httpServer);
return io;
},
getIO: (socket) => {
if (!io) {
throw new Error("Socket.io not initialized!");
}
console.log("socket", socket());
return io;
},
};
chatController.js
const io = require("../socket");
const chatModel = require("./chatModel.js");
exports.createChat = async (req, res) => {
try {
const savedMessage = await chatModel.saveMessage(req.body);
if (!savedMessage) {
return res.status(400).json({
errorMessage: "Something went wrong with your chat request",
});
}
io.getIO().socket.to(req.body.order_id).emit("newMessage", { action: "create", message: savedMessage });
return res.status(200).json(savedMessage);
} catch (error) {
return res.status(500).json({
errorMessage: error,
});
}
};
on the client, I'm listening like this:
Chat.js
useEffect(() => {
const socket = openSocket(baseURL);
socket.on("newMessage", ({ room, data }) => {
console.log("room", room); //not being reached
if (data.action === "create") {
dispatch(addMessage(...data.message));
}
});
}, []);
I tried adding the boilerplate code from documentation but that didn't seem to work.
io.on('connection', socket => {
socket.join('some room');
});
How can I join rooms based on orderId and listen to said room on the client?
Was able to reach a working solution (chat messages are being broadcast only to the intended recipients)but don't know if it's optimal or efficient.
added socket.join in my index.js file
io.on("connection", (socket) => {
socket.on("joinRoom", (room) => {
console.log("joined room");
socket.join(room);
});
console.log("cient connected");
});
modified my controller
io.getIO().to(req.body.order_id).emit("newMessage", {
action: "create",
message: savedMessage,
});
And on the front end, on mount, I'm joining a room and listening for newMessage from server.
useEffect(() => {
const socket = openSocket(baseURL);
socket.emit("joinRoom", orderId);
socket.on("newMessage", (data) => {
console.log("data", data);
if (data.action === "create") {
dispatch(addMessage(...data.message));
}
});
}, []);
I am working with the Asterisk ARI Node.js client and would like to listen for certain events and then perform an action. From my understanding after connecting to the server you can setup several different types of event listeners for events being published via WebSockets to perform tasks. In my code below I don't receive any output even though I am triggering these specific events and can connect via WSCat and watch the events streaming.
The app I am building should just listen for the events to occur and update a database. I will never need to access the Node application through an HTTP request which is why I returning forbidden on every request to the server. My end goal is to just have this application sitting on a server reacting to events.
'use strict';
const http = require('http');
const hostname = '127.0.0.1';
const port = 3000;
const client = require('ari-client');
const util = require('util');
const server = http.createServer((req, res) => {
res.statusCode = 403;
res.end('FORBIDDEN');
});
server.listen(port, hostname, () => {
client.connect('http://127.0.0.1:8088', 'username', 'password')
.then(function(ari) {
ari.on('DeviceStateChanged', function(event) {
console.log(event);
})
ari.on('ChannelCreated', function(event) {
console.log(event);
})
ari.on('BridgeCreated', function(event) {
console.log(event);
})
ari.on('StasisStart', function(event) {
console.log(event);
})
ari.on('PeerStatusChange', function(event) {
console.log('blah', event);
})
ari.on('Dial', function(event) {
console.log('Dial', event);
})
})
.catch(function(err) {
console.log(err);
})
});
Why create a server then? You might test the following.
'use strict';
const client = require('ari-client');
const util = require('util');
client.connect('http://127.0.0.1:8088', 'username', 'password')
.then(function(ari) {
ari.on('DeviceStateChanged', function(event) {
console.log(event);
})
ari.on('ChannelCreated', function(event) {
console.log(event);
})
ari.on('BridgeCreated', function(event) {
console.log(event);
})
ari.on('StasisStart', function(event) {
console.log(event);
})
ari.on('PeerStatusChange', function(event) {
console.log('blah', event);
})
ari.on('Dial', function(event) {
console.log('Dial', event);
})
})
.catch(function(err) {
console.log(err);
});