Adjusted internal documentation links and updated references within various Markdown files to reflect a new directory structure for audit and meta documents. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 4dee684a-a4b9-4c32-a818-3a27dae1b027 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 971b8c75-c322-4ede-98ea-eb5ae31d4e5c Replit-Helium-Checkpoint-Created: true
16 KiB
Messaging Subsystem Audit
Document Version: 1.0
Last Updated: January 2026
Scope: Pushover, IFTTT Maker, notification flows, deduplication, acknowledgment flows
1. Executive Summary
The Nightscout messaging subsystem enables critical alerts to reach caregivers through multiple channels. This audit examines notification generation, delivery mechanisms, and reliability considerations.
Messaging Overview
| Component | Purpose | Status |
|---|---|---|
| Internal Notifications | Alarm management | Core |
| Pushover | Push notifications | Integration |
| IFTTT Maker | Webhook automation | Integration |
| Apple Push (APN) | iOS notifications | Optional |
| WebSocket Alerts | Browser notifications | Core |
2. Notification Architecture
2.1 Notification Flow
Plugin checks data
↓
requestNotify() or requestSnooze()
↓
Notification Manager (lib/notifications.js)
↓
Process notifications
↓
emit('notification', notify)
↓
┌───────────────────────────────────────────┐
│ Event Bus │
└───────┬─────────────┬─────────────────────┘
│ │
▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Pushover │ │ Maker │ │ WebSocket │
│ Plugin │ │ Plugin │ │ Broadcast │
└─────────────┘ └─────────────┘ └─────────────┘
2.2 Notification Object
{
level: 1, // 0=INFO, 1=WARN, 2=URGENT
title: 'Low Glucose', // Short title
message: 'BG is 65 mg/dL', // Detailed message
plugin: plugin, // Source plugin reference
group: 'default', // Notification group
isAnnouncement: false, // Is user announcement
// Optional fields
clear: false, // Is clear notification
debug: {}, // Debug information
pushoverSound: 'climb', // Custom sound
// Computed
notifyhash: 'abc123' // Deduplication hash
}
2.3 Notification Levels
| Level | Name | Constant | Use Case |
|---|---|---|---|
| -2 | None | NONE |
Internal only |
| -1 | Low | LOW |
Debug/trace |
| 0 | Info | INFO |
Informational |
| 1 | Warning | WARN |
Attention needed |
| 2 | Urgent | URGENT |
Immediate action |
3. Notification Manager
3.1 Core Implementation
Location: lib/notifications.js
Key Functions:
// Request a notification
notifications.requestNotify = function(notify) {
if (!notify.level || !notify.title || !notify.message || !notify.plugin) {
console.error('Incomplete notification');
return;
}
notify.group = notify.group || 'default';
requests.notifies.push(notify);
};
// Request a snooze
notifications.requestSnooze = function(snooze) {
snooze.group = snooze.group || 'default';
requests.snoozes.push(snooze);
};
// Process all pending notifications
notifications.process = function() {
// Find highest alarm per group
// Check for snoozing
// Emit or suppress
};
3.2 Alarm Management
Alarm Object:
var Alarm = function(level, group, label) {
this.level = level;
this.group = group;
this.label = label;
this.silenceTime = 30 * 60 * 1000; // 30 minutes default
this.lastAckTime = 0;
this.lastEmitTime = null;
};
Alarm Processing:
- Collect all requested notifications
- Group by notification group
- Find highest priority per group
- Check if snoozed by any snooze request
- Check if silenced from previous ack
- Emit if not suppressed
3.3 Auto-Acknowledgment
When conditions return to normal:
function autoAckAlarms(group) {
for (var level = 1; level <= 2; level++) {
var alarm = getAlarm(level, group);
if (alarm.lastEmitTime) {
notifications.ack(alarm.level, group, 1); // 1ms silence
sendClear = true;
}
}
if (sendClear) {
ctx.bus.emit('notification', {
clear: true,
title: 'All Clear',
message: 'Auto ack\'d alarm(s)',
group: group
});
}
}
4. Push Notification Orchestrator
4.1 Implementation
Location: lib/server/pushnotify.js
function init(env, ctx) {
var receipts = new NodeCache({ stdTTL: 3600 });
var recentlySent = new NodeCache({ stdTTL: 900 });
pushnotify.emitNotification = function(notify) {
if (notify.clear) {
cancelPushoverNotifications();
sendMakerAllClear(notify);
return;
}
// Check deduplication
var key = notify.notifyhash || generateHash(notify);
if (recentlySent.get(key)) {
console.log('Skipping duplicate notification');
return;
}
// Send to providers
ctx.pushover.send(notify, callback);
ctx.maker.sendEvent(notify, callback);
};
}
4.2 Deduplication
Strategy:
- Generate hash from notification content
- Cache recently sent hashes (15 minute TTL)
- Skip if hash exists in cache
Hash Generation:
function generateHash(notify) {
const crypto = require('crypto');
const hash = crypto.createHash('sha1');
hash.update(notify.title + notify.message);
return hash.digest('hex').substring(0, 16);
}
4.3 Receipt Tracking (Pushover)
For emergency priority notifications:
var receipts = new NodeCache({ stdTTL: 3600 });
// Store receipt from Pushover
receipts.set(receipt, notify);
// Periodic check
pushnotify.checkReceipts = function() {
receipts.keys().forEach(function(receipt) {
ctx.pushover.checkReceipt(receipt, function(err, result) {
if (result.acknowledged) {
// User acknowledged, remove from cache
receipts.del(receipt);
}
});
});
};
5. Pushover Integration
5.1 Configuration
Location: lib/plugins/pushover.js
Environment Variables:
PUSHOVER_API_TOKEN=your-app-token
PUSHOVER_USER_KEY=user-or-group-key
PUSHOVER_ALARM_KEY=key-for-alarms
PUSHOVER_ANNOUNCEMENT_KEY=key-for-announcements
BASE_URL=https://nightscout.example.com
5.2 Key Management
var pushoverAPI = {
userKeys: env.extendedSettings.pushover.userKey.split(' '),
alarmKeys: (env.extendedSettings.pushover.alarmKey || userKey).split(' '),
announcementKeys: (env.extendedSettings.pushover.announcementKey || userKey).split(' '),
apiToken: env.extendedSettings.pushover.apiToken
};
function selectKeys(notify) {
if (notify.isAnnouncement) {
return pushoverAPI.announcementKeys;
} else if (ctx.levels.isAlarm(notify.level)) {
return pushoverAPI.alarmKeys;
}
return pushoverAPI.userKeys;
}
5.3 Priority Mapping
| Nightscout Level | Pushover Priority | Behavior |
|---|---|---|
| INFO | 0 (Normal) | Normal push |
| WARN | 1 (High) | Bypasses quiet hours |
| URGENT | 2 (Emergency) | Repeats until ack'd |
5.4 Message Sending
pushover.send = function(notify, callback) {
var selectedKeys = selectKeys(notify);
selectedKeys.forEach(function(userKey) {
var msg = {
message: notify.message,
title: notify.title,
priority: mapPriority(notify.level),
sound: notify.pushoverSound || 'gamelan',
callback: env.base_url + '/api/v1/notifications/pushovercallback',
timestamp: Math.round(Date.now() / 1000)
};
if (msg.priority === 2) {
msg.retry = 120; // Retry every 2 minutes
msg.expire = 3600; // Expire after 1 hour
}
pushoverClient.send(msg, userKey, callback);
});
};
5.5 Callback Handling
Endpoint: POST /api/v1/notifications/pushovercallback
api.post('/notifications/pushovercallback', function(req, res) {
if (ctx.pushnotify.pushoverAck(req.body)) {
res.sendStatus(200);
} else {
res.sendStatus(500);
}
});
6. IFTTT Maker Integration
6.1 Configuration
Location: lib/plugins/maker.js
Environment Variables:
MAKER_KEY=your-ifttt-webhooks-key
MAKER_ANNOUNCEMENT_KEY=optional-separate-key
6.2 Event Types
| Event Name | Trigger | Value1 | Value2 | Value3 |
|---|---|---|---|---|
ns-event |
Any event | Title | Message | Timestamp |
ns-allclear |
Alarm cleared | Title | Message | - |
ns-info |
INFO level | Title | Message | - |
ns-warning |
WARN level | Title | Message | - |
ns-urgent |
URGENT level | Title | Message | - |
ns-{plugin} |
Plugin event | Title | Message | - |
ns-{level}-{eventName} |
Specific event | Title | Message | - |
6.3 Event Sending
maker.sendEvent = function(notify, callback) {
if (!keys || keys.length === 0) return callback();
var events = [
'ns-event',
'ns-' + levelName(notify.level),
'ns-' + notify.plugin.name
];
if (notify.eventName) {
events.push('ns-' + levelName(notify.level) + '-' + notify.eventName);
}
events.forEach(function(event) {
keys.forEach(function(key) {
var url = 'https://maker.ifttt.com/trigger/' + event + '/with/key/' + key;
request.post({
url: url,
json: {
value1: notify.title,
value2: notify.message,
value3: Date.now()
}
}, callback);
});
});
};
6.4 All Clear Event
maker.sendAllClear = function(notify, callback) {
if (Date.now() - lastAllClear > 30 * 60 * 1000) {
lastAllClear = Date.now();
var key = keys[0];
var url = 'https://maker.ifttt.com/trigger/ns-allclear/with/key/' + key;
request.post({
url: url,
json: {
value1: notify.title,
value2: notify.message
}
}, callback);
}
};
7. WebSocket Notification Delivery
7.1 Browser Notifications
Location: lib/server/websocket.js, lib/api3/alarmSocket.js
Broadcast Flow:
ctx.bus.on('notification', function(notify) {
var event = mapLevelToEvent(notify.level);
if (notify.isAnnouncement) {
io.emit('announcement', notify);
} else if (notify.clear) {
io.emit('clear_alarm', {});
} else {
io.emit(event, notify); // 'alarm' or 'urgent_alarm'
}
});
7.2 Client-Side Handling
socket.on('alarm', function(alarm) {
// Show notification
showDesktopNotification(alarm);
// Play sound
playAlarmSound(alarm.level);
// Update UI
showAlarmModal(alarm);
});
socket.on('urgent_alarm', function(alarm) {
// More aggressive notification
showUrgentNotification(alarm);
playUrgentSound();
});
socket.on('clear_alarm', function() {
// Dismiss notifications
hideAlarmModal();
stopAlarmSound();
});
7.3 Desktop Notifications
function showDesktopNotification(alarm) {
if (Notification.permission === 'granted') {
new Notification(alarm.title, {
body: alarm.message,
icon: '/images/logo.png',
tag: 'nightscout-alarm-' + alarm.level,
requireInteraction: true
});
}
}
8. Acknowledgment Flow
8.1 Acknowledgment Sources
| Source | Method | Scope |
|---|---|---|
| Web UI | WebSocket ack |
Local + server |
| Pushover | Callback POST | Server + cancel loop |
| API | GET /notifications/ack | Server |
8.2 Web Acknowledgment
// Client sends ack
socket.emit('ack', level, group, silenceTime);
// Server handles
socket.on('ack', function(level, group, silenceTime) {
ctx.notifications.ack(level, group, silenceTime);
// Broadcast clear to all clients
ctx.bus.emit('notification', {
clear: true,
title: 'All Clear',
message: 'Alarm acknowledged',
group: group
});
});
8.3 API Acknowledgment
Endpoint: GET /api/v1/notifications/ack
Parameters:
level- Alarm level (1 or 2)group- Notification grouptime- Silence duration (ms)
api.get('/notifications/ack',
ctx.authorization.isPermitted('notifications:*:ack'),
function(req, res) {
var level = Number(req.query.level);
var group = req.query.group || 'default';
var time = Number(req.query.time) || 1800000; // 30 min default
ctx.notifications.ack(level, group, time);
res.sendStatus(200);
}
);
8.4 Silence Duration
| Method | Default Duration | Configurable |
|---|---|---|
| Web UI | 30 minutes | Yes (button presets) |
| Pushover | Until expired | Implicit |
| API | 30 minutes | Yes (query param) |
9. Reliability Considerations
9.1 Failure Modes
| Failure | Impact | Mitigation |
|---|---|---|
| Pushover API down | No push notifications | Retry logic, alternative channel |
| IFTTT unavailable | No webhook events | Silent failure (acceptable) |
| Network partition | Delayed notifications | Queue locally, retry |
| Server crash | Lost in-memory state | Events reconstructed on reload |
9.2 Retry Logic
Current State: Limited retry for Pushover, none for Maker
Recommendation:
async function sendWithRetry(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (i === maxRetries - 1) throw err;
await sleep(delay * Math.pow(2, i));
}
}
}
9.3 Queue Persistence
Current State: In-memory only
Recommendation:
- Add Redis queue for pending notifications
- Survive server restarts
- Enable horizontal scaling
10. Monitoring and Logging
10.1 Current Logging
console.info('EMITTING ALARM:', JSON.stringify(notify));
console.log('Skipping duplicate notification');
console.error('Pushover send failed:', err);
10.2 Recommended Metrics
| Metric | Type | Purpose |
|---|---|---|
notifications_emitted_total |
Counter | Total by level |
notifications_acknowledged_total |
Counter | Ack rate |
pushover_send_duration_ms |
Histogram | Latency |
pushover_failures_total |
Counter | Error rate |
maker_events_sent_total |
Counter | Volume |
10.3 Alerting Recommendations
| Condition | Threshold | Action |
|---|---|---|
| Pushover failure rate | >10% in 5min | Alert ops |
| Notification latency | >30s p99 | Warn |
| Queue depth | >100 | Scale |
11. Issues and Recommendations
11.1 Critical Issues
| Issue | Impact | Recommendation |
|---|---|---|
| No message queue | Lost notifications on crash | Add Redis queue |
Deprecated request library |
Security risk | Migrate to axios |
| No retry logic for IFTTT | Silent failures | Add retry with backoff |
11.2 Improvements
| Area | Current | Recommended |
|---|---|---|
| Dedup window | 15 minutes | Configurable |
| Retry strategy | None | Exponential backoff |
| Failure logging | Basic | Structured logging |
| Rate limiting | None | Per-channel limits |
11.3 Additional Channels
Consider adding support for:
-
Twilio SMS:
- Critical for non-smartphone users
- Reliable delivery
-
Email:
- Summary/digest notifications
- Non-critical alerts
-
Slack/Discord:
- Team notifications
- Care team coordination
-
Apple Push (APN):
- Native iOS app support
- Already has dependency (
@parse/node-apn)
12. Security Considerations
12.1 Sensitive Data
| Data | Risk | Mitigation |
|---|---|---|
| API keys | Exposure | Environment variables only |
| User keys | Exposure | Never log full keys |
| Health data in messages | Privacy | Minimal message content |
12.2 Callback Security
Pushover Callback:
- No signature verification
- Relies on obscure URL
- Consider adding HMAC signature
12.3 Rate Limiting
Current State: Deduplication only (15 min window)
Recommendation:
- Add per-minute rate limits per channel
- Prevent notification storms
- Log rate limit events