| name | notification-scheduler |
| description | Configure and manage scheduled dose reminders using node-cron and Web Notifications API. Use when building or debugging notification delivery in medication or health tracking apps. |
notification-scheduler
Server-side cron scheduling combined with client-side Web Notifications API for medication reminders.
Architecture
Two-layer delivery:
- Server: node-cron fires at each scheduled dose time, emits WebSocket event
- Client: receives WebSocket event, shows browser notification via Notifications API
Server setup (node-cron)
import cron from 'node-cron';
import { WebSocketServer } from 'ws';
interface ScheduledJob {
taskId: string;
medicationId: number;
scheduledTime: string;
task: cron.ScheduledTask;
}
const jobs = new Map<string, ScheduledJob>();
function scheduleReminder(medicationId: number, time: string, leadMins: number): void {
const [hour, minute] = time.split(':').map(Number);
const reminderMin = minute - leadMins;
const cronMin = ((reminderMin % 60) + 60) % 60;
const cronHour = hour + (reminderMin < 0 ? -1 : 0);
const expression = `${cronMin} ${cronHour} * * *`;
const task = cron.schedule(expression, () => {
broadcast({ type: 'dose_reminder', medicationId, scheduledTime: time });
});
const key = `${medicationId}-${time}`;
jobs.set(key, { taskId: key, medicationId, scheduledTime: time, task });
}
function broadcast(data: object): void {
const message = JSON.stringify(data);
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
Client setup (Web Notifications API)
export async function requestPermission(): Promise<boolean> {
if (!('Notification' in window)) return false;
if (Notification.permission === 'granted') return true;
if (Notification.permission === 'denied') return false;
const result = await Notification.requestPermission();
return result === 'granted';
}
export function showDoseReminder(medicationName: string, scheduledTime: string): void {
if (Notification.permission !== 'granted') return;
const n = new Notification('Dose reminder', {
body: `Time to take ${medicationName} (scheduled )`,
: ,
: ,
: ,
});
n. = {
.();
n.();
};
}
(): {
ws = ();
ws. = {
data = .(event. ) {
: ;
: ;
: ;
};
(data. === ) {
(data., data.);
}
};
ws;
}
React integration
import { useEffect } from 'react';
import { requestPermission, showDoseReminder, connectReminders } from './lib/notifications';
function useNotifications(medications: Medication[]) {
useEffect(() => {
requestPermission();
const ws = connectReminders((medicationId, time) => {
const med = medications.find((m) => m.id === medicationId);
if (med) showDoseReminder(med.name, time);
});
return () => ws.close();
}, [medications]);
}
Scheduling on app startup
When the server starts, load all active schedule_times from SQLite and call scheduleReminder for each. When a medication is added, updated, or deleted, cancel existing jobs for that medication and reschedule.
async function initScheduler(db: Database, leadMins: number): Promise<void> {
const times = db.prepare(
'SELECT st.medication_id, st.time_of_day FROM schedule_times st JOIN medications m ON m.id = st.medication_id WHERE m.is_active = 1'
).all() as Array<{ medication_id: number; time_of_day: string }>;
for (const { medication_id, time_of_day } of times) {
scheduleReminder(medication_id, time_of_day, leadMins);
}
}
Troubleshooting
Notifications not appearing
- Check
Notification.permission in browser console - must be 'granted'
- Some browsers block notifications on
http://localhost - check browser notification settings
- On macOS, check System Settings > Notifications for the browser
Jobs not firing
- Verify
cron.schedule expression with cron.validate(expression)
- Check server timezone matches user's timezone expectation
- Ensure
node-cron is installed: pnpm add node-cron