0
votes

I'm currently trying to build a firebase cloud function (using express) where: - I check if a use exists in database - Sends a message to the telegram API depending on whether it exists or not

The issue is, when I try to run the function, Firebase logs is able to get the console.log telling me if the user exists, but will not send to telegram. The error log says:

[2020-02-15T10:41:34.568Z] @firebase/database: FIREBASE WARNING: Exception was thrown by user callback. Error: Can't set headers after they are sent. at validateHeader (_http_outgoing.js:491:11) at ServerResponse.setHeader (_http_outgoing.js:498:3) at ServerResponse.header (/srv/node_modules/express/lib/response.js:771:10) at ServerResponse.send (/srv/node_modules/express/lib/response.js:170:12) at ServerResponse.json (/srv/node_modules/express/lib/response.js:267:15) at ServerResponse.send (/srv/node_modules/express/lib/response.js:158:21) at firebase.database.ref.child.once.snapshot (/srv/index.js:59:40) at onceCallback (/srv/node_modules/@firebase/database/dist/index.node.cjs.js:4933:51) at /srv/node_modules/@firebase/database/dist/index.node.cjs.js:4549:22 at exceptionGuard (/srv/node_modules/@firebase/database/dist/index.node.cjs.js:698:9)

Could anyone please help? Thank you!

 app.post("/", async (req, res) => {

    const isTelegramMessage =
        req.body &&
        req.body.message &&
        req.body.message.chat &&
        req.body.message.chat.id &&
        req.body.message.from &&
        req.body.message.from.first_name &&
        req.body.update_id;
    const user_id = req.body.message.from.id
 firebase.initializeApp(firebaseConfig);
    let myUser;
    const chat_id = req.body.message.chat.id;
    const {
        first_name
    } = req.body.message.from;
    // Check User Exists
    firebase
        .database()
        .ref("/telegramUsers")
        .child(req.body.message.from.id)
        .once("value", snapshot => {
            if (snapshot.exists()) {
                myUser = true;
                console.log("exists!", myUser);
                return res.status(200).send({
                    method: "sendMessage",
                    chat_id,
                    text: `Welcome Back ${first_name}`
                });
            } else {
                myUser = false;
                console.log("does not exist!");
                return res.status(200).send({
                    method: "sendMessage",
                    chat_id,
                    text: `Hello ${first_name}`
                });
            }
        });

    return res.status(200).send({
        status: "not a telegram message"
    });
});
1
you are returning before message is sent and then returning again - Uchit Kumar
write console.log() just before the last return you will understand the problem. You will get two log- one "exists" and other that you will write - Uchit Kumar

1 Answers

0
votes

As others have commented, you're returning and writing a response to the caller twice. Since send starts writing the body of the HTTP response, you can't call status (or even send) after you've already called it on res before.

In code that'd look something like this:

 app.post("/", async (req, res) => {

    const isTelegramMessage =
        req.body &&
        req.body.message &&
        req.body.message.chat &&
        req.body.message.chat.id &&
        req.body.message.from &&
        req.body.message.from.first_name &&
        req.body.update_id;
    const user_id = req.body.message.from.id
    firebase.initializeApp(firebaseConfig);
    let myUser;
    const chat_id = req.body.message.chat.id;
    const {
        first_name
    } = req.body.message.from;
    // Check User Exists
    if (isTelegramMessage) {
      return firebase
        .database()
        .ref("/telegramUsers")
        .child(req.body.message.from.id)
        .once("value")
        .then(snapshot => {
            if (snapshot.exists()) {
                myUser = true;
                console.log("exists!", myUser);
                return res.status(200).send({
                    method: "sendMessage",
                    chat_id,
                    text: `Welcome Back ${first_name}`
                });
            } else {
                myUser = false;
                console.log("does not exist!");
                return res.status(200).send({
                    method: "sendMessage",
                    chat_id,
                    text: `Hello ${first_name}`
                });
            }
          });
    } else {
      return res.status(200).send({
        status: "not a telegram message"
      });
   }
});

The changes:

  • Now only checks if the user exists if isTelegramMessage is true.
  • Now returns the result from the database read operation.
  • Use once().then(), so that the return res.status().send()... bubbles up. This ensures that Cloud Functions will not terminate the function before both the database load and the sending of the response are done.

Though the second and third bullets are not strictly needed for HTTPS triggered Cloud Functions (as those terminate when you send a response), I still recommend to use them, to make it easier to port/copy-paste the code to Cloud Functions types that are triggered by other events.