Brings the Work Package Suite from a browser-local prototype to a multi-tenant, SQL-backed deployment hardened for customer IP. Auth & access control - Local username/password login (bcrypt + JWT in an HttpOnly cookie), admin-managed users, per-project membership, and project-scoped API access. - Admin console: change user roles, view the audit trail, manage settings. Security hardening - CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping. - Login lockout, token_version session revocation, stronger password policy, fail-closed secret loading, encrypted (AES-256) database backups. Persistence & schema - SOPs and Work Packages are now DB-backed and shared across users, written through a durable client sync outbox that queues offline edits. - Alembic migrations applied automatically on container start. New capabilities - Phase 2 dashboard (progress, gating, pagination, archive). - Phase 3 PWA "Field View" with offline caching and auth fallback. - WP owner assignment with OPTIONAL email notifications, OFF by default and toggled from the admin console. SMTP password is read only from the SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link, never customer IP. Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
/* Service worker for the Work Package Suite PWA.
|
|
|
|
Goal: let the app (and especially the field view) load and run offline. Data
|
|
durability is already handled by the sync outbox in project-data.js — this
|
|
worker only caches the static app shell so the pages open without a network.
|
|
|
|
Strategy:
|
|
• /api/* and non-GET → never touched (pass straight to the network; offline
|
|
reads fall back to the app's localStorage cache, writes queue in the outbox).
|
|
• same-origin GET → stale-while-revalidate (instant from cache, refreshed
|
|
in the background when online).
|
|
*/
|
|
'use strict';
|
|
const CACHE = 'wp-suite-shell-v1';
|
|
const SHELL = [
|
|
'/', '/index.html', '/work-package-suite.html', '/wp-creation-index.html',
|
|
'/field.html', '/login.html', '/admin.html',
|
|
'/theme-light.css', '/work-package-suite-styles.css', '/wp-creation-styles.css',
|
|
'/auth-guard.js', '/project-data.js', '/feedback-config.js', '/help.js',
|
|
'/work-package-suite-app.js', '/wp-creation-app.js', '/field.js',
|
|
'/prime-controls-logo.jpg', '/favicon.ico',
|
|
'/manifest.webmanifest', '/icon-192.png', '/icon-512.png',
|
|
];
|
|
|
|
self.addEventListener('install', (e) => {
|
|
// Cache each shell asset individually so one missing file doesn't abort install.
|
|
e.waitUntil(
|
|
caches.open(CACHE)
|
|
.then((c) => Promise.all(SHELL.map((u) => c.add(u).catch(() => {}))))
|
|
.then(() => self.skipWaiting())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('activate', (e) => {
|
|
e.waitUntil(
|
|
caches.keys()
|
|
.then((keys) => Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k))))
|
|
.then(() => self.clients.claim())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', (e) => {
|
|
const req = e.request;
|
|
if (req.method !== 'GET') return; // outbox owns writes
|
|
const url = new URL(req.url);
|
|
if (url.origin !== self.location.origin) return; // third-party: default
|
|
if (url.pathname.startsWith('/api/')) return; // never cache the API
|
|
|
|
e.respondWith(
|
|
caches.match(req).then((cached) => {
|
|
const network = fetch(req)
|
|
.then((res) => {
|
|
if (res && res.ok) {
|
|
const copy = res.clone();
|
|
caches.open(CACHE).then((c) => c.put(req, copy));
|
|
}
|
|
return res;
|
|
})
|
|
.catch(() => cached); // offline → cached copy
|
|
return cached || network; // cache-first, then refresh
|
|
})
|
|
);
|
|
});
|