default button name

This commit is contained in:
cpu
2025-03-28 17:51:25 +01:00
parent 5a3b4974c4
commit 058441b6e6
4 changed files with 27 additions and 19 deletions

View File

@@ -9,6 +9,7 @@ VAPID_SUBJECT=mailto:mailto:admin@virtonline.eu # Contact email/URL for push ser
# --- Server Configuration --- # --- Server Configuration ---
PORT=3000 # Internal port for the Node.js app PORT=3000 # Internal port for the Node.js app
SUBSCRIPTIONS_FILE=subscriptions.json # Path inside the container SUBSCRIPTIONS_FILE=subscriptions.json # Path inside the container
DEFAULT_BUTTON_NAME=game-button # Default button name to use when not specified
# --- Authentication (Optional) --- # --- Authentication (Optional) ---
# If both USERNAME and PASSWORD are set, Basic Auth will be enabled for: # If both USERNAME and PASSWORD are set, Basic Auth will be enabled for:

View File

@@ -62,6 +62,7 @@ It's designed to be run as a Docker container and integrated with Traefik v3 for
* `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. * `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. * `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. * `SUBSCRIPTIONS_FILE`: (Default: `/app/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_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. * `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_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`.
@@ -150,11 +151,11 @@ In your Flic app or Flic Hub SDK interface:
* **Description:** Adds or updates the Web Push subscription associated with a specific Flic button ID. * **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. * **Authentication:** Optional Basic Authentication via `Authorization` header if `BASIC_AUTH_USERNAME` and `BASIC_AUTH_PASSWORD` are configured.
* **Request Body:** JSON object containing: * **Request Body:** JSON object containing:
* `button_id` (string, required): The unique identifier for the Flic button (lowercase recommended, e.g., "game", "lights"). * `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. * `subscription` (object, required): The [PushSubscription object](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription) obtained from the browser's Push API.
```json ```json
{ {
"button_id": "game", "button_id": "game-button", // Optional, defaults to DEFAULT_BUTTON_NAME environment variable
"subscription": { "subscription": {
"endpoint": "https://your_pwa_push_endpoint...", "endpoint": "https://your_pwa_push_endpoint...",
"expirationTime": null, "expirationTime": null,
@@ -167,7 +168,7 @@ In your Flic app or Flic Hub SDK interface:
``` ```
* **Responses:** * **Responses:**
* `201 Created`: Subscription saved successfully. * `201 Created`: Subscription saved successfully.
* `400 Bad Request`: Missing or invalid `button_id` or `subscription` object in the request body. * `400 Bad Request`: Missing or invalid `subscription` object in the request body.
* `401 Unauthorized`: Missing or invalid Basic Authentication credentials (if authentication is enabled). * `401 Unauthorized`: Missing or invalid Basic Authentication credentials (if authentication is enabled).
* `500 Internal Server Error`: Failed to save the subscription to the file. * `500 Internal Server Error`: Failed to save the subscription to the file.
@@ -177,7 +178,7 @@ In your Flic app or Flic Hub SDK interface:
* **URL Parameters:** * **URL Parameters:**
* `click_type` (required): The type of button press (e.g., `SingleClick`, `DoubleClick`, or `Hold`). * `click_type` (required): The type of button press (e.g., `SingleClick`, `DoubleClick`, or `Hold`).
* **Required Headers:** * **Required Headers:**
* `Button-Name` (required): The identifier of the Flic button (sent by the Flic system). * `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:** * **Optional Headers:**
* `Timestamp`: Timestamp of the button event (sent by the Flic system). * `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). * `Button-Battery-Level`: The battery level percentage of the button (sent by the Flic system).
@@ -202,7 +203,7 @@ In your Flic app or Flic Hub SDK interface:
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. 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. **Note:** Replace `<username>`, `<password>`, `<your_domain>`, and `<button_name>` with your actual values. The `Button-Name` header is optional and will default to the value of `DEFAULT_BUTTON_NAME` if not provided.
```bash ```bash
# Generate Base64 credentials (run this once) # Generate Base64 credentials (run this once)

View File

@@ -14,6 +14,7 @@ const vapidPublicKey = process.env.VAPID_PUBLIC_KEY;
const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY; const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY;
const vapidSubject = process.env.VAPID_SUBJECT; // mailto: or https: const vapidSubject = process.env.VAPID_SUBJECT; // mailto: or https:
const subscriptionsFilePath = process.env.SUBSCRIPTIONS_FILE || path.join(__dirname, 'subscriptions.json'); const subscriptionsFilePath = process.env.SUBSCRIPTIONS_FILE || path.join(__dirname, 'subscriptions.json');
const defaultButtonName = process.env.DEFAULT_BUTTON_NAME || 'game-button';
// Basic Authentication Credentials // Basic Authentication Credentials
const basicAuthUsername = process.env.BASIC_AUTH_USERNAME; const basicAuthUsername = process.env.BASIC_AUTH_USERNAME;
const basicAuthPassword = process.env.BASIC_AUTH_PASSWORD; const basicAuthPassword = process.env.BASIC_AUTH_PASSWORD;
@@ -233,20 +234,22 @@ const authenticateBasic = (req, res, next) => {
// Subscribe endpoint: Add a new button->subscription mapping // Subscribe endpoint: Add a new button->subscription mapping
// Apply Basic Authentication // Apply Basic Authentication
app.post('/subscribe', authenticateBasic, async (req, res) => { app.post('/subscribe', authenticateBasic, async (req, res) => {
const { button_id, subscription } = req.body; let { button_id, subscription } = req.body;
logger.debug('All headers received:'); logger.debug('All headers received:');
Object.keys(req.headers).forEach(headerName => { Object.keys(req.headers).forEach(headerName => {
logger.debug(` ${headerName}: ${req.headers[headerName]}`); logger.debug(` ${headerName}: ${req.headers[headerName]}`);
}); });
// If button_id is not provided, use defaultButtonName from environment variable
if (!button_id || typeof button_id !== 'string' || button_id.trim() === '') {
button_id = defaultButtonName;
logger.info(`No button_id provided, using default button name: ${button_id}`);
}
logger.info(`Received subscription request for button: ${button_id}`); logger.info(`Received subscription request for button: ${button_id}`);
// Basic Validation // Basic Validation - now we only validate subscription since button_id will use default if not provided
if (!button_id || typeof button_id !== 'string' || button_id.trim() === '') {
logger.warn('Subscription Error: Missing or invalid button_id');
return res.status(400).json({ message: 'Bad Request: Missing or invalid button_id' });
}
if (!subscription || typeof subscription !== 'object' || !subscription.endpoint || !subscription.keys || !subscription.keys.p256dh || !subscription.keys.auth) { if (!subscription || typeof subscription !== 'object' || !subscription.endpoint || !subscription.keys || !subscription.keys.p256dh || !subscription.keys.auth) {
logger.warn('Subscription Error: Missing or invalid subscription object structure'); logger.warn('Subscription Error: Missing or invalid subscription object structure');
return res.status(400).json({ message: 'Bad Request: Missing or invalid subscription object' }); return res.status(400).json({ message: 'Bad Request: Missing or invalid subscription object' });
@@ -273,7 +276,7 @@ app.post('/subscribe', authenticateBasic, async (req, res) => {
// Apply Basic Authentication // Apply Basic Authentication
app.get('/webhook/:click_type', authenticateBasic, async (req, res) => { app.get('/webhook/:click_type', authenticateBasic, async (req, res) => {
// Get buttonName from Header 'Button-Name' and timestamp from Header 'Timestamp' // Get buttonName from Header 'Button-Name' and timestamp from Header 'Timestamp'
const buttonName = req.headers['button-name']; const buttonName = req.headers['button-name'] || defaultButtonName;
const timestamp = req.headers['timestamp']; const timestamp = req.headers['timestamp'];
// Get click_type from URL path // Get click_type from URL path
const click_type = req.params.click_type; const click_type = req.params.click_type;
@@ -291,9 +294,9 @@ app.get('/webhook/:click_type', authenticateBasic, async (req, res) => {
logger.info(`Received GET webhook: Button=${buttonName}, Type=${click_type}, Timestamp=${timestamp || 'N/A'}`); logger.info(`Received GET webhook: Button=${buttonName}, Type=${click_type}, Timestamp=${timestamp || 'N/A'}`);
// Basic validation // Basic validation
if (!buttonName || !click_type) { if (!click_type) {
logger.warn(`Webhook Error: Missing Button-Name header or click_type query parameter`); logger.warn(`Webhook Error: Missing click_type query parameter`);
return res.status(400).json({ message: 'Bad Request: Missing Button-Name header or click_type query parameter' }); return res.status(400).json({ message: 'Bad Request: Missing click_type query parameter' });
} }
const normalizedButtonName = buttonName.toLowerCase(); // Use lowercase for lookup consistency const normalizedButtonName = buttonName.toLowerCase(); // Use lowercase for lookup consistency

View File

@@ -7,17 +7,20 @@ DefaultDependencies=no
[Service] [Service]
Type=simple Type=simple
Environment="HOME=/root" Environment="HOME=/root"
Environment="APP_PATH=/virt/flic-webhook-webpush"
ExecStartPre=-/usr/bin/env sh -c '/usr/bin/env docker kill virt-flic-webhook-webpush 2>/dev/null || true' ExecStartPre=-/usr/bin/env sh -c '/usr/bin/env docker kill virt-flic-webhook-webpush 2>/dev/null || true'
ExecStartPre=-/usr/bin/env sh -c '/usr/bin/env docker rm virt-flic-webhook-webpush 2>/dev/null || true' ExecStartPre=-/usr/bin/env sh -c '/usr/bin/env docker rm virt-flic-webhook-webpush 2>/dev/null || true'
ExecStartPre=/usr/bin/env sh -c 'touch ${APP_PATH}/subscriptions.json'
ExecStart=/usr/bin/env docker run \ ExecStart=/usr/bin/env docker run \
--rm \ --rm \
--name=virt-flic-webhook-webpush \ --name=virt-flic-webhook-webpush \
--log-driver=none \ --log-driver=none \
--network=traefik \ --network=traefik \
--env-file=/virt/flic-webhook-webpush/.env \ --env-file=${APP_PATH}/.env \
--label-file=/virt/flic-webhook-webpush/labels \ --label-file=${APP_PATH}/labels \
--mount type=bind,src=/virt/flic-webhook-webpush/subscriptions.json,dst=/app/subscriptions.json \ --mount type=bind,src=${APP_PATH}/subscriptions.json,dst=/app/subscriptions.json \
flic-webhook-webpush flic-webhook-webpush
ExecStop=-/usr/bin/env sh -c '/usr/bin/env docker kill virt-flic-webhook-webpush 2>/dev/null || true' ExecStop=-/usr/bin/env sh -c '/usr/bin/env docker kill virt-flic-webhook-webpush 2>/dev/null || true'