Firestore Push Notification "time out" error Notification doesn't always get sent

Viewed 156

I am calling a function from my app that sends a notification out to a specific user on the app. The notification gets sent successfully much of the time but a good amount of times it does not get sent. When it does not get sent I check the logs to see

Function execution took 60003 ms, finished with status: 'timeout'

I have tried playing with my promises / async await but have had no luck as I suspect that is where the issue lies.

Here is what my cloud code looks like now

exports.sendNotification = functions.https.onRequest(async (request, response) => {

    if (request.method !== "POST") {
        response.status(400).send("Send it using post request");
        return;
    }

    var toUid = request.body.toUid
    var fcm = request.body.fcm
    var fromUid = request.body.fromUid
    var type = request.body.type
    var fromName = request.body.fromName
    var messageText = request.body.message

    if (toUid === "" || fromUid === "" || fcm === "") {
        response.status(400).send("Parameter is missing!");
        return;
    }

    // common data for both platforms
    const notification = {
     title: fromName,
     body: messageText,
    }
    const fcmToken = fcm

    // ios specific headers
    const apns = {
      headers: {
        "apns-collapse-id": 'toUid'
      },
      payload: {
        aps: {
          sound: 'default'
        },
        "data": {
          "fromUid": fromUid,
          "type": type
        }
      }
    }

    // final message
    const message = {
     token: fcmToken,
     notification: notification,
     apns: apns,
    }

    // send message
    try {
      return await admin.messaging().send(message);
      response.status(200).send("Done");
    } catch(e) {
      console.log('Error sending message:', e);
    }
});

I call the function from the app as follows

         AF.request("https://myproject.net/sendNotification", method: .post, parameters: parameters, encoding: JSONEncoding.default)
         .responseString { response in
             print(response)
            DispatchQueue.main.async {
                completion("done")
            }
         }

I have seen other stackoverflow questions of similar questions where it was suggested to use .post and JSONEncoding.default and so that is what I have now.

2 Answers

From https://firebase.google.com/docs/functions/http-events#terminate_http_functions :

In your catch block there's no call to .send() or any equivalent, so from the link above :

Always end an HTTP function with send(), redirect(), or end(). Otherwise, your function might continue to run and be forcibly terminated by the system. See also Sync, Async and Promises.

Also it's better to wrap your entire code in the onRequest callback in a try/catch.

Here's the code with suggested fixes :

exports.sendNotification = functions.https.onRequest(async (request, response) => {

  try {

    if (request.method !== "POST") {
      response.status(400).send("Send it using post request");
      return;
    }

    var toUid = request.body.toUid
    var fcm = request.body.fcm
    var fromUid = request.body.fromUid
    var type = request.body.type
    var fromName = request.body.fromName
    var messageText = request.body.message

    if (toUid === "" || fromUid === "" || fcm === "") {
      response.status(400).send("Parameter is missing!");
      return;
    }

    // common data for both platforms
    const notification = {
      title: fromName,
      body: messageText,
    }
    const fcmToken = fcm

    // ios specific headers
    const apns = {
      headers: {
        "apns-collapse-id": 'toUid'
      },
      payload: {
        aps: {
          sound: 'default'
        },
        "data": {
          "fromUid": fromUid,
          "type": type
        }
      }
    }

    // final message
    const message = {
      token: fcmToken,
      notification: notification,
      apns: apns,
    }

    // send message
    await admin.messaging().send(message); // do not return here
    response.status(200).send("Done");

  } catch (e) {
    response.status(500).send(e) // note the .send() wich terminates the request
  }
});

I'm no node expert by any means, but played with Firebase Messaging for a while and came up with a working async solution to send a notification based on data creation in the realtime db. The only other difference aside from the trigger I really see is I'm not sending the APNS specific header yet...

I'm also remembering now I was having an intermittent timeout issue when I tried using the async messaging.send which is why I'm using promises there.

exports.onMessageCreate = functions.database
.ref('/users/{userId}/notifications/unread/{notificationId}')
.onCreate(async (snapshot, context) => {
    
    const message = snapshot.val()
    const category = message.category
    const title = category + ": " + message.title
    const text = message.text
    const forUserId = message.forUserId
    const date = message.date
    
    const token = await getUserToken(forUserId)
    
    if (token == null) {
        throw new functions.https.HttpsError('unavailable', 'The token is nil, unable to send message')
    }
    
    const notification = {
        notification: {
            title: title,
            body: text
        },
        data: {
            category: category,
            title: message.title,
            text: text,         
            forUserId: forUserId,
            date: date.toString()
        },
        token: token
    }
    
    return admin.messaging().send(notification)
    .then((response) => {
        console.log('Successfully sent notification:', response)
    })
    .catch((error) => {
        console.log('Error sending notification:', error)
    })
    
})
Related