75 lines
2.7 KiB
JavaScript
75 lines
2.7 KiB
JavaScript
// 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}`));
|