Initial import
This commit is contained in:
parent
ccdc09ff63
commit
6dc4e2e902
5
.firebaserc
Normal file
5
.firebaserc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"projects": {
|
||||
"default": "ha-client-c73c4"
|
||||
}
|
||||
}
|
65
.gitignore
vendored
Normal file
65
.gitignore
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
firebase-debug.log*
|
||||
|
||||
# Firebase cache
|
||||
.firebase/
|
||||
|
||||
# Firebase config
|
||||
|
||||
# Uncomment this if you'd like others to create their own Firebase project.
|
||||
# For a team working on the same Firebase project(s), it is recommended to leave
|
||||
# it commented so all members can deploy to the same project(s) in .firebaserc.
|
||||
# .firebaserc
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
1
firebase.json
Normal file
1
firebase.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
1
functions/.gitignore
vendored
Normal file
1
functions/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
153
functions/index.js
Normal file
153
functions/index.js
Normal file
@ -0,0 +1,153 @@
|
||||
'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)
|
||||
};
|
||||
}
|
2008
functions/package-lock.json
generated
Normal file
2008
functions/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
functions/package.json
Normal file
22
functions/package.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "functions",
|
||||
"description": "Cloud Functions for Firebase",
|
||||
"scripts": {
|
||||
"serve": "firebase serve --only functions",
|
||||
"shell": "firebase functions:shell",
|
||||
"start": "npm run shell",
|
||||
"deploy": "firebase deploy --only functions",
|
||||
"logs": "firebase functions:log"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8"
|
||||
},
|
||||
"dependencies": {
|
||||
"firebase-admin": "^8.0.0",
|
||||
"firebase-functions": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"firebase-functions-test": "^0.1.6"
|
||||
},
|
||||
"private": true
|
||||
}
|
3
package-lock.json
generated
Normal file
3
package-lock.json
generated
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"lockfileVersion": 1
|
||||
}
|
Reference in New Issue
Block a user