1
0
Fork 0
mirror of https://github.com/imjasonh/webpush synced 2026-07-06 23:52:25 +00:00
webpush/example/static/index.html
copilot-swe-agent[bot] 191144839a Use go:embed for static content and simplify README to reference example
Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com>
2025-11-24 22:28:44 +00:00

186 lines
6.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Push Demo</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
background: #f5f5f5;
}
.card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
h1 { color: #333; }
button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin: 5px;
}
button:hover { background: #0056b3; }
button:disabled { background: #ccc; cursor: not-allowed; }
button.danger { background: #dc3545; }
button.danger:hover { background: #c82333; }
button.success { background: #28a745; }
#status {
padding: 10px;
border-radius: 4px;
margin-top: 10px;
}
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.info { background: #cce5ff; color: #004085; }
</style>
</head>
<body>
<div class="card">
<h1>🔔 Web Push Demo</h1>
<p>Subscribe to receive push notifications from this server.</p>
<p>Notifications are sent:</p>
<ul>
<li>Every minute automatically</li>
<li>When someone visits <a href="/ping">/ping</a></li>
</ul>
</div>
<div class="card">
<h2>Subscription</h2>
<button id="subscribeBtn" onclick="subscribe()">Subscribe to Notifications</button>
<button id="unsubscribeBtn" onclick="unsubscribe()" class="danger" disabled>Unsubscribe</button>
<div id="status"></div>
</div>
<div class="card">
<h2>Test Push</h2>
<button onclick="sendPing()" class="success">Send Ping to All Subscribers</button>
</div>
<script>
let vapidPublicKey = '';
let currentSubscription = null;
// Convert base64 to Uint8Array
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function setStatus(message, type) {
const status = document.getElementById('status');
status.textContent = message;
status.className = type;
}
async function init() {
// Get VAPID public key
const resp = await fetch('/api/vapid-public-key');
const data = await resp.json();
vapidPublicKey = data.publicKey;
// Check if already subscribed
if ('serviceWorker' in navigator && 'PushManager' in window) {
const registration = await navigator.serviceWorker.register('/sw.js');
const subscription = await registration.pushManager.getSubscription();
if (subscription) {
currentSubscription = subscription;
document.getElementById('subscribeBtn').disabled = true;
document.getElementById('unsubscribeBtn').disabled = false;
setStatus('You are subscribed to notifications', 'success');
}
} else {
setStatus('Push notifications not supported in this browser', 'error');
document.getElementById('subscribeBtn').disabled = true;
}
}
async function subscribe() {
try {
const permission = await Notification.requestPermission();
if (permission !== 'granted') {
setStatus('Notification permission denied', 'error');
return;
}
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
});
// Send to server
const resp = await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscription.toJSON())
});
if (resp.ok) {
currentSubscription = subscription;
document.getElementById('subscribeBtn').disabled = true;
document.getElementById('unsubscribeBtn').disabled = false;
setStatus('Successfully subscribed!', 'success');
} else {
setStatus('Failed to subscribe: ' + await resp.text(), 'error');
}
} catch (err) {
setStatus('Error: ' + err.message, 'error');
}
}
async function unsubscribe() {
try {
if (currentSubscription) {
await fetch('/api/unsubscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ endpoint: currentSubscription.endpoint })
});
await currentSubscription.unsubscribe();
currentSubscription = null;
}
document.getElementById('subscribeBtn').disabled = false;
document.getElementById('unsubscribeBtn').disabled = true;
setStatus('Successfully unsubscribed', 'info');
} catch (err) {
setStatus('Error: ' + err.message, 'error');
}
}
async function sendPing() {
try {
const resp = await fetch('/ping');
if (resp.ok) {
setStatus('Ping sent to all subscribers!', 'success');
} else {
setStatus('Failed to send ping', 'error');
}
} catch (err) {
setStatus('Error: ' + err.message, 'error');
}
}
init();
</script>
</body>
</html>