diff --git a/functions/index.js b/functions/index.js index a366e04..002d673 100644 --- a/functions/index.js +++ b/functions/index.js @@ -11,10 +11,142 @@ var db = admin.firestore(); const MAX_NOTIFICATIONS_PER_DAY = 100; +//============================== +// Legacy function begin +//============================== exports.sendPushNotification = functions.https.onRequest(async (req, res) => { - return handleRequest(req, res, worker.createLegacyPayload); + if (debug()) console.log('Received payload', req.body); + var currentRateLimitDocName = legacyGetCurrentRateLimitsDocName(); + var token = req.body.push_token; + var ref = db.collection('rateLimits').doc(currentRateLimitDocName).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; + } + } + + if (debug()) 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 legacyHandleError(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 legacyHandleError(res, 'sendNotification', err); + } + + if (debug()) 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) { + if (debug()) console.log('Updating existing doc'); + await ref.update(docData); + } else { + if (debug()) 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 legacyHandleError(res, 'setDocument', err); + } + return true; +} + +function legacyGetCurrentRateLimitsDocName() { + 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 legacyHandleError(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, + }); +} +//============================== +// Legacy function end +//============================== exports.pushNotifyV2 = functions.https.onRequest(async (req, res) => { return handleRequest(req, res, worker.createPayload); }); @@ -140,14 +272,12 @@ function getToday() { function getRateLimitsObject(doc) { var d = new Date(); - var remainingCount = (MAX_NOTIFICATIONS_PER_DAY - doc.deliveredCount); - if (remainingCount === -1) remainingCount = 0; return { successful: (doc.deliveredCount || 0), errors: (doc.errorCount || 0), total: (doc.totalCount || 0), maximum: MAX_NOTIFICATIONS_PER_DAY, - remaining: remainingCount, + remaining: (MAX_NOTIFICATIONS_PER_DAY - doc.deliveredCount), resetsAt: new Date(d.getFullYear(), d.getMonth(), d.getDate()+1) }; } diff --git a/functions/worker.js b/functions/worker.js index 768202d..bfb2f72 100644 --- a/functions/worker.js +++ b/functions/worker.js @@ -1,45 +1,4 @@ module.exports = { - - createLegacyPayload: function createLegacyPayload(req) { - 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; - } - } - - if (debug()) console.log('Notification payload', JSON.stringify(payload)); - - return { updateRateLimits: true, payload: payload }; - }, - createPayload: function createPayload(req) { let payload = { android: {},