Procházet zdrojové kódy

Server: FCM Admin SDK implementation; + Testing Endpoint POST /fcm/topics/:topic

Christian Kahlau před 3 roky
rodič
revize
da0da3f083

+ 1 - 0
.gitignore

@@ -6,6 +6,7 @@ daemon/data/
 daemon/dist/
 daemon/.env
 
+server/google-cloud/
 server/public/
 server/dist/
 server/data/

+ 3 - 1
server/.env.default

@@ -1,3 +1,5 @@
 LOG_LEVEL=INFO
 WEB_PORT=8880
-DATA_DIR=data
+DATA_DIR=data
+GOOGLE_APPLICATION_CREDENTIALS="google-cloud/firebase-adminsdk.json"
+NOTIFICATION_ICON_URL="https://fcm.hostbbq.net/logo.png"

+ 33 - 0
server/docs/Monitoring.postman_collection.json

@@ -210,6 +210,39 @@
 						}
 					},
 					"response": []
+				},
+				{
+					"name": "/fcm/topics/{:topic}",
+					"request": {
+						"method": "POST",
+						"header": [],
+						"body": {
+							"mode": "raw",
+							"raw": "{\r\n    \"title\": \"HostBBQ Monitoring Notification\",\r\n    \"body\": \"First Test from Monitoring Backend via Firebase Admin SDK\"\r\n}",
+							"options": {
+								"raw": {
+									"language": "json"
+								}
+							}
+						},
+						"url": {
+							"raw": "http://10.8.0.1:8880/fcm/topics/monitoring-services",
+							"protocol": "http",
+							"host": [
+								"10",
+								"8",
+								"0",
+								"1"
+							],
+							"port": "8880",
+							"path": [
+								"fcm",
+								"topics",
+								"monitoring-services"
+							]
+						}
+					},
+					"response": []
 				}
 			]
 		}

+ 14 - 0
server/install/install.sh

@@ -24,6 +24,20 @@ if [ -z "$EXC_NODE" ]; then
   exit 1
 fi
 
+GCC_CONFIG_DIR="${INSTALL_DIR}/google-cloud"
+FCM_ACCOUNT_JSON="${GCC_CONFIG_DIR}/firebase-adminsdk.json"
+
+if [ ! -d "$GCC_CONFIG_DIR" ]; then
+  echo "[ERROR] Missing required google cloud key files. Please prepare directory 'google-cloud' in install directory." >&2
+  exit 1
+fi
+
+if [ ! -f "$FCM_ACCOUNT_JSON" ]; then
+  echo "[ERROR] Missing required Firebase Admin SDK key file. Please provide this file as 'google-cloud/firebase-adminsdk.json' in order to install." >&2
+  exit 1
+fi
+
+
 # exit on error exit codes
 set -e
 

+ 1 - 0
server/package.json

@@ -13,6 +13,7 @@
     "axios": "^0.27.2",
     "dotenv": "^16.0.2",
     "express": "^4.18.1",
+    "firebase-admin": "^11.4.1",
     "moment": "^2.29.4",
     "sqlite3": "^5.1.1"
   },

+ 48 - 0
server/src/ctrl/fcm-controller.class.ts

@@ -0,0 +1,48 @@
+import firebaseAdmin from 'firebase-admin';
+
+export class FCMController {
+  private static INSTANCE?: FCMController;
+
+  private app: firebaseAdmin.app.App;
+  private messaging;
+
+  private constructor() {
+    // Uses ENV GOOGLE_APPLICATION_CREDENTIALS to load credentials file
+    this.app = firebaseAdmin.initializeApp();
+    this.messaging = this.app.messaging();
+  }
+
+  public static get instance(): FCMController {
+    if (!FCMController.INSTANCE) {
+      FCMController.INSTANCE = new FCMController();
+    }
+    return FCMController.INSTANCE;
+  }
+
+  public async sendNotificationToTopic(topic: string, notification: { title: string; body: string }) {
+    console.log('ICON URL:', process.env.NOTIFICATION_ICON_URL);
+
+    const response = await this.messaging.send({
+      notification: {
+        imageUrl: process.env.NOTIFICATION_ICON_URL ?? '',
+        ...notification
+      },
+      android: {
+        notification: {
+          icon: process.env.NOTIFICATION_ICON_URL ?? '',
+          imageUrl: process.env.NOTIFICATION_ICON_URL ?? ''
+        }
+      },
+      webpush: {
+        headers: {
+          image: process.env.NOTIFICATION_ICON_URL ?? ''
+        },
+        notification: {
+          icon: process.env.NOTIFICATION_ICON_URL ?? ''
+        }
+      },
+      topic
+    });
+    return response;
+  }
+}

+ 27 - 0
server/src/webhdl/fcm-api-handler.class.ts

@@ -0,0 +1,27 @@
+import { json, RouterOptions } from 'express';
+
+import { ControllerPool } from '../ctrl/controller-pool.interface';
+import { FCMController } from '../ctrl/fcm-controller.class';
+import { WebHandler } from './web-handler.base';
+
+export class FCMAPIHandler extends WebHandler {
+  constructor(protected ctrlPool: ControllerPool, options?: RouterOptions) {
+    super(ctrlPool, options);
+
+    this.router.use(json());
+    this.router.use(this.avoidCache);
+
+    this.router.post('/topics/:topic', async (req, res, next) => {
+      try {
+        const topic = req.params.topic;
+        const notification = req.body;
+
+        const messageId = await FCMController.instance.sendNotificationToTopic(topic, notification);
+
+        res.status(201).send({ sent: true, id: messageId });
+      } catch (err) {
+        next(err);
+      }
+    });
+  }
+}

+ 3 - 0
server/src/webserver.class.ts

@@ -6,6 +6,7 @@ import { Logger } from '../../common/util/logger.class';
 
 import { ControllerPool } from './ctrl/controller-pool.interface';
 import { ValidationException } from './lib/validation-exception.class';
+import { FCMAPIHandler } from './webhdl/fcm-api-handler.class';
 import { ServerAPIHandler } from './webhdl/server-api-handler.class';
 import { ServicesAPIHandler } from './webhdl/services-api-handler.class';
 
@@ -15,6 +16,8 @@ export class Webserver {
   constructor(private port: number, ctrlPool: ControllerPool) {
     this.app = express();
 
+    const fcmApi = new FCMAPIHandler(ctrlPool);
+    this.app.use('/fcm', fcmApi.router);
     const serverApi = new ServerAPIHandler(ctrlPool);
     this.app.use('/server', serverApi.router);
     const servicesApi = new ServicesAPIHandler(ctrlPool);