websocket request with nodeJS - node.js

I am trying to initiate a websocket request using nodeJS. I am new to WS, and I am quite stuck with this, and all examples I could find seem to be the same one with building a chat, repeated over and over in every media lol, and it didn't help me better understand.
I have an API that provides real-time data (forex). I am able to successfully subscribe to sources of real-time data on the front-end, and do what I want. BTW, I don't get any CORS issue doing that, for some reason I don't understand.... :-o
However, i'd like to get this real-time data through my server, so my API key doesn't appear clearly.
So, I want to initiate a request to my server; this request open a channel to the supplier of forex data's API. Then I open a channel to receive this real-time data from my server.
I have thought of the following, but that doesn't work.
var enableWs = require('express-ws');
enableWs(app);
const WebSocket = require('ws');
const WebSocketServer = require('ws').Server;
const URL = 'wss://API_ENDPOINT?api_key=MY_KEY';
app.ws('/ws', function (ws, req) {
const wss = new WebSocket(URL);
let msg = { action: 'subscribe', symbol : 'EUR-USD'};
wss.onopen = (event) => {
wss.send(JSON.stringify(msg));
};
wss.onmessage = function (event) {
jsonData = JSON.parse(event.data);
// now, i want to send the RT data received in jsonData to another channel so I can display the data on the FE
const wssReact = new WebSocketServer({ port: 7100 });
wssReact.send(jsonData);
}
wss.onclose = function (event) {
wss.terminate();
};
});
On the front-end, I expected to get a result when opening a socket as follows :
const socket = new WebSocket('ws://localhost:7100');

ok, I figured how to do what i wanted to achieve. I was close :-) And now it seems obvious lol.
My incoming WS data from the data provider is inconsistent, that's one reason why I couldn't figure the solution earlier.
app.ws('/ws', function (ws, req) {
const wss = new WebSocket(URL);
let msg = { action: 'subscribe', symbol : 'EUR-USD'};
wss.onopen = (event) => {
wss.send(JSON.stringify(msg));
};
wss.onmessage = function (event) {
ws.send(event.data);
}
wss.onclose = function (event) {
wss.terminate();
};
});
I hope it can be of any help to someone else :)

Related

How to trigger websocket send from another function in Node?

It's been a while since I've worked with Node and Websockets. Basically how do I get socket.send() to work from another function is what I'm stuck on.
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', socket => {
socket.on('message', message => {
console.log(`received from a client: ${message}`);
});
socket.send('yo world!');
});
function onMessageHandler (target, context, msg, self) {
client.say(target, response);
server.socket.send(response);
console.log(response);
}
}
How do I get my onMessageHandler to trigger a socket send, this is fail... server.socket.send(response);
Seeing your question i think there is a lack of understanding on how Websockets work. I am assuming you're using https://github.com/websockets/ws
There are two things. First is the WebSocketerver which you've named as server and then an Individual Socket which you've named as socket
Now the thing to understand is socket is not accessible outside server.on() callback The reason for this is there could be 1000 of sockets connected at a given instance and there would be no way to uniquely identify a particular socket you want to send message to.
So ask yourself the question that your application wants to send message to an individual socket to send to everyone who is connected to your server (basically broadcast)
If you want to send to an individual, you will have to uniquely identify the user
this._wss = new WebSocket.Server({
port: ENV_APP_PORT_WS
});
this._wss.on("connection", async (ws: AppWebSocket, req: IncomingMessage) => {
// const ipAddress = req.connection.remoteAddress; // IP Address of User
logger.info(req);
const queryParams = url.parse(req.url, true).query;
let authUser: User;
try {
authUser = await this._authenticateWebSocket(queryParams);
} catch (e) {
// Terminate connection and return...
}
// WS User INIT
ws.isAlive = true;
ws.userId = authUser.id;
ws.uuid = Helpers.generateUUIDV4();
ws.send(JSON.stringify({
type: "connected",
env: ENV
}));
});
The above code will add a property to each socket object that will enable it to uniquely identify a particular user/socket.
While sending =>
onMessageHandler(targetUserId: number, message: string) {
const allSockets = <AppWebSocket[]>Array.from(this._wss.clients.values());
const targetSocket = allSockets.find(w => w.userId === targetUserId);
targetSocket.send(message);
}
If You want to send to all connect users, it's quite easy:
https://github.com/websockets/ws#server-broadcast

