flow diagrams

This commit is contained in:
cpu
2025-03-27 20:17:35 +01:00
parent c89eaacd42
commit 2da6061795
14 changed files with 479 additions and 271 deletions

240
README.md
View File

@@ -6,14 +6,15 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
## Features
* 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.
* 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 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).
* Optional Basic Authentication for securing the `/webhook` and `/subscribe` endpoints.
## Prerequisites
@@ -24,6 +25,14 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
* **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
@@ -41,23 +50,7 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
```
This will output a Public Key and a Private Key.
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).
4. **Configure Environment Variables:**
3. **Configure Environment Variables:**
* Copy the example `.env` file:
```bash
cp .env.example .env
@@ -67,41 +60,22 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
* `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`).
* `SUBSCRIPTIONS_FILE`: (Default: `subscriptions.json`) The path *inside the container* where the button-to-subscription mapping is stored.
* `DEFAULT_BUTTON_NAME`: (Default: `game-button`) The default button name to use when the `Button-Name` header is not provided in the webhook request.
* `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.
* `NOTIFICATION_MAX_RETRIES`: (Default: `3`) Number of retry attempts for failed push notifications. Must be a number.
* `NOTIFICATION_FIRST_RETRY_DELAY_MS`: (Default: `10`) Delay in milliseconds for the first retry attempt. Setting to 0-10ms provides near-immediate first retry for transient DNS issues. Must be a number.
* `NOTIFICATION_SUBSEQUENT_RETRY_DELAY_MS`: (Default: `1000`) Base delay in milliseconds for subsequent retries. Each additional retry uses this value with exponential backoff and jitter. 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.
5. **Configure Traefik Labels:**
4. **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`.
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.
## Running the Service
@@ -115,18 +89,13 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
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 \
docker run --rm -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
```
* `--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.
3. **Check Logs:**
Monitor the container logs to ensure it started correctly and to see incoming webhook requests or errors.
@@ -142,62 +111,137 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
In your Flic app or Flic Hub SDK interface:
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).
2. Add an "Internet Request" action.
3. Fill in the following details:
* Select the `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 Headers:
* Set the `Key` fields to `Authorization`.
* Set the `Value` fields to `Basic <base64 encoded username:password>`.
* Click `ADD`.
* Tap on `SAVE ACTION`.
4. Repeat for Double Click (i.e., `/DoubleClick`) and Hold (i.e., `/Hold`) events.
The request for the Hold event should look like this:
<img src="images/flic-button-request.png" width="300" alt="Flic Button Request">
## API Endpoint
## App Example: "HTTP Shortcuts" by waboodoo
* **`POST /flic-webhook`**
* **Description:** Receives Flic button events.
* **Authentication:** Optional Bearer token via `Authorization` header if `FLIC_SECRET` is configured.
* **Request Body (JSON):**
Search the Play Store - there might be others with similar names.
1. Install the App: Download and install "HTTP Shortcuts" or a similar app from the Google Play Store.
2. Create a New Shortcut within the App:
* Open the app and usually tap a '+' or 'Add' button.
* Give your shortcut a Name (e.g., "Turn on Office Light", "Log Water Intake").
* Choose an Icon.
* Enter the URL you want the request sent to (your webhook URL, IFTTT URL, Home Assistant webhook trigger, etc.).
* Select the HTTP Method (GET, POST, PUT, DELETE, etc. - often GET or POST for simple triggers).
* For POST/PUT: You'll likely need to configure the Request Body (e.g., JSON data) and Content Type (e.g., application/json).
* Authentication: Configure Basic Auth, Bearer Tokens, or custom Headers if your endpoint requires authentication.
* Other Options: Explore settings for response handling (show message on success/failure), timeouts, etc.
* Save the shortcut configuration within the app.
3. Add the Widget/Shortcut to your Home Screen:
* Go to your Android Home Screen.
* Long-press on an empty space.
* Select "Widgets".
* Scroll through the list to find the "HTTP Shortcuts" app (or the app you installed).
* Drag the app's widget or shortcut option onto your home screen.
* The app will likely ask you to choose which specific shortcut (the one you just created) this widget should trigger. Select it.
4. Test: Tap the newly created button on your home screen. It should trigger the internet request you configured.
## 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, optional): The unique identifier for the Flic button (lowercase recommended, e.g., "game-button", "lights-button"). If not provided, the value of `DEFAULT_BUTTON_NAME` environment variable will be used as a fallback.
* `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": "SERIAL_NUMBER_OF_FLIC_BUTTON",
"click_type": "SingleClick | DoubleClick | Hold",
"timestamp": "ISO_8601_TIMESTAMP_STRING (Optional)"
"button_id": "game-button", // Optional, defaults to DEFAULT_BUTTON_NAME environment variable
"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 `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`: The identifier of the Flic button (sent by the Flic system). If not provided, the value of `DEFAULT_BUTTON_NAME` environment variable will be used as a fallback.
* **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:
```bash
curl -X GET https://webpush.virtonline.eu/webhook/SingleClick \
-H 'Authorization: Basic cGxheWVyOlNldmVuT2ZOaW5l' \
-H "Button-Name: Game-button" \
-H "Timestamp: 2025-03-26T01:10:20Z" \
-H "Button-Battery-Level: 100"
```
* **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`.
* `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.
* **`GET /health`** (Optional)
* **Description:** Simple health check endpoint.
* **Response:**
```json
{
"status": "UP",
"timestamp": "ISO_8601_TIMESTAMP_STRING"
}
```
## 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>` with your actual values. The `Button-Name` header is optional and will default to the value of `DEFAULT_BUTTON_NAME` if not provided.
```bash
curl -X GET "https://webpush.virtonline.eu/webhook/SingleClick" \
-H "Authorization: Basic $(echo -n 'user:password' | base64)" \
-H "Button-Name: game-button" \
-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 name 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, 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`.
* **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`.
* **Push Notification Retry Mechanism:** The service includes an optimized retry mechanism for handling temporary DNS resolution issues:
* First retry happens immediately or with minimal delay (controlled by `NOTIFICATION_FIRST_RETRY_DELAY_MS`, default 10ms)
* Subsequent retries use exponential backoff with jitter (starting from `NOTIFICATION_SUBSEQUENT_RETRY_DELAY_MS`, default 1000ms)
* Maximum number of retries is controlled by `NOTIFICATION_MAX_RETRIES` (default 3)
* This approach minimizes latency for transient DNS issues while preventing excessive requests for persistent problems
* Adjust these values in your `.env` file based on your network conditions and reliability requirements