'use strict'; const functions = require('firebase-functions'); const admin = require('firebase-admin'); admin.initializeApp(); var db = admin.firestore(); const MAX_NOTIFICATIONS_PER_DAY = 150; exports.sendPushNotification = functions.https.onRequest(async (req, res) => { console.log('Received payload', req.body); var today = getToday(); var token = req.body.push_token; var ref = db.collection('rateLimits').doc(today).collection('tokens').doc(token); var payload = { notification: { body: req.body.message }, android: { priority: 'HIGH', notification: { sound: 'default', icon: 'mini_icon', channel_id: 'ha_notify' } }, token: token, }; if(req.body.title) { payload.notification.title = req.body.title; } if(req.body.data) { if(req.body.data.android) { payload.android = req.body.data.android; } if(req.body.data.apns) { payload.apns = req.body.data.apns; } if(req.body.data.data) { payload.data = req.body.data.data; } if(req.body.data.webpush) { payload.webpush = req.body.data.webpush; } } console.log('Notification payload', JSON.stringify(payload)); var docExists = false; var docData = { deliveredCount: 0, errorCount: 0, totalCount: 0, }; try { let currentDoc = await ref.get(); docExists = currentDoc.exists; if(currentDoc.exists) { docData = currentDoc.data(); } } catch(err) { console.error('Error getting document!', err); return handleError(res, 'getDoc', err); } if(docData.deliveredCount > MAX_NOTIFICATIONS_PER_DAY) { return res.status(429).send({ errorType: 'RateLimited', message: 'The given target has reached the maximum number of notifications allowed per day. Please try again later.', target: token, rateLimits: getRateLimitsObject(docData), }); } docData.totalCount = docData.totalCount + 1; var messageId; try { messageId = await admin.messaging().send(payload); docData.deliveredCount = docData.deliveredCount + 1; } catch(err) { docData.errorCount = docData.errorCount + 1; await setRateLimitDoc(ref, docExists, docData, res); return handleError(res, 'sendNotification', err); } console.log('Successfully sent message:', messageId); await setRateLimitDoc(ref, docExists, docData, res); return res.status(201).send({ messageId: messageId, sentPayload: payload, target: token, rateLimits: getRateLimitsObject(docData), }); }); async function setRateLimitDoc(ref, docExists, docData, res) { try { if(docExists) { console.log('Updating existing doc!'); await ref.update(docData); } else { console.log('Creating new doc!'); await ref.set(docData); } } catch(err) { if(docExists) { console.error('Error updating document!', err); } else { console.error('Error creating document!', err); } return handleError(res, 'setDocument', err); } return true; } function handleError(res, step, incomingError) { if (!incomingError) return null; console.error('InternalError during', step, incomingError); return res.status(500).send({ errorType: 'InternalError', errorStep: step, message: incomingError.message, }); } function getToday() { var today = new Date(); var dd = String(today.getDate()).padStart(2, '0'); var mm = String(today.getMonth() + 1).padStart(2, '0'); var yyyy = today.getFullYear(); return yyyy + mm + dd; } function getRateLimitsObject(doc) { var d = new Date(); return { successful: (doc.deliveredCount || 0), errors: (doc.errorCount || 0), total: (doc.totalCount || 0), maximum: MAX_NOTIFICATIONS_PER_DAY, remaining: (MAX_NOTIFICATIONS_PER_DAY - doc.deliveredCount), resetsAt: new Date(d.getFullYear(), d.getMonth(), d.getDate()+1) }; }