Is it possible to send a function from server to a client?

By my yet little understanding on how NodeJS/Javascript works, I believe it should be possible. Is it?
I would like to be able to send a function to a client based on http request, something like this (pseudo short code):
// Server
server.get("/function",()=>{
return function () => {
console.log('test)
}
// client
request ("server/function",(res)=>{
func = res.body
func()
// # result
// test
I have tried to convert to string to send the function from the server and convert back to an object on the client, but it return errors that I couldn't understand why as if I send a simple key value json object it works.
// server
const http = require('http');
const port = 3000;
const hostname = '127.0.0.1';
const server = http.createServer((req, res) => {
if (req.url === '/function') {
res.statusCode = 200;
function func () {
console.log('test')
}
res.end(func.toString())
}
});
server.listen(3000)
// client
const http = require('http');
const httpGet = url => {
return new Promise((resolve, reject) => {
http.get(url, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => resolve(body));
}).on('error', reject);
});
};
const call = async () => {
const body = await httpGet('http://localhost:3000/function')
console.log(`${body}`)
func = JSON.parse(body) // try to redefine function based on the response
}
const func = () => {
console.log('before')
}
func() // run the func defined here
call()
func() // run the function got from the request?
The idea is to have a client that will be able to execute almoust any code without the need of updating the client itself.
I'm not sure why you want to do this, it is really unsafe and you shouldn't have web applications that do this. But here goes:
Let's say you have a function:
function test() {
console.log("test")
}
And as string:
const funcString = test.toString()
// ^ is now "function test() {\n console.log(\"test\")\n }"
You need to first get the function body:
const funcBody = funcString.replace(/^[^{]+{/, '').replace(/}[^}]*$/, '')
// ^ is now "console.log(\"test\")"
Then create a function from it:
const newTest = Function(funcBody)
Now if you call newTest() you will see test printed in your console.
Note that the regular expression I have used to get the function body will not work on all functions. For example, for const test = () => 1 you will need a different regular expression.
I'm not sure this is the best idea.
Historically, the client-server relationship you are describing was inversed through remote procedure calls where the client would invoke a specific endpoint on a remote server. It sounds like your biggest draw towards having the client arbitrarily execute the code was the removal of updating the client code. What happens if you want to make backwards-incompatible changes to the server code? I think you will have better success using versioned API endpoints to execute code server-side based on client-side logic, which you will find many RPC and/or REST frameworks for within npm.
The ideia is to have a client that will be able to execute almoust any code without the need of updating the client itself.
One final thing to keep in mind are the security implications of this. What happens if I find your client and substitute in my malicious JavaScript?

How to get data from a RESTful API with dialogflow

i am struggling a bit with my google assistant action. Right now i am using Dialogflow and Firebase for my webhook. In my code i would like to get data from an API, for example this one: API. I am coding with Node.js by the way. Since Node is asynchronous i do not know how to get the Data. When i try to make an Callback it doesnt work e.g.:
app.intent(GetData, (conv) => {
var test= "error";
apicaller.callApi(answer =>
{
test = answer.people[0].name
go()
})
function go ()
{
conv.ask(`No matter what people tell you, words and ideas change the world ${test}`)
}
For some reason this works when i test it in an other application. With Dialogflow it does not work
I have also tried to use asynch for the function app.intent and tried it with await but this did not work too.
Do you have any idea how i could fix this?
Thank you in advance and best regards
Luca
You need to return Promise like
function dialogflowHanlderWithRequest(agent) {
return new Promise((resolve, reject) => {
request.get(options, (error, response, body) => {
JSON.parse(body)
// processing code
agent.add(...)
resolve();
});
});
};
See following for details:
Dialogflow NodeJs Fulfillment V2 - webhook method call ends before completing callback
If this works in another application then I believe you're getting an error because you're attempting to access an external resource while using Firebases free Spark plan, which limits you to Google services only. You will need to upgrade to the pay as you go Blaze plan to perform Outbound networking tasks.
Due to asynchronicity, the function go() is gonna be called after the callback of callapi been executed.
Although you said that you have tried to use async, I suggest the following changes that are more likely to work in your scenario:
app.intent(GetData, async (conv) => {
var test= "error";
apicaller.callApi(async answer =>
{
test = answer.people[0].name
await go()
})
async function go ()
{
conv.ask(`No matter what people tell you, words and ideas change the world ${test}`)
}
First follow the procedure given in their Github repository
https://github.com/googleapis/nodejs-dialogflow
Here you can find a working module already given in the README file.
You have to make keyFilename object to store in SessionsClient object creation (go at the end of post to know how to genearate credentials file that is keyFileName). Below the working module.
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
//////////////////////////////////////////////////////////////////
const dialogflow = require("dialogflow");
const uuid = require("uuid");
/**
* Send a query to the dialogflow agent, and return the query result.
* #param {string} projectId The project to be used
*/
async function runSample(projectId = "<your project Id>") {
// A unique identifier for the given session
const sessionId = uuid.v4();
// Create a new session
const sessionClient = new dialogflow.SessionsClient({
keyFilename: "<The_path_for_your_credentials_file>"
});
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
// The query to send to the dialogflow agent
text: "new page",
// The language used by the client (en-US)
languageCode: "en-US"
}
}
};
// Send request and log result
const responses = await sessionClient.detectIntent(request);
console.log("Detected intent");
console.log(responses);
const result = responses[0].queryResult;
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
console.log(` Intent: ${result.intent.displayName}`);
} else {
console.log(` No intent matched.`);
}
}
runSample();
Here you can obtain the keyFileName file that is the credentials file using google cloud platform using
https://cloud.google.com/docs/authentication/getting-started
For complete tutorial (Hindi language) watch youtube video:
https://www.youtube.com/watch?v=aAtISTrb9n4&list=LL8ZkoggMm9re6KMO1IhXfkQ

Telegraf and Heroku not sending the first reply on webhook [nodejs]

I am tring to write a simple bot that after some task reply with a text.
The bot offline with app.polling is working perfectly. But when I deploy it on heroku, if I write only one ctx.reply(), no message is sent. If I write it twice, one message is sent on telegram. You can see the code on the snippet below, I included only the necessary code (the log show me with console.log that all the function are working and the final text is ready to be sent, I also commented the code a little to exlpain better the situation).
So this appear strange to me,can anyone explain why?
const Telegraf = require('telegraf');
const request = require('request');
const date = require('date-and-time');
const API_TOKEN = process.env.API_TOKEN || ''; //the api token is into env var
const PORT = process.env.PORT || 3000;
const URL = process.env.URL || 'url of my heroku account';
const app = new Telegraf(API_TOKEN);
app.telegram.setWebhook(`${URL}/bot${API_TOKEN}`);
app.startWebhook(`/bot${API_TOKEN}`, null, PORT);
function getdata(ctx,stazione1,stazione2){
let tempo = gettempo();
let linkget = '....';
var options = {
url: linkget,
headers: {
'Referer':'http://m.trenord.it/site-lite/index.html',
'secret': '...'
}
};
let linkget1 = '...';
var options1 = {
url: linkget1,
headers: {
'Referer':'...',
'secret': '...'
}
};
request(options, function(error, response, body){
request(options1, async function(error1, response1, body1){
let text = await gettext(body,stazione1,stazione2);//return text
let text1 = await gettext(body1,stazione2,stazione1);//return text
let final = await text+"\r\n\r\n"+text1;
console.log(ctx);
//here is the problem, if i write only one reply no message is sent on the bot, but if i wrtite it twice one message is sent.
ctx.replyWithMarkdown(final);
ctx.replyWithMarkdown(final);
});
});
}//getdata
app.command('pl', function(ctx){
getdata(ctx,stazione1,stazione2);
});
NEWS
I want to add some feedback while the server works, so I add a ctx.reply("searching...") after the command right before the function getdata is launched. Now all two messages are sent to telegram. On the previous case is possible that heroku "shut down the webhook" and at the first ctx.reply was wake up and than at the second ctx.reply the message was sent?
I'm using node-telegram-bot-api for node.js/Heroku and this config work well for me:
const options = {
webHook: {
// Port to which you should bind is assigned to $PORT variable
// See: https://devcenter.heroku.com/articles/dynos#local-environment-variables
port: process.env.PORT,
// you do NOT need to set up certificates since Heroku provides
// the SSL certs already (https://<app-name>.herokuapp.com)
// Also no need to pass IP because on Heroku you need to bind to 0.0.0.0
},
};
// okk
// Heroku routes from port :443 to $PORT
// Add URL of your app to env variable or enable Dyno Metadata
// to get this automatically
// See: https://devcenter.heroku.com/articles/dyno-metadata
const url = process.env.APP_URL || 'https://my-bot.herokuapp.com:443';
const bot = new Tg(config.BOT_TOKEN, options);
// This informs the Telegram servers of the new webhook.
// Note: we do not need to pass in the cert, as it already provided
bot.setWebHook(`${url}/bot${config.BOT_TOKEN}`);

Simple communication via MQTT between node apps

Hi I'm really new to MQTT and I've read a lot of posts and blogs about it the last days, yet I seem not to fully understand the different parts needed, such as Broker, Clients.
I want two node apps to communicate with each other via a local mqtt service. As far as I understand, this mqtt service is called broker. I managed it to let 2 node apps communicate via a public broker like this:
app1 (sender)
const mqtt = require('mqtt');
// to be changed to own local server/service
const client = mqtt.connect('http://broker.hivemq.com');
client.on('connect', () => {
let id = 0;
setInterval(() => {
client.publish('myTopic', 'my message - ' + ++id);
}, 3000);
});
app2 (receiver)
const mqtt = require('mqtt');
// to be changed to own local server/service
const client = mqtt.connect('http://broker.hivemq.com');
client.on('connect', () => {
client.subscribe('myTopic');
});
client.on('message', (topic, message) => {
console.log('MSG: %s: %s', topic, message);
});
As this worked, I wanted to move on by replacing the public broker with a private one. After a while I found mqtt-server as a node package.
So I tried the following as a third node app, which is supposed to be the broker for app1 and app2:
server (MQTT broker)
fs = require('fs');
mqttServer = require('mqtt-server');
let subscriptions = [];
let servers = mqttServer(
// servers to start
{
mqtt: 'tcp://127.0.0.1:1883',
mqttws: 'ws://127.0.0.1:1884',
},
// options
{
emitEvents: true
},
// client handler
function (client) {
client.connack({
returnCode: 0
});
client.on('publish', (msg) => {
let topic = msg.topic;
let content = msg.payload.toString();
// this works, it outputs the topic and the message.
// problem is: app2 does not receive them.
// do we have to forward them somehow here?
console.log(topic, content);
});
client.on('subscribe', (sub) => {
let newSubscription = sub.subscriptions[0];
subscriptions.push(newSubscription);
console.log('New subscriber to topic:', newSubscription.topic);
});
});
servers.listen(function () {
console.log('MQTT servers now listening.');
});
Problem
After adjusting the connection-Uris (both to ws://127.0.0.1:1884) in app1 and app2 The server app receives all messages that are published and recognises that someone connected and listens to a specific topic.
But: While the server gets all those events/messages, app2 does not receive those messages anymore. Deducing that, something with this broker must be wrong, since using the public broker everything works just fine.
Any help is appreciated! Thanks in advance.
I can't get mqtt-server to work either, so try Mosca.
Mosca only needs a back end if you want to send QOS1/2 messages, it will work with out one.
var mosca = require('mosca');
var settings = {
port:1883
}
var server = new mosca.Server(settings);
server.on('ready', function(){
console.log("ready");
});
That will start a mqtt broker on port 1883
You need to make sure your clients are connecting with raw mqtt not websockets, so makes sure the urls start mqtt://

Resources