What's a valid #MessagePattern for NestJS MQTT microservice? - node.js

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!

Related

Twitch bot with node.js

Im new to node and tried to make a simple twitch chat bot with node.js following a youtube tutorial but
everything works untill i try to use "!" command... I use the command and the bot simply doesnt respond to the command.
Here is what i have so far:
const tmi = require('tmi.js'),
{channel , username , password } = require('./settings.json');
const options = {
options: {debug: true},
connection: {
reconnct: true,
secure: true
},
identity : {
username,
password
},
channel: [channel]
};
const client = new tmi.Client(options);
client.connect().catch(console.error);
client.on('connected', () => {
client.say(channel, `${username} joined the chat`)
})
client.on('message', (channel, user, message ,self) => {
if(self) return;
if(message == '!ping') {
client.say(channel, `#${user.username}, pong`);
}
});
I cant figure it out i just want it to respond
I only played a bit with twitch bots a while ago, but the problem might be the part where you return on self:
if(self) return;
This means that if a message comes from the bot itself, it will be ignored and the function will return (and therefore stop). Meaning that if you linked the bot to the same account from which you are sending messages, those messages are ignored.
Also:
you mispelled reconnect with reconnct in the connection options
in JS I would recommend to use === instead of == because the latter attempts to convert and compare operands that are of different types (you can learn more here)
Edit: what also helps in general is to use console.log() to debug and find out where the problem is

NestJS websockets - Get client URL and hostname

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();
}
}

Cannot subscribe to more than 6 ipfs pubsub channels with ipfs-http-client

I'm currently building a node application with ipfs-http-client.
I need to subscribe to several pubsub channels (~20).
When I'm connecting to the channels, I receive a 200 response for each subscribe, but only the 6 first subscribe are receiving the messages.
I isolated the problem in a little snippet with only:
Connect to the node (ipfs 0.4.23 and I tried with another one in 0.8)
Subscribe to 20 channels (with different names or the same channel with different handlers)
I always reproduce the problem (only connected to the 6 first subscribers)
I'm running my tests with node 14.16.0
When I look into the ipfs-http-client package, I can see that I actually have no response from the http request after the 6 first. Still, no error is reported.
achingbrain answers this question: https://github.com/ipfs/js-ipfs/issues/3741#issuecomment-898344489
In node the default agent used by the http client limits connections to 6 so it's consistent with the browser. You can configure your own agent to change this:
const { create } = require('ipfs-http-client');
const http = require('http')
async function echo(msg) {
console.log(`TopicID: ${msg.topicIDs[0]}, Msg: ${new TextDecoder().decode(msg.data)}`);
}
async function run() {
// connect to the default API address http://localhost:5001
const client = create({
agent: http.Agent({ keepAlive: true, maxSockets: Infinity })
});
console.log(await client.version());
for (let i = 0; i < 20; i++) {
await client.pubsub.subscribe(parseInt(i), echo);
}
}
run();

How to call FIXAPI using any library (fixparser for nodejs)?

I want to use FIXAPI for one of my application.
I am trying connect "LOGON" api. But i am not getting any error or any Response.
While i was tried with "b2bits simulator", it works.
But Using any library, it does not give me any error or reponse.
I am using "fixparser" library (NodeJS npm library) to call the api.
Any help would be appreciated.
Thanks.
calling fixparser with different versions. and also tried different library (node-quickfix)
var fixparser = require("fixparser");
const fixParser = new FIXParser();
fixParser.connect({
host: HOST,
port: PORT,
protocol: 'tcp',
sender: SENDER,
target: TARGET,
fixVersion: VERSION
});
// Sendlogon function
function sendLogon() {
const logon = fixParser.createMessage(
new Field(Fields.MsgType, Messages.Logon),
new Field(Fields.MsgSeqNum, fixParser.getNextTargetMsgSeqNum()),
new Field(Fields.SenderCompID, SENDER),
new Field(Fields.SendingTime, fixParser.getTimestamp()),
new Field(Fields.TargetCompID, TARGET),
new Field(Fields.ResetSeqNumFlag, 'Y'),
new Field(Fields.EncryptMethod, EncryptMethod.None),
new Field(Fields.HeartBtInt, 10)
);
const messages = fixParser.parse(logon.encode());
fixParser.send(logon);
}
// Open connection
fixParser.on('open', async (s, se) => {
console.log("Started....");
sendLogon();
});
// Retrive response
fixParser.on('message', (message) => {
// Received FIX message
// console.log("message",message);
console.log('received message', message.description, message.string);
});
// Close connection
fixParser.on('close', () => {
console.log("closed");
});
I want to get response and error(if any)
Looks like you are trying to connect to Stock Exchange api. So according to their docs: first message sent by client have to be a LOGON message, but FixParser after opening sockets sends heartbeat instead, and api closes connection after unexpected message.

Issue in publishing custom events to socket.io channels in feathersjs

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.

Resources