feat: add webchat pwa push subscription support

This commit is contained in:
William Valentin
2026-02-18 10:46:55 -08:00
parent 02fa604c7c
commit 8234cc93f3
17 changed files with 743 additions and 2 deletions
+88
View File
@@ -4,6 +4,12 @@
* Read-only config view (redacted), editable hook patterns,
* tool list, and channel overview.
*/
import {
isPushSupported,
getPushStatus,
enablePushNotifications,
disablePushNotifications,
} from '../lib/pwa.js';
function escapeHtml(text) {
const div = document.createElement('div');
@@ -14,6 +20,75 @@ function escapeHtml(text) {
let _client = null;
let _el = null;
function describePushStatus(status) {
if (!status.supported) {
return status.message || 'Push notifications are not supported in this browser.';
}
if (!status.enabled) {
return 'Gateway push is disabled (`server.webchat_push.enabled: false`).';
}
if (!status.configured) {
return 'Gateway push key is missing (`server.webchat_push.vapid_public_key`).';
}
if (status.permission === 'denied') {
return 'Browser notifications are blocked. Allow notifications in browser settings.';
}
if (status.subscribed) {
return 'Push notifications are enabled for this browser.';
}
return 'Push is configured. Click Enable to subscribe this browser.';
}
async function renderPushStatus() {
const statusEl = _el.querySelector('#push-status');
const enableBtn = _el.querySelector('#push-enable');
const disableBtn = _el.querySelector('#push-disable');
if (!statusEl || !enableBtn || !disableBtn) {
return;
}
try {
const status = await getPushStatus();
statusEl.textContent = describePushStatus(status);
statusEl.className = status.subscribed ? 'text-sm text-success' : 'text-sm text-muted';
enableBtn.disabled = !status.supported || !status.enabled || !status.configured || status.subscribed;
disableBtn.disabled = !status.supported || !status.subscribed;
} catch (err) {
statusEl.textContent = `Push status error: ${err.message}`;
statusEl.className = 'text-sm text-error';
enableBtn.disabled = true;
disableBtn.disabled = true;
}
}
async function onEnablePush() {
const statusEl = _el.querySelector('#push-status');
if (!statusEl) {return;}
statusEl.textContent = 'Enabling push notifications...';
statusEl.className = 'text-sm text-muted';
try {
await enablePushNotifications();
await renderPushStatus();
} catch (err) {
statusEl.textContent = `Enable failed: ${err.message}`;
statusEl.className = 'text-sm text-error';
}
}
async function onDisablePush() {
const statusEl = _el.querySelector('#push-status');
if (!statusEl) {return;}
statusEl.textContent = 'Disabling push notifications...';
statusEl.className = 'text-sm text-muted';
try {
await disablePushNotifications();
await renderPushStatus();
} catch (err) {
statusEl.textContent = `Disable failed: ${err.message}`;
statusEl.className = 'text-sm text-error';
}
}
async function loadSettings() {
if (!_client || !_el) {return;}
@@ -52,6 +127,16 @@ async function loadSettings() {
_el.innerHTML = `
<h1 class="page-title">Settings</h1>
<h2 class="section-title">WebChat Push Notifications</h2>
<div class="settings-section">
${isPushSupported() ? '' : '<div class="text-sm text-muted">This browser does not support PushManager APIs.</div>'}
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<button id="push-enable" class="btn btn-primary" type="button">Enable Push</button>
<button id="push-disable" class="btn btn-secondary" type="button">Disable Push</button>
</div>
<div id="push-status" class="text-sm text-muted"></div>
</div>
<h2 class="section-title">Hook Patterns</h2>
<div class="settings-section">
<div class="hook-editor">
@@ -133,6 +218,9 @@ async function loadSettings() {
// Bind save hooks
_el.querySelector('#hooks-save').addEventListener('click', saveHooks);
_el.querySelector('#push-enable').addEventListener('click', onEnablePush);
_el.querySelector('#push-disable').addEventListener('click', onDisablePush);
await renderPushStatus();
}
async function saveHooks() {