// server.js — minimal Web Push backend. // // Setup: // npm init -y // npm install express web-push // npx web-push generate-vapid-keys # paste the two keys below // node server.js // // Serves the static test page from ./public, stores subscriptions in memory, // and exposes /api/send to push to every stored subscription. const express = require('express'); const webpush = require('web-push'); const path = require('path'); const app = express(); app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); // --- VAPID keys ----------------------------------------------------------- // Paste the output of `npx web-push generate-vapid-keys` here. const VAPID_PUBLIC = 'BDjn64xCA8SGTAZy8Tes-Kqm44sgFTyZlgXRfpeKvUjSSFdnfI4ru0jY64JExOlfV4qNke7kqKb6B_sBUmbTdIY'; const VAPID_PRIVATE = 'u2cXEA3HC2FUY7f4FedQNqnAcOeFdfG655WK_ktYWbQ'; webpush.setVapidDetails( 'mailto:simon@unisolve.com.au', // contact the push service can reach if there's a problem VAPID_PUBLIC, VAPID_PRIVATE ); // Expose the public key so the page can subscribe without hardcoding it. app.get('/api/vapidPublicKey', (req, res) => res.send(VAPID_PUBLIC)); // --- Subscription store --------------------------------------------------- // In-memory for testing. Use a real table (one row per device) in production. const subscriptions = new Map(); // endpoint -> subscription object app.post('/api/subscribe', (req, res) => { const sub = req.body; if (!sub || !sub.endpoint) return res.status(400).json({ error: 'bad subscription' }); subscriptions.set(sub.endpoint, sub); console.log('Stored subscription. Total:', subscriptions.size); res.status(201).json({ ok: true }); }); // --- Send a push to everyone ---------------------------------------------- // POST /api/send { "title": "...", "body": "..." } app.post('/api/send', async (req, res) => { const payload = JSON.stringify({ title: req.body.title || 'Test push', body: req.body.body || 'Sent from the server at ' + new Date().toLocaleTimeString() }); const results = []; for (const [endpoint, sub] of subscriptions) { try { await webpush.sendNotification(sub, payload); results.push({ endpoint, ok: true }); } catch (err) { // 404/410 mean the subscription is dead — drop it so we stop trying. if (err.statusCode === 404 || err.statusCode === 410) { subscriptions.delete(endpoint); results.push({ endpoint, ok: false, removed: true }); } else { results.push({ endpoint, ok: false, status: err.statusCode }); } } } res.json({ sent: results.length, results }); }); const PORT = process.env.PORT || 8000; app.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`));