Files
flic-webhook-webpush/README.md
2025-03-28 17:28:24 +01:00

247 lines
14 KiB
Markdown

# Flic to PWA WebPush Backend
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
* Receives GET requests on `/webhook`.
* Receives POST requests on `/subscribe` to manage button-PWA mappings.
* Uses HTTP headers `Button-Name` and `Timestamp` from the Flic request.
* Gets `click_type` from URL path.
* Looks up the target PWA push subscription based on the `Button-Name` header 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 Basic Authentication for securing the `/webhook` and `/subscribe` endpoints.
* CORS configuration for allowing requests (needed if your PWA management interface interacts with the `/subscribe` endpoint).
## Prerequisites
* **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.
## System Architecture
### Subscription Flow
<img src="images/subscription-flow.png" width="600" alt="Subscription Flow">
### Interaction Flow
<img src="images/interaction-flow.png" width="300" alt="Interaction Flow">
## Project Structure
## Setup
1. **Clone the Repository:**
```bash
git clone https://gitea.virtonline.eu/2HoursProject/flic-webhook-webpush.git
cd flic-webhook-webpush
```
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. **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.
* `BASIC_AUTH_USERNAME`: (Optional) Username for Basic Authentication. If set along with `BASIC_AUTH_PASSWORD`, authentication will be enabled for `/webhook` and `/subscribe`.
* `BASIC_AUTH_PASSWORD`: (Optional) Password for Basic Authentication. If set along with `BASIC_AUTH_USERNAME`, authentication will be enabled.
* `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.
* `MAX_NOTIFICATION_RETRIES`: (Default: `3`) Number of retry attempts for failed push notifications. Must be a number.
* `INITIAL_RETRY_DELAY_MS`: (Default: `1000`) Initial delay in milliseconds before first retry. Must be a number.
* `DNS_TIMEOUT_MS`: (Default: `5000`) DNS resolution timeout in milliseconds. Must be a number.
* `HTTP_TIMEOUT_MS`: (Default: `10000`) HTTP request timeout in milliseconds. Must be a number.
* `LOG_LEVEL`: (Default: `info`) Controls verbosity of logs. Valid values are `error`, `warn`, `info`, or `debug`. Use `debug` to see detailed header information and other diagnostic messages.
* `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. **Configure Traefik Labels:**
* Copy the example `labels` file:
```bash
cp labels.example labels
```
5. **Prepare Subscription Mapping File:**
* Edit the `subscriptions.json` file
* Add entries mapping your Flic button's serial number (as a lowercase string key) to the PWA `PushSubscription` object.
```json
{
"game": { // <-- Replace with your actual Flic Button name (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.
## Running the Service
1. **Build the Docker Image:**
Make sure you are in the `flic-webhook-webpush` directory.
```bash
docker build -t flic-webhook-webpush:latest .
```
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.
```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 \
flic-webhook-webpush:latest
```
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.
4. **Verify Traefik:** Check your Traefik dashboard to ensure the `flic-webhook-webpush` service and router are discovered and healthy.
## Flic Button Configuration
In your Flic app or Flic Hub SDK interface:
1. Select your Flic button.
2. Add an "Internet Request" action.
3. Fill in the following details:
* Set `GET` method.
* Set URL with query parameter: `https://<your_domain>/webhook/SingleClick` (Replace `<your_domain>` with your actual service domain, e.g., `webpush.virtonline.eu`).
* **If Basic Authentication is enabled:**
* Set the `Username` and `Password` fields to the values from your `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` environment variables.
* **If Basic Authentication is disabled:**
* Leave the `Username` and `Password` fields empty.
* Tap on `Save action`.
4. Repeat for Double Click and/or Hold events, changing the `click_type` parameter accordingly (e.g., `/DoubleClick`).
## API Endpoints
* **`POST /subscribe`**
* **Description:** Adds or updates the Web Push subscription associated with a specific Flic button ID.
* **Authentication:** Optional Basic Authentication via `Authorization` header if `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` are configured.
* **Request Body:** JSON object containing:
* `button_id` (string, required): The unique identifier for the Flic button (lowercase recommended, e.g., "game", "lights").
* `subscription` (object, required): The [PushSubscription object](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) obtained from the browser's Push API.
```json
{
"button_id": "game",
"subscription": {
"endpoint": "https://your_pwa_push_endpoint...",
"expirationTime": null,
"keys": {
"p256dh": "YOUR_PWA_SUBSCRIPTION_P256DH_KEY",
"auth": "YOUR_PWA_SUBSCRIPTION_AUTH_KEY"
}
}
}
```
* **Responses:**
* `201 Created`: Subscription saved successfully.
* `400 Bad Request`: Missing or invalid `button_id` or `subscription` object in the request body.
* `401 Unauthorized`: Missing or invalid Basic Authentication credentials (if authentication is enabled).
* `500 Internal Server Error`: Failed to save the subscription to the file.
* **`GET /webhook/:click_type`**
* **Description:** Receives Flic button events.
* **Authentication:** Optional Basic Authentication via `Authorization` header if `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` are configured.
* **URL Parameters:**
* `click_type` (required): The type of button press (e.g., `SingleClick`, `DoubleClick`, or `Hold`).
* **Required Headers:**
* `Button-Name` (required): The identifier of the Flic button (sent by the Flic system).
* **Optional Headers:**
* `Timestamp`: Timestamp of the button event (sent by the Flic system).
* `Button-Battery-Level`: The battery level percentage of the button (sent by the Flic system).
* **Push Notification Payload (`data` field):** The service sends a JSON payload within the push notification. The client-side Service Worker can access this data via `event.data.json()`. The structure is:
```json
{
"action": "SingleClick", // or DoubleClick, Hold
"button": "game-button", // Normalized button name (lowercase)
"timestamp": "2024-03-28T15:00:00.000Z", // ISO 8601 timestamp or current server time
"batteryLevel": 100 // Integer percentage (0-100) or 'N/A' if not provided
}
```
* **Responses:**
* `200 OK`: Webhook received, push notification sent successfully.
* `400 Bad Request`: Missing `Button-Name` header or `click_type` URL parameter.
* `401 Unauthorized`: Missing or invalid Basic Authentication credentials (if authentication is enabled).
* `404 Not Found`: No subscription found in `subscriptions.json` for the given `Button-Name`.
* `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.
## Testing the Webhook
Once your service is up and running, you can test the webhook endpoint using curl or any API testing tool. This example assumes Basic Authentication is enabled.
**Note:** Replace `<username>`, `<password>`, `<your_domain>`, and `<button_name>` with your actual values.
```bash
# Generate Base64 credentials (run this once)
# echo -n '<username>:<password>' | base64
# Example using generated Base64 string (replace YOUR_BASE64_CREDS)
curl -X GET "https://<your_domain>/webhook/SingleClick" \
-H "Authorization: Basic YOUR_BASE64_CREDS" \
-H "Button-Name: <button_name>" \
-H "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-H "Button-Battery-Level: 100" \
# Example using curl's built-in Basic Auth (-u)
curl -X GET "https://<your_domain>/webhook/SingleClick" \
-u "<username>:<password>" \
-H "Button-Name: <button_name>" \
-H "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-H "Button-Battery-Level: 100" \
```
The expected response should be:
```json
{"message":"Push notification sent successfully"}
```
If successful, the above response indicates that:
1. Your webhook endpoint is properly configured
2. The button ID was found in your subscriptions.json file
3. The web push notification was successfully sent to the registered PUSH API endpoint (e.g. https://jmt17.google.com/fcm/send/cf907M...)
If you receive a different response, refer to the Troubleshooting section below.
## Troubleshooting
* **Check Backend Logs:** `docker logs flic-webhook-webpush`. Look for errors related to configuration, file access, JSON parsing, authentication, or sending push notifications.
* To see detailed debug information including all headers received from the Flic button, set `LOG_LEVEL=debug` in your .env file.
* **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, `click_type` parameter, and authentication details (Username/Password if enabled) 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`.