diff --git a/.env.example b/.env.example index 72d89ce..7410146 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,7 @@ VAPID_SUBJECT=mailto:mailto:admin@virtonline.eu # Contact email/URL for push ser # --- Server Configuration --- PORT=3000 # Internal port for the Node.js app SUBSCRIPTIONS_FILE=subscriptions.json # Path inside the container +DEFAULT_BUTTON_NAME=game-button # Default button name to use when not specified # --- Authentication (Optional) --- # If both USERNAME and PASSWORD are set, Basic Auth will be enabled for: diff --git a/README.md b/README.md index caa4930..de7a074 100644 --- a/README.md +++ b/README.md @@ -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. * `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. + * `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. * `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. * **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"). + * `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": "game", + "button_id": "game-button", // Optional, defaults to DEFAULT_BUTTON_NAME environment variable "subscription": { "endpoint": "https://your_pwa_push_endpoint...", "expirationTime": null, @@ -167,7 +168,7 @@ In your Flic app or Flic Hub SDK interface: ``` * **Responses:** * `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). * `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:** * `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). + * `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). @@ -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. -**Note:** Replace ``, ``, ``, and `` with your actual values. +**Note:** Replace ``, ``, ``, and `` with your actual values. The `Button-Name` header is optional and will default to the value of `DEFAULT_BUTTON_NAME` if not provided. ```bash # Generate Base64 credentials (run this once) diff --git a/server.js b/server.js index e2ad2d3..3d2e5c0 100644 --- a/server.js +++ b/server.js @@ -14,6 +14,7 @@ const vapidPublicKey = process.env.VAPID_PUBLIC_KEY; const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY; const vapidSubject = process.env.VAPID_SUBJECT; // mailto: or https: const subscriptionsFilePath = process.env.SUBSCRIPTIONS_FILE || path.join(__dirname, 'subscriptions.json'); +const defaultButtonName = process.env.DEFAULT_BUTTON_NAME || 'game-button'; // Basic Authentication Credentials const basicAuthUsername = process.env.BASIC_AUTH_USERNAME; const basicAuthPassword = process.env.BASIC_AUTH_PASSWORD; @@ -233,20 +234,22 @@ const authenticateBasic = (req, res, next) => { // Subscribe endpoint: Add a new button->subscription mapping // Apply Basic Authentication app.post('/subscribe', authenticateBasic, async (req, res) => { - const { button_id, subscription } = req.body; + let { button_id, subscription } = req.body; logger.debug('All headers received:'); Object.keys(req.headers).forEach(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}`); - // Basic Validation - 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' }); - } + // Basic Validation - now we only validate subscription since button_id will use default if not provided 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'); 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 app.get('/webhook/:click_type', authenticateBasic, async (req, res) => { // 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']; // Get click_type from URL path 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'}`); // Basic validation - if (!buttonName || !click_type) { - logger.warn(`Webhook Error: Missing Button-Name header or click_type query parameter`); - return res.status(400).json({ message: 'Bad Request: Missing Button-Name header or click_type query parameter' }); + if (!click_type) { + logger.warn(`Webhook Error: Missing 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 diff --git a/virt-flic-webhook-webpush.service b/virt-flic-webhook-webpush.service index a914971..c9fd808 100644 --- a/virt-flic-webhook-webpush.service +++ b/virt-flic-webhook-webpush.service @@ -7,17 +7,20 @@ DefaultDependencies=no [Service] Type=simple 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 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 \ --rm \ --name=virt-flic-webhook-webpush \ - --log-driver=none \ + --log-driver=none \ --network=traefik \ - --env-file=/virt/flic-webhook-webpush/.env \ - --label-file=/virt/flic-webhook-webpush/labels \ - --mount type=bind,src=/virt/flic-webhook-webpush/subscriptions.json,dst=/app/subscriptions.json \ + --env-file=${APP_PATH}/.env \ + --label-file=${APP_PATH}/labels \ + --mount type=bind,src=${APP_PATH}/subscriptions.json,dst=/app/subscriptions.json \ flic-webhook-webpush ExecStop=-/usr/bin/env sh -c '/usr/bin/env docker kill virt-flic-webhook-webpush 2>/dev/null || true'