How to issue an event to all connected sockets?
export class EventsGateway {
#SubscribeMessage('message')
async onEvent(client, data) {
// The following is the use of `socket.io` to issue events to all connected sockets.
// io.emit('message', data);
}
}
How do I perform this in nestjs?
NestJS allows you to create message listeners using decorators. Within this method, you are able to respond to the client by returning a WsResponse object.
However, NestJS also allows you to get the WebSocket instance using the WebSocketServer decorator.
To send an Event to all connected clients you will need to use the WebSocketServer decorator and use the native WebSocket instance to emit a message, like so:
import WebSocketServer from '#nestjs/websockets'
export class EventsGateway {
#WebSocketServer() server;
#SubscribeMessage('message')
onEvent(client: any, payload: any): Observable<WsResponse<any>> | any {
this.server.emit('message', payload);
}
}
Related
I am trying to access the hostname of a client in NestJS websockets when a message is received. I have searched here, NestJS docs, and the project Github issues & code but have not found a solution.
I have tried accessing properties on the client object, but the hostname is not included. I was able to find it in the handleConnection subscriber, but I need it in the SubscribeMessage handler.
Would love any help!
Sample of the code from the docs:
export class SessionsGateway
implements OnGatewayConnection, OnGatewayDisconnect
{
constructor() {}
async handleDisconnect() {}
async afterInit(server) {}
#SubscribeMessage('init')
async onInit(
#MessageBody() event,
#ConnectedSocket() client,
) {
try {
// GET THE HOSTNAME HERE
} catch (error) {
console.log(error);
client.close();
}
}
I am making an application with sockets and the need arises to broadcast information, but only to people who are inside a room.
This is my code from the server.ts
// Dependencies
import express from 'express'
import http from 'http'
import socket from 'socket.io';
import {connect, disconnect, orderChanged} from './sockets/socket';
import {config} from 'dotenv';
config ();
// Main class
export default class server {
_private static instance: server
public app: express.Application
public port: number
http: http.Server private
public io: socket.Server
// Initialize variables and methods
// Singleton pattern implementation
private constructor () {
this.app = express ()
this.port = Number (process.env.SRV_PORT)
this.http = new http.Server (this.app)
this.io = new socket.Server (this.http, {
cors: {
origin: true,
credentials: true
}
})
this.listenSockets ();
}
// Return the instance running Singleton pattern
public static get instance () {
returns this._instance || (this._instance = new Server ())
}
// Method to start the server
start (callback: any) {
this.http.listen (this.port, callback)
}
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
// User disconnected
disconnect (client);
connect (client);
});
}
}
Since node starts, an instance is created in DP Singleton and the socket listener is launched
When an operation happens in the database, anywhere in the app, I send it to call and send information to the front-end which is correctly received by the front-end and does what it has to do. Example url / edit-products
import server from '../core/server';
// Socket broadcast, new information
const __id = String (req.headers.id);
const updatedData = await getNewData (__id);
Server.instance.io.emit ('data changed', updatedData);
The problem is that this information is sent indiscriminately to all users connected to the socket. Now, I have a unique ID that brings multiple users together in a MongoDB model. You could use that ID to broadcast only to users with that ID. There is a logic that implies that if the user connects from Mexico, add it to an Array of people in MongoDB, otherwise it will add it to another MongoDB document, then they are two different IDs.
I would love the room to be that ID.
I saw that I could use the socket's join () method, but that function derives from the connected client, not from the server itself. I try to issue the information like this
// Socket broadcast, new information
const __id = String (req.headers.id);
const updatedData = await getNewData (__id);
Server.instance.io.in (updatedData._id) .emit ('data changed', updatedData);
But at no point did I set up that "ROOM". When the user login, he could add it but I don't know how to create a custom room, he tried something like this
const user = await UserModel.find (_data);
Server.instance.io.join (user.channel._id);
But that function within io does not exist.
It exists this way, but it doesn't work for me
Server.instance.io.on ('user-join', (socket: Socket) => {
console.log (plug);
socket.join (uuid);
});
Server.instance.io.emit ('user join');
What could I do?
.join() is a method on an individual socket. That's how you use it as socket.join(roomName). When the first user joins a room, the room is created automatically and other users can also join it. When the last user leaves the room, the room is removed automatically from the server. So, you join a user's socket to a room - you don't join something to the server.
Similarly, when you tried this:
Server.instance.io.on ('user-join', (socket: Socket) => {
console.log (plug);
socket.join (uuid);
});
That doesn't work because you don't listen for incoming messages from a socket on the server (except for the connection message - which introduces the socket object). You listen for incoming client messages on a socket itself:
Server.instance.io.on ('connection', (socket: Socket) => {
socket.on('user-join', () => {
// you will have to find the room name that goes with this socket
socket.join(someRoomName);
});
});
Also, note that this code:
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
// User disconnected
disconnect (client);
connect (client);
});
}
looks problematic. Why would you disconnect a client when they connect? You don't show those functions disconnect() and connect() so it's unclear what they actually do - I would guess they keep track of connected clients somehow. If you're just trying to clean up any state that might have been previously left hanging, then you should be doing something like this:
private listenSockets (): void {
console.log ('Listening Sockets');
this.io.on ('connection', client => {
console.log ('Connected to room', client.rooms, '-', client.id);
client.on('disconnect', () => {
// User disconnected
disconnect(client);
});
// user connected now
connect(client);
});
}
You don't have to worry about inaccurate housekeeping on whether a socket is connected or not. You will always get a disconnect event for a socket when it disconnects. This is for two reasons. For a browser window that closes or a page that the user navigates away from, the browser cleans up all objects associated with that page, including the open socket.io connection. This will always close the socket and cause a disconnect event. Second, socket.io uses ping and pong messages to regularly check if an existing connection is still working. If it's not, it will get disconnected. The client may or may not retry to open a new connection depending upon the circumstance. But, any disfunctional connection (one that isn't respond to ping messages) will get closed by the server and a disconnect event will occur for that too. So, those two circumstances make sure that a disconnect event always happens.
Server.instance.io.emit ('data changed', updatedData); The problem is that this information is sent indiscriminately to all users connected to the socket.
This sends to all users connected to your server and is how it was designed.
To send to a single socket, you would use:
socket.emit(...);
where socket is what you're code calls client, the object you get from the connection event.
To send to all sockets who have joined a room, you would use:
io.in(roomName).emit(...)
where io is the socket.io server instance.
And, there are many, many more variations of .emit() depending upon exactly what you're trying to send to.
Now, I have a unique ID that brings multiple users together in a MongoDB model. You could use that ID to broadcast only to users with that ID. There is a logic that implies that if the user connects from Mexico, add it to an Array of people in MongoDB, otherwise it will add it to another MongoDB document, then they are two different IDs. I would love the room to be that ID.
I don't completely follow what you're trying to do, but it seems like inside your connect(client) function, you could just call client.join(uniqueIDForMultipleUsers) and that would create a room with this uniqueID and add this client to that room. In the future, you can send to everyone in that room with io.in(uniqueIDForMultipleUsers).emit(...).
But at no point did I set up that "ROOM". When the user login, he could add it but I don't know how to create a custom room, he tried something like this
You don't create rooms manually. You just use socket.join(roomName) and the socket.io infrastructure automatically creates the room if it doesn't already exist. Similarly when the last socket in a room either leaves the room or disconnects, the room is automatically removed. So you just don't have to manage the room creation or deletion yourself. In fact, a room object is not something you ever deal with directly - it's a housekeeping item inside of the socket.io server that contains a list of sockets that are currently in the room. A socket can be in as many rooms as it wants to be. You use these on the server:
socket.join(roomName); // add a client's socket to a room
socket.leave(roomName); // remove a client's socket from a room
io.in(roomName).emit(...); // broadcast a message to every socket in a room
What is sometimes a bit confusing about the above logic is that socket.join() and socket.leave() are socket methods, but they actually modify a data structure in the server (where the list of rooms/sockets are kept). For whatever reason, that's just how they chose to originally design the API. Logically, it's more like io.join(socket, roomName) since it's modifying something on the server. But, since the socket knows the server object it's part of, they can leave that off and just do socket.join(roomName).
I am trying to see if there is a way to dynamically use #SubscribeMessage() decorator.
So far, I have been able to listen to "known events" like in Method(a). But I would like to dynamically generate the event-name <Method (b)> in controller and start listening to it in gateway.
or, I want to listen to all events emitted on that socket and process them as in Method 2
Method(a):
#SubscribeMessage('event1')
handleMessage(args[]){
// DO STUFF
}
Method (b):
#SubscribeMessage(MY_DYNAMIC_EVENT_AT_RUNTIME)
async handleMessage(client: Socket, payload: string){
//DO STUFF
}
Method 2:
#SubscribeMessage(*)
async handleMessage(client: Socket, payload: string){
//DO STUFF
}
I saw some wildcard concepts for socket.io , How can I use it here with NestJs?
I'm trying to setup a MQTT Microservice using NestJS according to the docs.
I've started a working Mosquitto Broker using Docker and verified it's operability using various MQTT clients. Now, when I start the NestJS service it seems to be connecting correctly (mqqt.fx shows new client), yet I am unable to receive any messages in my controllers.
This is my bootstrapping, just like in the docs:
main.ts
async function bootstrap() {
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.MQTT,
options: {
host: 'localhost',
port: 1883,
protocol: 'tcp'
}
});
app.listen(() => console.log('Microservice is listening'));
}
bootstrap();
app.controller.ts
#Controller()
export class AppController {
#MessagePattern('mytopic') // tried {cmd:'mytopic'} or {topic:'mytopic'}
root(msg: Buffer) {
console.log('received: ', msg)
}
}
Am I using the message-pattern decorator wrongly or is my concept wrong of what a NestJS MQTT microservice even is supposed to do? I thought it might subscribe to the topic I pass to the decorator. My only other source of information being the corresponding unit tests
nest.js Pattern Handler
On nest.js side we have the following pattern handler:
#MessagePattern('sum')
sum(data: number[]): number {
return data.reduce((a, b) => a + b, 0);
}
As #Alexandre explained, this will actually listen to sum_ack.
Non-nest.js Client
A non-nest.js client could look like this (just save as client.js, run npm install mqtt and run the program with node client.js):
var mqtt = require('mqtt')
var client = mqtt.connect('mqtt://localhost:1883')
client.on('connect', function () {
client.subscribe('sum_res', function (err) {
if (!err) {
client.publish('sum_ack', '{"data": [2, 3]}');
}
})
})
client.on('message', function (topic, message) {
console.log(message.toString())
client.end()
})
It sends a message on the topic sum_ack and listens to messages on sum_res. When it receives a message on sum_res, it logs the message and ends the program. nest.js expects the message format to be {data: myData} and then call the param handler sum(myData).
// Log:
{"err":null,"response":5} // This is the response from sum()
{"isDisposed":true} // Internal "complete event" (according to unit test)
Of course, this is not very convenient...
nest.js Client
That is because this is meant to be used with another nest.js client rather than a normal mqtt client. The nest.js client abstracts all the internal logic away. See this answer, which describes the client for redis (only two lines need to be changed for mqtt).
async onModuleInit() {
await this.client.connect();
// no 'sum_ack' or {data: [0, 2, 3]} needed
this.client.send('sum', [0, 2, 3]).toPromise();
}
The documentation is not very clear, but it seem that for mqtt if you have #MessagePattern('mytopic') you can publish a command on the topic mytopic_ack and you will get response on mytopic_res. I am still trying to find out how to publish to the mqtt broker from a service.
See https://github.com/nestjs/nest/blob/e019afa472c432ffe9e7330dc786539221652412/packages/microservices/server/server-mqtt.ts#L99
public getAckQueueName(pattern: string): string {
return `${pattern}_ack`;
}
public getResQueueName(pattern: string): string {
return `${pattern}_res`;
}
#Tanas is right. Nestjs/Microservice now listens to your $[topic] and answer to $[topic]/reply. The postfix _ack and _res are deprecated.
For example:
#MessagePattern('helloWorld')
getHello(): string {
console.log("hello world")
return this.appService.getHello();
}
Listens now on Topic: helloWorld
Replies now on Topic helloWorld/reply
Regarding ID
You should also provide an id within the payload (See #Hakier) and Nestjs will reply with an answer, containing your id.
If you don't have any id, there still won't be any reply but the corresponding logic will still trigger.
For example (Using the snipped from above):
your msg:
{"data":"foo","id":"bar"}
Nestjs reply:
{"response":"Hello World!","isDisposed":true,"id":"bar"}
Without ID:
your message:
{"data":"foo"} or {}
No reply but Hello World in Terminal
I was fighting with MQTT today and this helped me a little, but I had more problems and below you can see my findings:
Wrong way of configuration broker URL
In my case when I used non-local MQTT server I started with this:
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.MQTT,
options: {
host: 'test.mosquitto.org',
port: 1883,
protocol: 'tcp',
},
});
await app.listenAsync();
but like you can read in a constructor of ServerMqtt they use url option only (when not provided it fallbacks to 'mqtt://localhost:1883'. While I do not have local MQTT it will never resolve app.listenAsync() which is resolved only on connect and will also not run any handler.
It started to work when I adjusted code to use url option.
const app = await NestFactory.createMicroservice(AppModule, {
transport: Transport.MQTT,
options: {
url: 'mqtt://test.mosquitto.org:1883',
},
});
await app.listenAsync();
Messages require id property
Second very weird problem was that when I used Non-nest.js Client script from #KimKern I had to register two MessagePatterns: sum and sum_ack:
#MessagePattern('sum')
sum(data: number[]): number {
return data.reduce((a, b) => a + b, 0);
}
#MessagePattern('sum_ack')
sumAck(data: number[]): number {
return data.reduce((a, b) => a + b, 0);
}
When I used console.log I discovered that the latter is being run but only when the first one is present. You can push the same message to the broker using mqtt cli tool to check it:
mqtt pub -t 'sum_ack' -h 'test.mosquitto.org' -m '{"data":[1,2]}'
But the biggest problem was that it didn't reply (publish sum_res).
The solution was to provide also id while sending a message.
mqtt pub -t 'sum_ack' -h 'test.mosquitto.org' -m '{"data":[1,2], "id":"any-id"}'
Then we could remove 'sum_ack' MessagePattern and leave only this code:
#MessagePattern('sum')
sum(data: number[]): number {
return data.reduce((a, b) => a + b, 0);
}
The reason for this was hidden inside handleMessage method of ServerMqtt which will not publish response from a handler if a message didn't have id.
TL/DR
Specify url to message broker using url option only and always provide id for a message.
I hope that will save some time to others.
Happy hacking!
I am trying to publish data to a frontend angular application using socket.io in feathersjs. I have created a channel as follow and created mockData stream.
function mockData(app) {
app.emit('testEvent', { test: 'Something happened'});
};
setInterval(mockData.bind(null, app), 1000);
app.publish('testEvent', (data) => {
console.log('test');
return app.channel('testStream1');}
)
And when client is connected, I have added the client to testStream1 channel as follows.
app.on('connection', connection => {
// On a new real-time connection, add it to the anonymous channel
app.channel('testStream1');
app.channel('anonymous').join(connection);
app.channel('testStream1').join(connection);
});
Here nothing in app.publish callback executes, but if I try to directly emit from socketio object then I can receive values in client.
function mockData(app) {
app.emit('testEvent', { test: 'Something happened'});
app.io.emit('testStream1', { text: 'A client connected!' }); // Adding this line, I am able to get values from socketio client
};
Seems that app.publish([event,] fn) doesn't register the publishing function for custom event testEvent. How to get the data using app.publish ?
It might be neat feature in the future but currently, publishers and channels are only intended for service events, not global events. Although the app object is an event emitter, it won't send anything to the client. If you create a custom service event named testEvent you will be able to do
app.service('myservice').emit('testEvent', 'this will be published to clients');
You can also emit any Socket.io event on app.io directly but it will not go through Feathers channel/publishing mechanism.