From 7c66c10b6db82288df71fc301dd960100eb928b1 Mon Sep 17 00:00:00 2001 From: formless63 <59035664+formless63@users.noreply.github.com> Date: Sat, 28 Feb 2026 10:31:18 -0500 Subject: [PATCH] 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) --- src/features/notifications/providers/ntfy.ts | 37 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/features/notifications/providers/ntfy.ts b/src/features/notifications/providers/ntfy.ts index d51451b5..fbbc6111 100644 --- a/src/features/notifications/providers/ntfy.ts +++ b/src/features/notifications/providers/ntfy.ts @@ -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 = {