feat: require owner login and add onboarding

This commit is contained in:
2026-09-28 19:14:14 +09:00
parent 57882cf5f9
commit 85d23a328b
42 changed files with 2400 additions and 23 deletions
+18 -1
View File
@@ -4,7 +4,11 @@ type Snapshot = { configured: boolean; run: ResearchRun | null }
type Subscribe = (listener: (snapshot: Snapshot) => void) => () => void
/** Subscribe to current state and subsequent changes; disconnect only this viewer. */
export function researchEvents(request: Request, subscribe: Subscribe) {
export function researchEvents(
request: Request,
subscribe: Subscribe,
isAuthorized: () => boolean = () => true,
) {
const encoder = new TextEncoder()
let dispose = () => {}
let flushPending = () => {}
@@ -14,7 +18,15 @@ export function researchEvents(request: Request, subscribe: Subscribe) {
let closed = false
let unsubscribe = () => {}
let heartbeat: ReturnType<typeof setInterval> | undefined
const checkSession = () => {
if (closed) return false
if (isAuthorized()) return true
dispose()
controller.close()
return false
}
const flush = () => {
if (!checkSession()) return
if (
!closed &&
pending !== undefined &&
@@ -46,7 +58,12 @@ export function researchEvents(request: Request, subscribe: Subscribe) {
pending = `data: ${JSON.stringify(snapshot)}\n\n`
flush()
})
if (closed) {
unsubscribe()
return
}
heartbeat = setInterval(() => {
if (!checkSession()) return
if (
!closed &&
pending === undefined &&
+22
View File
@@ -81,3 +81,25 @@ it('bounds a slow viewer buffer while retaining the latest state', async () => {
)
await reader.cancel()
})
it('stops sending updates when its login session is revoked', async () => {
let authorized = true
let emit = (_value: { configured: boolean; run: null }) => {}
const unsubscribe = vi.fn()
const response = researchEvents(
new Request('http://127.0.0.1/events'),
(listener) => {
emit = listener
listener({ configured: false, run: null })
return unsubscribe
},
() => authorized,
)
const reader = response.body?.getReader()
expect.assert.isDefined(reader)
await reader.read()
authorized = false
emit({ configured: true, run: null })
expect((await reader.read()).done).toBe(true)
expect(unsubscribe).toHaveBeenCalledOnce()
})