default button name
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
11
README.md
11
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.
|
* `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)
|
||||||
|
|||||||
23
server.js
23
server.js
@@ -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
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
Reference in New Issue
Block a user