fix(ntfy): fix silent delivery, malformed payload, and tag fallthrough

### Description
Fixes three issues with the `ntfy` notification provider to improve mobile delivery and readability.

**Previous Behavior:**
1. **Silent Delivery:** Priority was hardcoded to `1` (Min). On Android and iOS, priority 1 notifications are delivered completely silently (no sound, no vibration, hidden from lock screen). 
2. **Malformed Payload:** The `payload.data` object was appended as a raw, stringified JSON blob, making the notification body difficult to read.
3. **Tag Fallthrough:** The chained ternary logic for tags caused `error` level events to fall through to the default `information_source` icon.

### Changes Made
- **Dynamic Priority Mapping:** Added `getPriority` to map severity levels to Ntfy's 1-5 scale. `info`/default maps to `2` (quiet ping), `warning` to `3` (default alert), `error` to `4` (high), and `critical` to `5` (max).
- **Plain Text Data Formatting:** Added `formatData` to parse the JSON payload into a clean `- key: value` list. Markdown was purposely avoided to ensure clean rendering in mobile OS lock screen previews.
- **Improved Tagging:** Refactored into a `getTags` helper. Added a base `floppy_disk` icon to instantly identify the notifications, and properly mapped `error` payloads to the `x` icon.

### Related Issues
None (Direct Fix)
This commit is contained in:
formless63
2026-02-28 10:31:18 -05:00
committed by GitHub
parent 8c69fdef95
commit 7c66c10b6d
+34 -3
View File
@@ -1,5 +1,36 @@
import type {EventPayload, DispatchResult} from '../types';
const getPriority = (level?: string): number => {
switch (level) {
case 'critical': return 5;
case 'error': return 4;
case 'warning': return 3;
case 'info':
default: return 2;
}
};
const formatData = (data: any): string => {
if (!data || typeof data !== 'object') return '';
return '\n\nDetails:\n' + Object.entries(data)
.map(([key, value]) => {
const stringVal = value !== null && typeof value === 'object' ? JSON.stringify(value) : String(value);
return `- ${key}: ${stringVal}`;
})
.join('\n');
};
const getTags = (level?: string): string[] => {
const baseTags = ['floppy_disk'];
if (level === 'critical') baseTags.push('rotating_light');
else if (level === 'error') baseTags.push('x');
else if (level === 'warning') baseTags.push('warning');
else baseTags.push('information_source');
return baseTags;
};
export async function sendNtfy(
config: { ntfyServerUrl?: string; ntfyTopic: string; ntfyToken?: string, ntfyUsername?: string, ntfyPassword?: string },
payload: EventPayload
@@ -11,9 +42,9 @@ export async function sendNtfy(
const body = {
topic: ntfyTopic,
title: payload.title,
message: payload.message + (payload.data ? `\n\nData:\n${JSON.stringify(payload.data, null, 2)}` : ''),
priority: 1,
tags: [payload.level === 'critical' ? 'rotating_light' : payload.level === 'warning' ? 'warning' : 'information_source'],
message: payload.message + formatData(payload.data),
priority: getPriority(payload.level),
tags: getTags(payload.level),
};
const headers: Record<string, string> = {