Task #626: stop notifications after logout + tighten meeting reminder
- /auth/logout: delete all push_subscriptions rows for the user before destroying the session. Best-effort with logger.warn on failure so an operator can correlate any leaked-ring report to a real DB error. - home.tsx handleLogout: call pushSub.disable() before the logout mutation, raced against a 1.5s timeout so a stalled service worker cannot block sign-out. - executive-meeting-scheduler: switch the eligibility filter from denylist (ne cancelled + ne completed) to whitelist (eq status='scheduled'). Postponed / rescheduled / future statuses can no longer trigger false 5-minute reminders. - docker-compose.yml: pin TZ=Asia/Riyadh on postgres, api, and web services so the scheduler's naive date/time math matches the operator's wall clock instead of UTC. Gitea push failed with TLS error during this session — code committed locally, needs manual `./scripts/publish-to-gitea.sh --push` retry when the desktop-11cj93j tunnel recovers.
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
} from "@workspace/api-client-react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { usePushSubscription } from "@/hooks/use-push-subscription";
|
||||
import { useReceivedNotes } from "@/lib/notes-api";
|
||||
import { useIncomingNotePopup } from "@/contexts/IncomingNotePopupContext";
|
||||
import {
|
||||
@@ -491,6 +492,7 @@ export default function HomePage() {
|
||||
const notesPulse = notePopupQueueLength > 0;
|
||||
|
||||
const logout = useLogout();
|
||||
const pushSub = usePushSubscription();
|
||||
|
||||
const openApp = (app: App) => {
|
||||
// Fire-and-forget: keepalive ensures the request survives any
|
||||
@@ -521,7 +523,31 @@ export default function HomePage() {
|
||||
? (user?.displayNameAr ?? user?.username ?? "")
|
||||
: (user?.displayNameEn ?? user?.username ?? "");
|
||||
|
||||
const handleLogout = () => {
|
||||
const handleLogout = async () => {
|
||||
// #626: tear down this browser's Web Push subscription BEFORE
|
||||
// hitting /auth/logout, so the local pushManager endpoint and the
|
||||
// server row both go away. The server-side logout also deletes
|
||||
// every push_subscriptions row for this user as a safety net, but
|
||||
// doing the browser unsubscribe first means the OS-level
|
||||
// subscription is gone too — otherwise the SW would keep an
|
||||
// orphan endpoint that the next signed-in user could inherit on
|
||||
// resubscribe.
|
||||
// Race the browser unsubscribe against a 1.5s timeout. `disable()`
|
||||
// awaits `navigator.serviceWorker.ready`, which can hang forever
|
||||
// if the SW never activates (rare but observed on first iOS PWA
|
||||
// launch). A stalled unsubscribe must not block the user from
|
||||
// signing out — the server-side delete in /auth/logout is the
|
||||
// authoritative cleanup, so a missed browser unsubscribe just
|
||||
// leaves an orphan pushManager endpoint that the OS will GC on
|
||||
// its own schedule.
|
||||
try {
|
||||
await Promise.race([
|
||||
pushSub.disable(),
|
||||
new Promise<void>((resolve) => setTimeout(resolve, 1500)),
|
||||
]);
|
||||
} catch {
|
||||
// Best-effort: a failed unsubscribe must not block sign-out.
|
||||
}
|
||||
logout.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
queryClient.clear();
|
||||
|
||||
Reference in New Issue
Block a user