flow diagrams
This commit is contained in:
240
README.md
240
README.md
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user