Skip to content

Commit bf0dd67

Browse files
authored
fix(viewer): defer SSE connect on session-scoped public-read post permalinks (#222)
1 parent 32ed841 commit bf0dd67

3 files changed

Lines changed: 40 additions & 9 deletions

File tree

.changeset/public-read-sse-race.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"sideshow": patch
3+
---
4+
5+
Fix blank page when opening a post permalink (`/p/:id`) on a
6+
`publicRead="session"` workspace without authentication. The SSE connection
7+
fired before the viewer discovered the post's session ID, hitting `/api/events`
8+
without the required `?session=` param and getting a 401. The connection is now
9+
deferred until after the initial post fetch resolves.

viewer/src/App.tsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
initialPageTitle,
77
isReadonly,
88
layoutMode,
9+
publicReadMode,
910
relTime,
1011
sessionLabel,
1112
type Post,
@@ -118,14 +119,35 @@ export default function App() {
118119
// nor a host's loading overlay flips to real content before we know what to
119120
// show. .catch keeps it unblocking — a failed fetch still resolves to the
120121
// (empty) onboarding view, and the host overlay still clears.
121-
void bootstrap()
122-
.catch(() => {})
123-
.finally(() => {
124-
setInitialLoaded(true);
125-
host().onReady?.();
126-
});
127-
const disconnect = connect();
128-
onCleanup(disconnect);
122+
// On a session-scoped publicRead workspace, the SSE connection requires a
123+
// ?session= param. For standalone post permalinks (/p/:id) the session ID
124+
// is only discovered during bootstrap (enterStandalone fetches the post).
125+
// Connecting before that resolves sends /api/events without a session and
126+
// the server returns 401. Defer the SSE connection until bootstrap finishes
127+
// so eventsPath() can read the resolved session ID.
128+
let disconnect: (() => void) | undefined;
129+
let unmounted = false;
130+
if (isReadonly() && publicReadMode() === "session") {
131+
void bootstrap()
132+
.catch(() => {})
133+
.finally(() => {
134+
setInitialLoaded(true);
135+
host().onReady?.();
136+
if (!unmounted) disconnect = connect();
137+
});
138+
} else {
139+
void bootstrap()
140+
.catch(() => {})
141+
.finally(() => {
142+
setInitialLoaded(true);
143+
host().onReady?.();
144+
});
145+
disconnect = connect();
146+
}
147+
onCleanup(() => {
148+
unmounted = true;
149+
disconnect?.();
150+
});
129151
checkVersion();
130152
void initTheme();
131153
const timer = setInterval(() => {

viewer/src/state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ const WS_RECONNECT_MS = 1000;
478478

479479
function eventsPath(): string {
480480
const route = host().router.get();
481-
const sessionId = route.sessionId ?? selected();
481+
const sessionId = route.sessionId ?? selected() ?? standalonePost()?.sessionId;
482482
return isReadonly() && publicReadMode() === "session" && sessionId
483483
? `/api/events?session=${encodeURIComponent(sessionId)}`
484484
: "/api/events";

0 commit comments

Comments
 (0)