flask and node.js solution

This commit is contained in:
cpu
2025-03-26 19:12:43 +01:00
parent 102d2e2748
commit 9b4bf1e255
13 changed files with 1445 additions and 515 deletions

288
README.md
View File

@@ -1,133 +1,203 @@
# Flic Button Web Push Notification Service
# Flic to PWA WebPush Backend
## Overview
This application provides a dockerized solution for handling Flic smart button events and sending web push notifications to a Progressive Web App (PWA).
This project provides a self-hosted backend service that listens for HTTP requests from Flic smart buttons and triggers Web Push notifications to specific Progressive Web App (PWA) instances. The goal is to allow a Flic button press (Single Click, Double Click, Hold) to trigger actions within the PWA via push messages handled by a Service Worker.
It's designed to be run as a Docker container and integrated with Traefik v3 for SSL termination and routing.
## Features
- Webhook endpoint for Flic button events
- Web Push notification support
- Configurable button actions
- Subscription management
* Receives POST requests on `/flic-webhook`.
* Parses `button_id` and `click_type` from the Flic request body.
* Looks up the target PWA push subscription based on `button_id` in a JSON file.
* Sends a Web Push notification containing the click details (action, button, timestamp) to the corresponding PWA subscription.
* Integrates with Traefik v3 via Docker labels.
* Configurable via environment variables (`.env` file).
* Optional bearer token authentication for securing the Flic webhook endpoint.
* CORS configuration for allowing requests (needed if your PWA management interface interacts with this service, although not strictly necessary for the Flic->Backend->PWA push flow itself).
## Prerequisites
- Docker
- Docker Compose
- Traefik network
- Curl or Postman for testing
* **Docker:** [Install Docker](https://docs.docker.com/engine/install/)
* **Traefik:** A running Traefik v3 instance configured with SSL (Let's Encrypt recommended) and connected to a Docker network named `traefik`. You need to know your certificate resolver name.
* **Domain Name:** A domain or subdomain pointing to your Traefik instance (e.g., `webpush.virtonline.eu`). This will be used for the webhook URL.
* **Flic Hub/Service:** Configured to send HTTP requests for button actions. You'll need the serial number(s) of your Flic button(s).
* **Node.js & npm/npx (Optional):** Needed only locally to generate VAPID keys easily. Not required for running the container.
* **PWA Push Subscription Details:** You need to obtain the Push Subscription object (containing `endpoint`, `keys.p256dh`, `keys.auth`) from your PWA after the user grants notification permission.
## Project Structure
## Setup
### 1. Generate VAPID Keys
Run the VAPID key generation script:
```bash
python generate_vapid_keys.py
```
This will create a `.env` file with VAPID keys.
1. **Clone the Repository:**
```bash
git clone https://gitea.virtonline.eu/2HoursProject/flic-webhook-webpush.git
cd flic-webhook-webpush
```
### 2. Configure Flic Buttons
Edit the `.env` file to add your Flic button serial numbers:
```
FLIC_BUTTON1_SERIAL=your_button1_serial
FLIC_BUTTON2_SERIAL=your_button2_serial
FLIC_BUTTON3_SERIAL=your_button3_serial
```
2. **Generate VAPID Keys:**
Web Push requires VAPID keys for security. Generate them once and store them into `.env`. You can use `npx`:
```bash
npx web-push generate-vapid-keys
```
This will output a Public Key and a Private Key.
### 3. Docker Compose Configuration
```yaml
version: '3'
services:
flic-webpush:
build: .
volumes:
- ./subscriptions.json:/app/subscriptions.json
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.flic-webpush.rule=Host(`flic.yourdomain.com`)"
3. **Obtain PWA Push Subscription Details:**
* Your PWA needs to use the Push API to request notification permission from the user.
* When permission is granted, the browser's push service provides a `PushSubscription` object.
* This object typically looks like:
```json
{
"endpoint": "https://updates.push.services.mozilla.com/...",
"expirationTime": null,
"keys": {
"p256dh": "...",
"auth": "..."
}
}
```
* You need to get this JSON object from your PWA (e.g., display it to the user to copy, send it to a setup endpoint - though that's more complex).
networks:
traefik:
external: true
```
4. **Configure Environment Variables:**
* Copy the example `.env` file:
```bash
cp .env.example .env
```
* Edit the `.env` file with your specific values:
* `VAPID_PUBLIC_KEY`: The public key generated in step 2. **Your PWA will also need this key** when it subscribes to push notifications.
* `VAPID_PRIVATE_KEY`: The private key generated in step 2. **Keep this secret!**
* `VAPID_SUBJECT`: A `mailto:` or `https:` URL identifying you or your application (e.g., `mailto:admin@yourdomain.com`). Used by push services to contact you.
* `PORT`: (Default: `3000`) The internal port the Node.js app listens on. Traefik will map to this.
* `SUBSCRIPTIONS_FILE`: (Default: `/app/subscriptions.json`) The path *inside the container* where the button-to-subscription mapping is stored.
* `FLIC_SECRET`: (Optional) Set a strong, random secret string if you want to secure the webhook endpoint using Bearer token authentication. Generate with `openssl rand -hex 32` or a password manager.
* `ALLOWED_ORIGINS`: Comma-separated list of domains allowed by CORS. Include your PWA's domain if it needs to interact directly (e.g., for setup). Example: `https://my-pwa.com`.
* `ALLOWED_METHODS`: (Default: `POST,OPTIONS`) Standard methods needed.
* `ALLOWED_HEADERS`: (Default: `Content-Type,Authorization`) Standard headers needed.
* `TRAEFIK_SERVICE_HOST`: Your public domain for this service (e.g., `webpush.virtonline.eu`).
* `TRAEFIK_CERT_RESOLVER`: The name of your TLS certificate resolver configured in Traefik (e.g., `le`, `myresolver`).
### 4. Endpoints
- `/flic-webhook`: Receive Flic button events
- `/subscribe`: Add web push subscriptions
5. **Configure Traefik Labels:**
* Copy the example `labels` file:
```bash
cp labels.example labels
```
* **Important:** Edit the `labels` file. Replace `${TRAEFIK_SERVICE_HOST}`, `${TRAEFIK_CERT_RESOLVER}`, and `${PORT}` with the *actual values* from your `.env` file, as `docker run` does not substitute variables in label files.
* Example replacement: `Host(\`${TRAEFIK_SERVICE_HOST}\`)` becomes `Host(`webpush.virtonline.eu`)`.
* `traefik.http.routers.flic-webhook.tls.certresolver=${TRAEFIK_CERT_RESOLVER}` becomes `traefik.http.routers.flic-webhook.tls.certresolver=myresolver`.
* `traefik.http.services.flic-webhook.loadbalancer.server.port=${PORT}` becomes `traefik.http.services.flic-webhook.loadbalancer.server.port=3000`.
## Testing Webhooks
6. **Prepare Subscription Mapping File:**
* Create the `subscriptions.json` file (or edit the template provided).
* Add entries mapping your Flic button's serial number (as a lowercase string key) to the PWA `PushSubscription` object obtained in step 3.
```json
{
"80:e4:da:70:xx:xx:xx:xx": { // <-- Replace with your actual Flic Button Serial (lowercase recommended)
"endpoint": "https://your_pwa_push_endpoint...",
"expirationTime": null,
"keys": {
"p256dh": "YOUR_PWA_SUBSCRIPTION_P256DH_KEY",
"auth": "YOUR_PWA_SUBSCRIPTION_AUTH_KEY"
}
}
// Add more entries for other buttons if needed
}
```
* Ensure this file contains valid JSON.
### Simulating Flic Button Events
You can test the webhook endpoint using curl or Postman. Here are example requests:
## Running the Service
#### Button 1 Event (Home Lights On)
```bash
curl -X POST http://localhost:8080/flic-webhook \
-H "Content-Type: application/json" \
-d '{
"serial": "your_button1_serial",
"event": "click",
"timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
}'
```
1. **Build the Docker Image:**
Make sure you are in the `flic-webhook-webpush` directory.
```bash
docker build -t flic-webhook-webpush:latest .
```
#### Button 2 Event (Security System Arm)
```bash
curl -X POST http://localhost:8080/flic-webhook \
-H "Content-Type: application/json" \
-d '{
"serial": "your_button2_serial",
"event": "double_click",
"timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
}'
```
2. **Run the Container:**
This command runs the container in detached mode (`-d`), names it, connects it to the `traefik` network, passes environment variables from the `.env` file, applies the Traefik labels from the `labels` file, and mounts the `subscriptions.json` file into the container.
#### Button 3 Event (Panic Alert)
```bash
curl -X POST http://localhost:8080/flic-webhook \
-H "Content-Type: application/json" \
-d '{
"serial": "your_button3_serial",
"event": "long_press",
"timestamp": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
}'
```
```bash
docker run -d --name flic-webhook-webpush \
--network traefik \
--env-file .env \
--label-file labels \
--mount type=bind,src=./subscriptions.json,dst=/app/subscriptions.json,readonly \
flic-webhook-webpush:latest
```
* `--network traefik`: Connects to the Traefik network.
* `--env-file .env`: Loads configuration from your `.env` file.
* `--label-file labels`: Applies the Traefik routing rules from your edited `labels` file.
* `--mount ...`: Makes your local `subscriptions.json` available inside the container at `/app/subscriptions.json`. `readonly` is recommended as the app only reads it.
* `flic-webhook-webpush:latest`: The image built in the previous step.
### Adding a Web Push Subscription
To test the subscription endpoint:
```bash
curl -X POST http://localhost:8080/subscribe \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://example.com/push-endpoint",
"keys": {
"p256dh": "base64-public-key",
"auth": "base64-auth-secret"
}
}'
```
3. **Check Logs:**
Monitor the container logs to ensure it started correctly and to see incoming webhook requests or errors.
```bash
docker logs -f flic-webhook-webpush
```
You should see messages indicating the server started, configuration details, and subscription loading status.
### Debugging Tips
- Check container logs: `docker logs flic-webpush`
- Verify subscription file: `cat subscriptions.json`
- Ensure correct button serial numbers in `.env`
4. **Verify Traefik:** Check your Traefik dashboard to ensure the `flic-webhook-webpush` service and router are discovered and healthy.
## Button Actions
- Button 1: Home Lights On
- Button 2: Security System Arm
- Button 3: Panic Alert
## Flic Button Configuration
## Logging
Configurable via `LOG_LEVEL` in `.env`
In your Flic app or Flic Hub SDK interface:
## Security Considerations
- Keep VAPID keys secret
- Use HTTPS
- Validate and sanitize all incoming webhook requests
- Implement proper authentication for production use
1. Select your Flic button.
2. Add an "Internet Request" action (or similar HTTP request action) for Single Click, Double Click, and/or Hold events.
3. **URL:** `https://<YOUR_TRAEFIK_SERVICE_HOST>/flic-webhook` (e.g., `https://webpush.virtonline.eu/flic-webhook`)
4. **Method:** `POST`
5. **Body Type:** `JSON` (or `application/json`)
6. **Body:** Configure the JSON body to include the button's serial number and the click type. Flic usually provides variables for these. The backend expects `button_id` and `click_type`. Adapt the keys if needed, or modify `server.js` to expect different keys (e.g., `serialNumber`).
```json
{
"button_id": "{serialNumber}",
"click_type": "{clickType}",
"timestamp": "{timestamp}"
}
```
*(Verify the exact variable names like `{serialNumber}`, `{clickType}`, `{timestamp}` within your specific Flic interface.)*
7. **Headers:**
* Add `Content-Type: application/json`.
* **(Optional - if `FLIC_SECRET` is set):** Add an `Authorization` header:
* Key: `Authorization`
* Value: `Bearer <YOUR_FLIC_SECRET_VALUE>` (Replace `<YOUR_FLIC_SECRET_VALUE>` with the actual secret from your `.env` file).
## API Endpoint
* **`POST /flic-webhook`**
* **Description:** Receives Flic button events.
* **Authentication:** Optional Bearer token via `Authorization` header if `FLIC_SECRET` is configured.
* **Request Body (JSON):**
```json
{
"button_id": "SERIAL_NUMBER_OF_FLIC_BUTTON",
"click_type": "SingleClick | DoubleClick | Hold",
"timestamp": "ISO_8601_TIMESTAMP_STRING (Optional)"
}
```
* **Responses:**
* `200 OK`: Webhook received, push notification sent successfully.
* `400 Bad Request`: Missing `button_id` or `click_type` in the request body.
* `401 Unauthorized`: Missing or invalid Bearer token (if `FLIC_SECRET` is enabled).
* `404 Not Found`: No subscription found in `subscriptions.json` for the given `button_id`.
* `410 Gone`: The push subscription associated with the button was rejected by the push service (likely expired or revoked).
* `500 Internal Server Error`: Failed to send the push notification for other reasons.
* **`GET /health`** (Optional)
* **Description:** Simple health check endpoint.
* **Response:**
```json
{
"status": "UP",
"timestamp": "ISO_8601_TIMESTAMP_STRING"
}
```
## Troubleshooting
- Ensure all environment variables are correctly set
- Check network connectivity
- Verify Traefik configuration
- Validate button serial numbers match between configuration and webhook
* **Check Backend Logs:** `docker logs flic-webhook-webpush`. Look for errors related to configuration, file access, JSON parsing, authentication, or sending push notifications.
* **Check Traefik Logs:** `docker logs traefik`. Look for routing errors or certificate issues.
* **Verify `.env`:** Ensure all required variables are set correctly, especially VAPID keys and Traefik settings.
* **Verify `labels`:** Double-check that variables were correctly substituted manually and match your `.env` and Traefik setup.
* **Verify `subscriptions.json`:** Ensure it's valid JSON and the button serial number (key) matches exactly what Flic sends (check backend logs for "Received webhook: Button=..."). Check if the subscription details are correct. Case sensitivity matters for the JSON keys (button serials).
* **Check Flic Configuration:** Ensure the URL, Method, Body, and Headers (especially `Content-Type` and `Authorization` if used) are correct in the Flic action setup. Use `curl` or Postman to test the endpoint manually first.
* **PWA Service Worker:** Remember that the PWA needs a correctly registered Service Worker to receive and handle the incoming push messages. Ensure the PWA subscribes using the *same* `VAPID_PUBLIC_KEY` configured in the backend's `.env`.