I have been trying to convert my existing Node JS code from function callback to Async Await, because a new SDK came out and callbacks are deprecated. These are the related codes. The code is communicating and publishing to a mqtt broker.
First here I call the ToggleX method
super.controlToggleX(channel, value, (err, res) => {
if (err) {
this.log(`Toggle Response: err: ${err}`);
}
this.log(`Toggle Response: res: ${JSON.stringify(res)}`);
});
After that in the controlToggleX method I set the payload for the message and make the publlish to the broker.
controlToggleX(channel, onoff, callback) {
const payload = { togglex: { channel, onoff: onoff ? 1 : 0 } };
return this.publishMessage('SET', 'Appliance.Control.ToggleX', payload, callback);
}
In the publishMessage I compile the message to the broker and publish it. After publishing I waiting for the response and to keep track of the ongoing messages I create the waitingMessageIds array.
publishMessage(method, namespace, payload, callback) {
this.clientResponseTopic = `/app/${this.userId}-${appId}/subscribe`;
const messageId = crypto.createHash('md5').update(generateRandomString(16)).digest('hex');
const timestamp = Math.round(new Date().getTime() / 1000); // int(round(time.time()))
const signature = crypto.createHash('md5').update(messageId + this.key + timestamp).digest('hex');
const data = {
header: {
from: this.clientResponseTopic,
messageId,
method,
namespace,
payloadVersion: 1,
sign: signature,
timestamp,
},
payload,
};
this.client.publish(`/appliance/${this.uuid}/subscribe`, JSON.stringify(data));
if (callback) {
this.waitingMessageIds[messageId] = {};
this.waitingMessageIds[messageId].callback = callback;
this.waitingMessageIds[messageId].timeout = setTimeout(() => {
// this.log('TIMEOUT');
if (this.waitingMessageIds[messageId].callback) {
this.waitingMessageIds[messageId].callback(new Error('Timeout'));
}
delete this.waitingMessageIds[messageId];
}, 20000);
}
this.emit('rawSendData', data);
return messageId;
}
When a new message comes from the broker I check the waitingMessageIds array, the messageId is in the array? If yes I delete the Timer and process the message with the callback coming from the publishing.
this.client.on('message', (topic, message) => {
if (!message) return;
// message is Buffer
try {
message = JSON.parse(message.toString());
} catch (err) {
this.emit('error', `JSON parse error: ${err}`);
return;
}
if (message.header.from && !message.header.from.includes(this.uuid)) return;
if (this.waitingMessageIds[message.header.messageId]) {
if (this.waitingMessageIds[message.header.messageId].timeout) {
clearTimeout(this.waitingMessageIds[message.header.messageId].timeout);
}
this.waitingMessageIds[message.header.messageId].callback(null, message.payload || message);
delete this.waitingMessageIds[message.header.messageId];
} else if (message.header.method === 'PUSH') {
const namespace = message.header ? message.header.namespace : '';
this.log('Found message');
this.emit('data', namespace, message.payload || message);
}
this.emit('rawData', message);
});
mqtt package is working with callback, but the async-mqtt is returning Promise so it is going to be good for me.
I was successfull to publish with it, and after that point I put the messageId to the array and start a timer, but when the reply came i was not been able to proocess the waitingMessageIds and return to the original point (super.controlToggleX).
Could somebody please help me. Thank
Edit:
I tried to rewrite PublishMessage with async-mqtts and it looks like this:
async publishMessage(method, namespace, payload) {
.
.
.
try {
await this.client.publish(`/appliance/${this.uuid}/subscribe`, JSON.stringify(data));
} catch (err) {
return new Error(err);
}
this.waitingMessageIds[messageId] = {};
// this.waitingMessageIds[messageId].callback = callback;
this.waitingMessageIds[messageId].timeout = setTimeout(() => {
// this.log('TIMEOUT');
if (this.waitingMessageIds[messageId].callback) {
this.waitingMessageIds[messageId].callback(new Error('Timeout'));
}
delete this.waitingMessageIds[messageId];
}, 20000);
this.emit('rawSendData', data);
return messageId;
}
Because with the await publish waits for the response I do not need check if it is a callback, i just put the messageId into the waitingmessageIds array.
When I process the incoming message in this.client.on('message' I don not know how to change this -> this.waitingMessageIds[message.header.messageId].callback(null, message.payload || message);
Related
I need your help to mock a twilio service which sends a message, using jest to mock the service
I have the next code:
import { SQSEvent } from "aws-lambda";
import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanager";
export async function sendSms(event: SQSEvent, data: GetSecretValueResponse) {
const secrets = JSON.parse(data.SecretString);
const accountSid = secrets.TWILIO_ACCOUNT_SID;
const authToken = secrets.TWILIO_AUTH_TOKEN;
const twilioNumber = secrets.TWILIO_PHONE_NUMBER;
if (accountSid && authToken && twilioNumber) {
//Create a Twilio Client
const client = new Twilio(accountSid, authToken);
//Loop into al records of the event, every record is every message sent from Sqs
for (const record of event.Records) {
const body = JSON.parse(record.body);
const userNumber = "+" + body.number;
//SendMessage function
try {
const message = client.messages.create({
from: twilioNumber,
to: userNumber,
body: body.message,
});
return message;
} catch (error) {
return `Failed to send sms message. Error Code: ${error.errorCode} / Error Message: ${error.errorMessage}`;
}
}
} else {
return "You are missing one of the variables you need to send a message";
}
}
The I call this function from my index:
import { SQSEvent } from "aws-lambda";
import { sendSms } from "./services/sendSms/sendSms";
import { getSecret } from "./services/obtainSecrets/getSecret";
import { SecretsManager } from "aws-sdk";
export const lambdaHandler = async (event: SQSEvent) => {
try {
const obtainedSecret = await getSecret()
.then((credentials: SecretsManager.GetSecretValueResponse) => {
return credentials;
})
.catch(error => {
return error;
});
const response = sendSms(event, obtainedSecret)
.then(response => {
return response;
})
.catch(error => {
return error;
});
return {
message: "OK " + obtainedSecret + response,
code: 200,
};
} catch (error) {
throw new Error(error);
}
};
I have already make some tests, but them always makes a connection with Twilio api(requiring the real token, sid,etc), and I need to mock the Twilio service, so the function I call in my test.ts doesn't connects to internet.
import { Twilio } from "twilio";
import { MessageInstance } from "twilio/lib/rest/api/v2010/account/message";
import { sendSms } from "../../services/sendSms/sendSms";
//mock Twilio library and sendSms service
jest.mock("twilio");
jest.mock("../../services/sendSms/sendSms");
const smsMessageResultMock: Partial<MessageInstance> = {
status: "sent",
sid: "AC-lorem-ipsum",
errorCode: undefined,
errorMessage: undefined,
};
describe("SMS Service", () => {
describe("Send Message", () => {
it("Should fail", async () => {
// update smsMessageResultMock to simulate a faled response
const smsMessageMock = {
...smsMessageResultMock,
status: "failed",
errorCode: 123,
errorMessage: "lorem-ipsum",
};
// simulated response of secret management
let data = {
ARN: "arn:aws:secretsmanager:us-west-2:123456789012:secret:MyTestDatabaseSecret-a1b2c3",
Name: "MyTestDatabaseSecret",
SecretString:
'{"TWILIO_ACCOUNT_SID": "ACTWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN": "TWILIO_AUTH_TOKEN","TWILIO_PHONE_NUMBER": "TWILIO_PHONE_NUMBER"}',
VersionId: "EXAMPLE1-90ab-cdef-fedc-ba987SECRET1",
VersionStages: ["AWSPREVIOUS"],
};
// simulated response of SqsEvent
let event = {
Records: [
{
messageId: "19dd0b57-b21e-4ac1-bd88-01bbb068cb78",
receiptHandle: "MessageReceiptHandle",
body: '{"message": "Hello world","number": "(506)88888888"}',
attributes: {
ApproximateReceiveCount: "1",
SentTimestamp: "1523232000000",
SenderId: "123456789012",
ApproximateFirstReceiveTimestamp: "1523232000001",
},
messageAttributes: {},
md5OfBody: "{{{md5_of_body}}}",
eventSource: "aws:sqs",
eventSourceARN: "arn:aws:sqs:us-east-1:123456789012:MyQueue",
awsRegion: "us-east-1",
},
],
};
// simulate tokens for Twilio
const accountSid = "ACfjhdskjfhdsiuy876hfijhfiudsh";
const authToken = "fjfuewfiuewfbodfiudfgifasdsad";
//create client with mocked Twilio
const client = new Twilio(accountSid, authToken);
//call messages.create of Twilio client, and give it the expected result created
client.messages.create = jest
.fn()
.mockResolvedValue({ ...smsMessageMock });
console.log(await sendSms(event, data));
//expectes the function sendSms(event, data) to throw an error
await expect(sendSms(event, data)).rejects.toThrowError(
`Failed to send sms message. Error Code: ${smsMessageMock.errorCode} / Error Message: ${smsMessageMock.errorMessage}`
);
});
});
});
(event and data are simulated responses of SqsEvent and GetSecretValueResponse)
The problem is that when I run the npm test it throws me an error of Twilio's authentication, an it is because I'm passing self created tokens.
Expected substring: "Failed to send sms message. Error Code: 123 / Error Message: lorem-ipsum"
Received message: "Authentication Error - invalid username"
at success (node_modules/twilio/lib/base/Version.js:135:15)
at Promise_then_fulfilled (node_modules/q/q.js:766:44)
at Promise_done_fulfilled (node_modules/q/q.js:835:31)
at Fulfilled_dispatch [as dispatch] (node_modules/q/q.js:1229:9)
at Pending_become_eachMessage_task (node_modules/q/q.js:1369:30)
at RawTask.Object.<anonymous>.RawTask.call (node_modules/asap/asap.js:40:19)
at flush (node_modules/asap/raw.js:50:29)
So what I suppose is that the test is connecting to internet and calling Twilio's api.
I appreciate if you could help me.
I think what you want to do is mock the class returned by the module, using jest.mock('twilio', mockImplementation) and in mockImplementation return a function to act as a constructor that will take your account SID and auth token arguments and then return a mockClient implementation, which in this case needs to return an object which has a messages property, which in turn is an object with a create property that is a mock function.
It's probably easier to just show the code.
const mockClient = {
messages: {
create: jest.fn().mockResolvedValue({ ...smsMessageMock });
}
};
jest.mock("twilio", () => {
return function(accountSid, authToken) {
return mockClient;
}
});
I'm sending push messages using FCM through Firebase Functions. The messages are being sent properly, but I'm getting the 408 time-out error after the message is sent. I'm suspecting it might have to do with the unregistered tokens not being cleaned up because:
if I were to send another message to the same device, the same timeout occurs and
the only error message I get from the Firebase log is Function execution took 60002 ms, finished with status: 'timeout'.
exports.sendMessage = functions.https.onRequest(async (request, response) => {
const {
sender,
recipient,
content,
docID
} = request.body
functions.logger.log(
"docID:",
docID,
);
// Get the list of device notification tokens.
let deviceTokens; let ref;
try {
ref = admin.firestore().collection("deviceToken").doc(recipient);
const doc = await ref.get();
if (!doc.exists) {
console.log("No such document!");
response.status(500).send(e)
} else {
console.log("doc.data():", doc.data());
deviceTokens = doc.data().token;
}
} catch (e) {
response.status(500).send(e)
}
let senderProfile;
try {
senderProfile = await admin.auth().getUser(sender);
console.log("senderProfile", senderProfile);
} catch (e) {
console.log(e);
response.status(500).send(e)
}
// Notification details.
let payload = {
notification: {
title: senderProfile.displayName,
body: content,
sound: "default",
},
data: {
uid: senderProfile.uid,
displayName: senderProfile.displayName,
docID,
messageType: "status"
}
};
functions.logger.log(
"deviceTokens", deviceTokens,
"payload", payload,
);
// Send notifications to all tokens.
const messageResponse = await admin.messaging().sendToDevice(deviceTokens, payload);
// For each message check if there was an error.
messageResponse.results.forEach((result, index) => {
const error = result.error;
if (error) {
functions.logger.error(
"Failure sending notification to",
deviceTokens[index],
error,
);
// Cleanup the tokens who are not registered anymore.
if (error.code === "messaging/invalid-registration-token" ||
error.code === "messaging/registration-token-not-registered") {
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
console.log("updatedTokens", updatedTokens);
ref.update({
token: updatedTokens,
})
.catch(function(e) {
console.error("Error removing tokens", e);
response.status(500).send(e)
});
}
}
});
response.status(200)
});
I'm unsure why the following isn't cleaning up the unregistered tokens:
const updatedTokens = deviceTokens.filter((token) => token !== deviceTokens[index]);
ref.update({
token: updatedTokens,
})
You always have to end HTTP functions with response.status(200).send() or response.status(200).end(). In the above function, you have response.status(200) so you have to end it either with response.status(200).send() or response.status(200).end(). Please check the documentation if it helps.
I am trying to build a Whatsapp chatbot using Node.JS and am running into a bit of trouble in receiving the Whatsapp message from Twilio. On checking the debugger, I get a Bad Gateway error, ie. Error 11200: HTTP Retrieval Failure. The message is getting sent, and ngrok shows the post request, however, dialogflow does not receive the request. On terminal, the error is showing UnhandledPromiseRejectionWarning: Error: 3 INVALID ARGUMENT: Input text not set. I'm not sure if it's because the message is not in JSON format. Please help!
This is the app.post function:
app.post('/api/whatsapp_query', async (req, res) =>{
message = req.body;
chatbot.textQuery(message.body, message.parameters).then(result => {
twilio.sendMessage(message.from, message.to, result.fulfillmentText).then(result => {
console.log(result);
}).catch(error => {
console.error("Error is: ", error);
});
return response.status(200).send("Success");
})
});
And this is the sendMessage function I've imported:
const config = require('./config/keys');
const twilioAccountID = config.twilioAccountID;
const twilioAuthToken = config.twilioAuthToken;
const myPhoneNumber = config.myPhoneNumber;
const client = require('twilio')(twilioAccountID,twilioAuthToken);
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
And this is the textQuery function I've imported:
textQuery: async function(text, parameters = {}) {
let self = module.exports;
const request = {
session: sessionPath,
queryInput: {
text: {
text: text,
languageCode: config.dialogFlowSessionLanguageCode
},
},
queryParams: {
payload: {
date: parameters
}
}
};
let responses = await sessionClient.detectIntent(request);
responses = await self.handleAction(responses)
return responses[0].queryResult;
},
Twilio developer evangelist here.
The issue is that you are not passing the correct message body from the incoming WhatsApp message to your textQuery function.
First, you should make sure that you are treating the incoming webhook from Twilio as application/x-www-form-urlencoded. If you are using body-parser, ensure you have urlencoded parsing turned on.
app.use(bodyParser.urlencoded());
Secondly, the parameters that Twilio sends start with a capital letter. So your code currently gets message = req.body and then uses message.body. But it should be message.Body.
Those two points should sort you out.
One final thing though. The Twilio Node.js library will return a Promise if you do not pass a callback function. So you don't need to create a Promise here:
module.exports = {
sendMessage: async function(to, from, body) {
return new Promise((resolve, reject) => {
client.messages.create({
to,
from,
body
}).then(message => {
resolve(message.sid);
}).catch(error => {
reject(error);
});
});
}
}
You can just return the result of the call to client.messages.create
module.exports = {
sendMessage: async function(to, from, body) {
return client.messages.create({ to, from, body });
}
}
Hope this helps.
I am trying to set up a slack bot using javascript and a few helpful libraries.
All it does is run a postMessageToChannel Method when a user of the channel mentions a certain keyword " help"
My issue is when the runHelp() function is called it doesn't just post one message to the slack #channel but many. Maybe i am missing something here that someone can help me figure out.
Thanks,
Here's the js:
const SlackBot = require('slackbots');
const axios = require('axios')
const dotenv = require('dotenv')
dotenv.config()
const bot = new SlackBot({
token: `${process.env.BOT_TOKEN}`,
name: 'helpit-bot'
});
// Start Handler
bot.on('start', () => {
const params = {
icon_emoji: ':nerd_face:'
}
bot.postMessageToChannel('slack-bot', 'HELP IS ON THE WAY', params);
})
// Error Handler
bot.on('error', (err) => {
console.log(err);
});
// Message Handler
bot.on('message', (data) => {
if(data.type !== 'message') {
return;
}
handleMessage(data.text);
return;
})
// Response Handler
function handleMessage(message) {
if(message.includes(' help')) {
runHelp();
} else {
// Run something else
}
}
// Show Help
function runHelp() {
const params = {
icon_emoji: ':question:'
}
bot.postMessageToChannel('slack-bot', 'This is an automated help message', params);
}
Result:
Created an infinite loop because "This is an automated help message" includes the text "help" which triggers the bot. -.-
I am currently programming a socket server in node.js using the json-socket module, and am having some trouble.
Currently when a client connects to the server they send a command in a json object with some data for instance a login would look like this
{ type : 'login', data : { username: 'Spero78' } }
to deal with these requests i have a commands object
var commands = {
'login' : authUser,
'register' : userRegister
}
and these functions are called by server.on
server.on('connection', function(socket) {
console.log('Client Connected');
socket = new JsonSocket(socket);
socket.on('message', function(message) {
if(message.type != undefined) {
if(commands[message.type]){
var response = commands[message.type].call(this, message.data);
if(response != undefined){
console.log(response);
socket.sendMessage(response);
} else {
console.log("No Response!");
}
} else {
console.log('Unexpected Command!');
}
}
});
});
The functions return javascript objects but the response var is always undefined and the "No Response!" message is always printed
Here is the authUser function
function authUser(data){
console.log('Auth: ' + data.username);
database.query('SELECT * FROM players WHERE username = ?', [data.username], function(err, results) {
if(results.length < 1){
console.log('Bad Login!');
var response = {
type : 'badlogin',
data : {
//...
}
}
return response;
}
var player = results[0];
var response = {
type : 'player',
data : {
//...
}
}
return response;
});
}
Is there a better way of doing this? or am i missing something that is causing the objects to not return
database.query() is asynchronous, so when you call this function, nodejs don't wait the response of the callback to go to next instruction.
So the value tested on your condition is not the return in the callback, but of the whole authUser function, that's why it's always undefined.
You probably need tor refactor your code.