Fix ntfy: URL-encode Title header to avoid non-ASCII rejection

HTTP headers must be ASCII-only. Emoji in the Title header caused
"Invalid character in header content" errors. ntfy.sh accepts
URL-encoded header values, so encodeURIComponent() fixes it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-06 16:26:31 -04:00
1 parent 8d50d3cb61
commit d46a37a219
2 files changed
+18 -10

No files matched your search

+5 -1
View File
@@ -1567,7 +1567,11 @@ app.post('/api/test-ntfy', async (req, res) => {
const base = (ntfy_url || 'https://ntfy.sh').replace(/\/$/, ''); const base = (ntfy_url || 'https://ntfy.sh').replace(/\/$/, '');
try { try {
await axios.post(`${base}/${ntfy_topic}`, 'This is a test notification from SkyWatch', { await axios.post(`${base}/${ntfy_topic}`, 'This is a test notification from SkyWatch', {
headers: { Title: '🔔 SkyWatch Test', Priority: 'default', Tags: 'white_check_mark' }, headers: {
Title: encodeURIComponent('SkyWatch Test'),
Priority: 'default',
Tags: 'white_check_mark',
},
}); });
res.json({ message: 'Test notification sent to ntfy!' }); res.json({ message: 'Test notification sent to ntfy!' });
} catch (error) { } catch (error) {
+13 -9
View File
@@ -179,12 +179,16 @@ class NotificationService {
const ntfyTopic = this.config.ntfy_topic; const ntfyTopic = this.config.ntfy_topic;
if (ntfyTopic) { if (ntfyTopic) {
try { try {
await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, const groupedBody = pendingAlerts.map(({ resource, incident }) =>
pendingAlerts.map(({ resource, incident }) => `${incident.type === 'started' ? '[DOWN]' : '[UP]'} ${resource.name}`
`${incident.type === 'started' ? '🔴' : '🟢'} ${resource.name}` ).join('\n');
).join('\n'), await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, groupedBody, {
{ headers: { Title: `⚠️ ${pendingAlerts.length} monitors changed`, Priority: 'high', Tags: 'warning' } } headers: {
); Title: encodeURIComponent(`${pendingAlerts.length} monitors changed status`),
Priority: 'high',
Tags: 'warning',
},
});
} catch (_) {} } catch (_) {}
} }
} }
@@ -450,15 +454,15 @@ class NotificationService {
const isDown = type === 'started'; const isDown = type === 'started';
const isSlow = type === 'slow'; const isSlow = type === 'slow';
const title = isDown ? `🔴 ${resource.name} is DOWN` : isSlow ? `🟡 ${resource.name} is SLOW` : `🟢 ${resource.name} is UP`; const titleText = isDown ? `${resource.name} is DOWN` : isSlow ? `${resource.name} is SLOW` : `${resource.name} is UP`;
const priority = isDown ? 'high' : isSlow ? 'default' : 'low'; const priority = isDown ? 'high' : isSlow ? 'default' : 'low';
const tags = isDown ? 'red_circle,warning' : isSlow ? 'yellow_circle' : 'green_circle,white_check_mark'; const tags = isDown ? 'red_circle,warning' : isSlow ? 'yellow_circle' : 'green_circle,white_check_mark';
const body = resource.url ? `${resource.url}` : resource.name; const body = resource.url || resource.name;
try { try {
await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, body, { await axios.post(`${ntfyUrl.replace(/\/$/, '')}/${ntfyTopic}`, body, {
headers: { headers: {
Title: title, Title: encodeURIComponent(titleText),
Priority: priority, Priority: priority,
Tags: tags, Tags: tags,
}, },