Last active
March 18, 2026 14:06
-
-
Save pHo9UBenaA/7fcb4359ab5fbadb8e83e8f3ccc42069 to your computer and use it in GitHub Desktop.
A lightweight local HTTP server that triggers macOS notifications via AppleScript.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Notifications triggered via osascript are handled by Script Editor, so make sure notifications are enabled for Script Editor in macOS System Settings. | |
| // Run: nohup node notify-server.mjs | |
| // Test: curl "http://host.docker.internal:8100/notify?title=Title&message=$(node -p 'encodeURIComponent(`Write your message here`)')" | |
| import http from 'node:http'; | |
| import { execFile } from 'node:child_process'; | |
| import { promisify } from 'node:util'; | |
| const execFileAsync = promisify(execFile); | |
| const HOST = '127.0.0.1'; | |
| const PORT = Number(process.env.PORT ?? 8100); | |
| function escapeAppleScriptString(value) { | |
| return String(value) | |
| .replaceAll('\\', '\\\\') | |
| .replaceAll('"', '\\"'); | |
| } | |
| function createNotificationScript({ title, message, sound }) { | |
| const escapedTitle = escapeAppleScriptString(title); | |
| const escapedMessage = escapeAppleScriptString(message); | |
| const escapedSound = escapeAppleScriptString(sound); | |
| return `display notification "${escapedMessage}" with title "${escapedTitle}" sound name "${escapedSound}"`; | |
| } | |
| async function handleNotify(req, res) { | |
| const url = new URL(req.url ?? '/', `http://${req.headers.host ?? `${HOST}:${PORT}`}`); | |
| const title = url.searchParams.get('title') ?? 'You have a notification'; | |
| const message = url.searchParams.get('message') ?? ''; | |
| const sound = url.searchParams.get('sound') ?? 'Funk'; | |
| const script = createNotificationScript({ title, message, sound }); | |
| try { | |
| await execFileAsync('osascript', ['-e', script]); | |
| res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); | |
| res.end('OK\n'); | |
| } catch (error) { | |
| console.error('Failed to send notification:', error); | |
| if (error?.stderr) { | |
| console.error(error.stderr); | |
| } | |
| res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); | |
| res.end('NG\n'); | |
| } | |
| } | |
| const server = http.createServer(async (req, res) => { | |
| const url = new URL(req.url ?? '/', `http://${req.headers.host ?? `${HOST}:${PORT}`}`); | |
| if (url.pathname === '/notify') { | |
| await handleNotify(req, res); | |
| return; | |
| } | |
| res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); | |
| res.end('Not Found\n'); | |
| }); | |
| server.listen(PORT, HOST, () => { | |
| console.log(`Notification server is listening at http://${HOST}:${PORT} ...`); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment