fix: SSE sync indicator stuck on 'Connexion...' (3 fixes)

1. Move initSyncStatus() AFTER auth check — EventSource was connecting
   before the access_token cookie was available, causing 401 errors.

2. Reconnect SSE after login — Login form handler now calls
   IndexUpdateManager.connect() + showWelcome() after successful auth.

3. SSESafeGZipMiddleware — GZip buffering breaks Server-Sent Events
   streaming. Custom middleware subclass skips compression for
   /api/events endpoint (path-based bypass).
This commit is contained in:
Bruno Charest 2026-05-27 21:40:32 -04:00
parent 2469026c1d
commit a5afbb1dc1
2 changed files with 24 additions and 2 deletions

View File

@ -556,8 +556,24 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="ObsiGate", version="1.4.0", lifespan=lifespan) app = FastAPI(title="ObsiGate", version="1.4.0", lifespan=lifespan)
# GZip compression — reduces bandwidth by ~70% for text responses # GZip compression — reduces bandwidth by ~70% for text responses
# Custom wrapper: skip compression for SSE streams (/api/events)
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
app.add_middleware(GZipMiddleware, minimum_size=1000) from starlette.types import Scope, Receive, Send
class SSESafeGZipMiddleware(GZipMiddleware):
"""GZip middleware that skips SSE (Server-Sent Events) streams.
GZip buffering breaks incremental streaming required by SSE.
We detect SSE endpoints by path and bypass compression entirely.
"""
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "http" and scope.get("path") == "/api/events":
# Bypass GZip: passthrough directly to the inner app
await self.app(scope, receive, send)
else:
await super().__call__(scope, receive, send)
app.add_middleware(SSESafeGZipMiddleware, minimum_size=1000)
# Security headers on all responses # Security headers on all responses
app.add_middleware(SecurityHeadersMiddleware) app.add_middleware(SecurityHeadersMiddleware)

View File

@ -1734,6 +1734,10 @@
// Load app data after successful login // Load app data after successful login
try { try {
await Promise.all([loadVaults(), loadTags()]); await Promise.all([loadVaults(), loadTags()]);
// Start SSE sync now that auth cookie is set
IndexUpdateManager.connect();
// Show dashboard
showWelcome();
} catch (err) { } catch (err) {
console.error("Failed to load data after login:", err); console.error("Failed to load data after login:", err);
} }
@ -7818,7 +7822,6 @@
initSidebarFilter(); initSidebarFilter();
initSidebarResize(); initSidebarResize();
initEditor(); initEditor();
initSyncStatus();
initLoginForm(); initLoginForm();
initRecentTab(); initRecentTab();
RightSidebarManager.init(); RightSidebarManager.init();
@ -7829,6 +7832,9 @@
const authOk = await AuthManager.initAuth(); const authOk = await AuthManager.initAuth();
if (authOk) { if (authOk) {
// Start SSE sync AFTER auth is established (cookie available)
initSyncStatus();
try { try {
await Promise.all([loadVaultSettings(), loadVaults(), loadTags()]); await Promise.all([loadVaultSettings(), loadVaults(), loadTags()]);