feat(notifications): Bark notification provider (#1495)

Bark is the open-source, account-free iOS push app (self-hostable
via bark-server). Configure with just the device key from the app;
the server URL defaults to the official api.day.app relay and
accepts a self-hosted instance. Optional settings: notification
Group, Sound, and iOS Interruption Level - Time Sensitive breaks
through scheduled summaries, Critical bypasses Silent mode and
Focus, Passive delivers silently.

bark-server can wrap failures in an HTTP 200 body ({"code": 400}),
so the sender checks the body code as well as the HTTP status.
Unknown interruption levels are dropped rather than forwarded.
This commit is contained in:
maziggy
2026-07-28 10:58:08 +02:00
parent 49f9d7120d
commit 62ba751278
22 changed files with 271 additions and 19 deletions
@@ -12,7 +12,7 @@ interface AddNotificationModalProps {
onClose: () => void;
}
const PROVIDER_VALUES: ProviderType[] = ['email', 'telegram', 'discord', 'ntfy', 'pushover', 'callmebot', 'webhook', 'homeassistant'];
const PROVIDER_VALUES: ProviderType[] = ['email', 'telegram', 'discord', 'ntfy', 'pushover', 'bark', 'callmebot', 'webhook', 'homeassistant'];
export function AddNotificationModal({ provider, onClose }: AddNotificationModalProps) {
const { t } = useTranslation();
@@ -282,6 +282,20 @@ export function AddNotificationModal({ provider, onClose }: AddNotificationModal
{ key: 'service', label: 'Home Assistant Service', placeholder: 'notify.mobile_app_myphone', type: 'text', required: false },
{ key: 'data', label: 'Data (JSON, optional)', placeholder: '{"priority": "high", "ttl": 0, "channel": "3D Printing"}', type: 'textarea', required: false },
];
case 'bark':
return [
{ key: 'device_key', label: 'Device Key', placeholder: 'Your Bark device key', type: 'text', required: true },
{ key: 'server', label: 'Server URL', placeholder: 'https://api.day.app', type: 'text', required: false },
{ key: 'group', label: 'Group', placeholder: 'Bambuddy', type: 'text', required: false },
{ key: 'sound', label: 'Sound', placeholder: 'minuet', type: 'text', required: false },
{ key: 'level', label: 'Interruption Level', type: 'select', required: false, options: [
{ value: '', label: 'Default' },
{ value: 'active', label: 'Active' },
{ value: 'timeSensitive', label: 'Time Sensitive' },
{ value: 'critical', label: 'Critical (bypasses Silent/Focus)' },
{ value: 'passive', label: 'Passive (no sound)' },
]},
];
default:
return [];
}