I am writing a Telegram bot using the Telegraf framework in Nodejs, and would like to display the 'bot is typing...' banner while the bot is performing a networking call.
The issue that I have is that my function isn't executed like how it is supposed to be, ie the 'typing' banner appears and disappears after 5 seconds (as per the docs), but the invoice never gets sent until at least 30 seconds later. Assessing the logs also shows that the console logs were executed after the function ends.
Here is my implementation:
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return Promise.all([
ctx.replyWithChatAction('typing'),
sendInvoice(catalogueId, ctx)
])
})
function sendInvoice(catalogueId, ctx) {
console.log('1')
return helper.getItem(catalogueId)
.then((item) => {
console.log('2')
return createInvoice(item, ctx)
.then((invoice) => {
console.log('3')
return ctx.replyWithInvoice(invoice)
})
})
}
The log statements as seen in my Firebase Cloud Functions are like so:
As you can see, the console logs are printed after the function has ended, and are at almost 15 seconds apart. Other attempts:
//Attempt 2, same result as above
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return ctx.replyWithChatAction('typing')
.then(() => sendInvoice(catalogueId, ctx))
})
//Attempt 3, log statements are printed before function ends, but the 'typing' only shows at the end. Need it to show when the networking calls starts
module.exports = (bot) => bot.command('buy', (ctx) => {
let catalogueId = //some regex
return sendInvoice(catalogueId, ctx))
})
function sendInvoice(catalogueId, ctx) {
console.log('1')
return helper.getItem(catalogueId)
.then((item) => {
console.log('2')
return createInvoice(item, ctx)
.then((invoice) => {
console.log('3')
return ctx.replyWithChatAction('typing')
.then(() => ctx.replyWithInvoice(invoice))
})
})
}
My bot is currently deployed on Firebase Cloud Function.
I had a similar problem with a bot running on AWS Lambda. My bot would send the typing chat action correctly to the chat, then all of a sudden the lambda function would quit and none of my other code would be processed.
I'm not an expert but I think that as soon as the handler function you provide to Firebase Cloud Function (or Lambda) returns something to the client that initiated your request, Firebase considers the request complete and shuts down your function invocation. The default webhook callback that Telegraf provides sends a response to the client when your bot replies with a chat action or a message. Consequently, if you export that default webhook reply as your handler (as I did) then your handler will return a response as soon as your bot replies with anything, including a chat action, and the rest of your code may not be processed.
I solved my problem by replacing this:
export const handler = makeHandler(bot.webhookCallback("/"))
with this:
export const handler = async (event, context, callback) => {
const tmp = JSON.parse(event.body) // get data passed to us
await bot.handleUpdate(tmp) // make Telegraf process that data
return callback(null, {
// return something for webhook, so it doesn't try to send same stuff again
statusCode: 200,
body: "",
})
}
(copied from this excellent github comment)
As you can see, instead of returning whenever the bot's webhook callback returns something, I'm now waiting until the update is completely handled and only returning afterward. Thus I can reply with a chat action, wait for my code to do some processing, reply with a message, wrap things up, and only then will my function invocation stop processing.
Related
I am trying to understand a case I am running into with my Node/Express application.
Let's say I have the following which is a function called when a specific route is hit:
async function routeThatDoesStuff(req, res, next) {
doAsyncStuff()
res.status(200).json({ message: 'Completed' })
}
In this case doAsyncStuff() does database operations on resources that are not critical to send back to the user so theoretically we don't need to await it. However, it seems that this operation does not actually complete unless I put the await in front.
My guess is that this has to do with the event loop etc in Node. That potentially because the route is completing before the doAsyncStuff() completes, the function doesn't actually complete because Node terminated it prematurely.
My big question is how Node handles async children function execution when parent functions have already completed?
An example to show that this should work:
const sleep = (milliseconds, cb) => {
return new Promise(resolve => setTimeout(() => {
resolve(cb())
}, milliseconds))
}
async function routeThatDoesStuff(req, res, next) {
sleep(5000, () => console.log('done'))
res.status(200).json({ message: 'Completed' })
}
JSON response is shown immediately, callback is executed after 5 seconds even though the response has been sent.
I built an app in Slack, that on interactions in Slack, will send an HTTP POST request to a URL. That URL is a Firebase Function that is triggered with an HTTP request.
The Firebase Function looks like this...
// process incoming shortcuts
exports.interactions = functions.https.onRequest(async (request, response) => {
response.send();
const payload = JSON.parse(request.body.payload);
functions.logger.log(payload);
if (payload.type === 'shortcut') {
functions.logger.log('Found a shortcut...');
const shortcuts = require('./shortcuts');
await shortcuts(payload);
} else if (payload.type === 'block_actions') {
functions.logger.log('Found a block action...');
const blockActions = require('./blockActions');
await blockActions(payload);
} else if (payload.type === 'view_submission') {
functions.logger.log('Found a view submission...');
const viewSubmissions = require('./viewSubmissions');
await viewSubmissions(payload);
}
functions.logger.log('Done with interactions.');
});
The problem is, is that Firebase is taking 5-10 seconds to respond, and Slack is expecting a response in 3 seconds.
So the app in Slack erroring out.
It turns out while I thought it would be useful to do a response.send() immediately when the function was called so that Slack had its instant response, I was then also inadvertently starting background activities in Firebase.
The line in the above Firebase docs that gave me the biggest clue was:
Background activity can often be detected in logs from individual invocations, by finding anything that is logged after the line saying that the invocation finished.
Which I found here... the function started, and completed, and then the code to open a modal began to be executed...
I then found in the Firebase docs
Terminate HTTP functions with res.redirect(), res.send(), or res.end().
So all I really had to do was move response.send() to the end of the function. Also I had to make sure that I had await statements before my async functions, so that async functions waited to be resolved before executing the final response.send()
// process incoming shortcuts
exports.interactions = functions.https.onRequest(async (request, response) => {
const payload = JSON.parse(request.body.payload);
functions.logger.log(payload);
if (payload.type === 'shortcut') {
functions.logger.log('Found a shortcut...');
const shortcuts = require('./shortcuts');
await shortcuts(payload);
} else if (payload.type === 'block_actions') {
functions.logger.log('Found a block action...');
const blockActions = require('./blockActions');
await blockActions(payload);
} else if (payload.type === 'view_submission') {
functions.logger.log('Found a view submission...');
const viewSubmissions = require('./viewSubmissions');
await viewSubmissions(payload);
}
functions.logger.log('Done with interactions.');
response.send();
});
The modal interaction response times in Slack are much quicker and usable now.
I want to add row to Amazon DDB table from node app deployed on Zeit-now every time I get a post reqest but after sending response to post request. My ddb.putItem stops as pending promise and no errors are logged. I don't understand why.
The app is a slack bot. I get a message from slack api and it fires response by my bot. I want to quickly send 200 to slack to avoid getting message sent again.
I tried different approaches, using EventEmitter or res.on('finish'...
I did test that the module sending update to table in ddb works as when I fire it from command line node it works.
But neither from now dev or deployed now app it does not.
I made a simplified test case in this repository:
https://github.com/halas/now-test-case
Basically entrypoint for node app looks like this:
const send = require('./send.js');
module.exports = (req, res) => {
res.on('finish', send);
res.end(`Hello from Node.js on Now 2.0!`);
console.log('still here'); // this gets logged
};
And the send module:
[ require aws-sdk and set credentials ]
const ddb = new AWS.DynamoDB({apiVersion: '2012-08-10'});
module.exports = () => {
let params = {
TableName: 'now-test-case',
Item: {
'id': { N: String(Date.now()) },
'message': { S: 'hello World' },
}
};
console.log('hello'); //we get here
try {
console.log('try'); //we get here
const data = ddb.putItem(params).promise();
console.log(data); //this is pending promise now
data
.then((data) => {console.log(data)})
.catch((error) => {console.log(error)});
// and it never gets resolved or rejected
} catch(e) {
console.log(e); //and no error catched here either
}
};
As suggested by Rob Evans on Zeit Spectrum chat, I prepared the version
with async-await (on branch in test repo), but results are the same.
I would expect to get the update on DynamoDB (resolved promise).
While I get only pending promise and now resolve or reject.
I have the following firebase cloud function
exports.sendToUser = functions.region('europe-west1').https.onRequest((request, response) => {
console.log('start');
const receiverUid = request.body.receiverId;
const requestingUid = getUserIdFromRequest(request);
// Notification details.
const payload = {
"data": {
"type": request.body.type,
"mainData": request.body.mainData
}
};
const options = {
"priority": "high"
}
console.log('check');
requestingUid.then(uid => {
console.log('complete');
payload.data.senderId = uid;
sendMessageToUser(receiverUid, payload, options);
response.status(200).send();
}).catch(err => {
console.error(err);
response.status(401).send(err);
});
});
My problem with this function is that it prints "start" and "check" all fine but doesn't print the "complete" or the "receiver" nor does it execute the "sendMessageToUser" function. However, the status 200 is responded and the function terminates which I can see in the logs in the developer console and on the client device.
Also, when replacing line response.status(200).send(); with response.status(400).send(); or some other response code the correct status (400) is sent but the logs are not printed and the "sendMessageToUser" function not executed.
From time to time, however, the function is properly executed so I'm assuming that the function is somehow prematurely terminated. But how can the correct response code be sent then? I'm aware of the necessity to return a promise from asynchronus functions but as this is a http-triggered (synchronus) function I wouldn't know how to handle this.
Besides, yesterday was the first time this particular error occured. Before that, it worked fine. So can anybody explain what is happening here?
I'm new to redux and programming in general and am having trouble wrapping my head around certain unit testing concepts.
I have some async actions in redux, which involve calls to a third party API (from the 'amazon-cognito-identity-js' node module).
I have wrapped the external API call in a promise function, and I call this function from the 'actual' action creator. So for testing I just want to stub the result of externalAWS() function so that I can check that the correct actions are being dispatched.
I'm using redux-thunk for my middleware.
import { AuthenticationDetails,
CognitoUser
} from 'amazon-cognito-identity-js';
export function externalAWS(credentials) {
//This is required for the package
let authenticationDetails = new AuthenticationDetails(credentials);
let cognitoUser = new CognitoUser({
//Construct the object accordingly
})
return new Promise ((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: result => {
resolve(result);
},
onFailure: error => {
reject(error)
}
})
}
}
export function loginUser(credentials) {
//return function since it's async
return dispatch => {
//Kick off async process
dispatch(requestLogin());
externalAWS(credentials)
.then((result) => {
dispatch(receiveLogin(result.getAccessToken().getJwtToken(), credentials.username))
})
.catch((error) => {
dispatch(failedLogin(error.message, etc))
})
}
}
I don't have any test code yet because I am really not sure how to approach this. All the examples deal with mocking a HTTP request, which I know is
what this boils down to, so am I supposed to inspect the HTTP requests in my browser and mock them out directly?
It's further complicated by the fact that the second argument of authenticateUser is not even a plain callback, but an object with callbacks as it's values.
Can anyone offer some advice on whether my intention in unit testing the async function is correct, and how I should approach it? Thank you.
Edit: I'm testing in Jest.
Edit2: Request Headers
First POST request,
Second POST request
Edit3: Split the function, trying my best to isolate the external API and create something that is 'easily mock/stub-able'. But still running into issues of how to properly stub this function.
Redux thunk gives you the ability to dispatch future actions within the context of a main action that kicks off the process. This main action is your thunk action creator.
Therefore tests should focus on what actions are dispatched within your thunk action creator according to the outcome of the api request.
Tests should also look at what arguments are passed to your action creators so that your reducers can be informed about the outcome of the request and update the store accordingly.
To get started with testing your thunk action creator you want to test that the three actions are dispatched appropriately depending on whether login is successful or not.
requestLogin
receiveLogin
failedLogin
Here are some tests I wrote for you to get started using Nock to intercept http requests.
Tests
import nock from 'nock';
const API_URL = 'https://cognito-idp.us-west-2.amazonaws.com/'
const fakeCredentials = {
username: 'fakeUser'
token: '1234'
}
it('dispatches REQUEST_LOGIN and RECEIVE_LOGIN with credentials if the fetch response was successful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(200, Promise.resolve({"token":"1234", "userName":"fakeUser"}) })
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'RECEIVE_LOGIN', token: '1234', userName: 'fakeUser'});
})
});
it('dispatches REQUEST_LOGIN and FAILED_LOGIN with err and username if the fetch response was unsuccessful', () => {
nock(API_URL)
.post( ) // insert post request here e.g - /loginuser
.reply(404, Promise.resolve({"error":"404", "userName":"fakeUser"}))
return store.dispatch(loginUser(fakeCredentials))
.then(() => {
const expectedActions = store.getActions();
expect(expectedActions.length).toBe(2);
expect(expectedActions[0]).toEqual({type: 'REQUEST_LOGIN'});
expect(expectedActions[1]).toEqual({type: 'FAILED_LOGIN', err: '404', userName: 'fakeUser'});
})
});
So I figured it out in the end.
First, I had to require() the module into my test file (as opposed to ES6 import). Then I removed the promise for now since it was adding a layer of complexity and combined everything into one function, let's call it loginUser(). It is a redux async action, that dispatches one action upon being called, and then a success or failure action depending on the result of the API call. See above for what the API call looks like.
Then I wrote the test as follows:
const CognitoSDK = require('/amazon-cognito-identity-js')
const CognitoUser = CognitoSDK.CognitoUser
//Set up the rest of the test
describe('async actions', (() => {
it('should dispatch ACTION_1 and ACTION_2 on success', (() => {
let CognitoUser.authenticateUser = jest.fn((arg, callback) => {
callback.onSuccess(mockResult)
})
store.dispatch(loginUser(mockData))
expect(store.getActions()).toEqual([{ACTION_1}, {ACTION_2}])
}))
}))
So basically once requiring the module in, I mocked it in Jest and did a mock implementation too, so that I could access the onSuccess function of the callback object.