import { useEffect } from "react"; import { useSWRConfig } from "swr"; import { subscribeToCrossTabSse, type CrossTabSseCoordinator, } from "./cross-tab-sse"; import { runListCacheMatchers } from "./board-cache"; import { queryKeys } from "./query-keys"; import { createBrowserEventSource, subscribeToSharedEventSource, type EventPayload, type EventSourceLike, type MutateFn, type SharedEventSubscription, } from "./sse"; interface BoardEventOptions { debounceMs?: number; coordinator?: CrossTabSseCoordinator; } const BOARD_STATUS_EVENTS = new Set([ "run.submitted", "run.start_requested", "run.pending", "run.approved", "run.denied", "run.runnable", "run.starting", "run.running", "run.removing", "run.paused", "run.unpaused", "run.blocked", "run.unblocked", "run.cancel.requested", "run.pause.requested", "run.unpause.requested", "run.completed", "run.failed", "run.archived", "run.unarchived", "run.title.updated", "interview.started", "interview.completed", "interview.timeout", "interview.interrupted", "pull_request.created", "pull_request.linked", "pull_request.unlinked", ]); const subscriptions = new Map(); const BOARD_SUBSCRIPTION_KEY = "board"; export function shouldRefreshBoardForEvent(event: string) { return BOARD_STATUS_EVENTS.has(event); } export function subscribeToBoardEvents( mutate: MutateFn, eventSourceFactory: (url: string) => EventSourceLike = createBrowserEventSource, { debounceMs = 500, coordinator }: BoardEventOptions = {}, ): () => void { return subscribeToCrossTabSse({ coordinator, subscriptionKey: BOARD_SUBSCRIPTION_KEY, mutate, debounceMs, resyncKeys: () => boardRunKeys(), resolveInvalidation: boardInvalidation, fallbackSubscribe: () => subscribeToSharedEventSource({ subscriptions, subscriptionKey: BOARD_SUBSCRIPTION_KEY, url: queryKeys.system.attachUrl(), mutate, eventSourceFactory, debounceMs, resolveInvalidation: boardInvalidation, }), }); } function boardInvalidation(payload: EventPayload) { return { keys: payload.event && shouldRefreshBoardForEvent(payload.event) ? boardRunKeys() : [], }; } function boardRunKeys() { return runListCacheMatchers(); } /** * Synchronizes React/SWR with the shared board SSE stream. The subscription is * closed before resubscribe and on unmount. */ export function useBoardEvents() { const { mutate } = useSWRConfig(); useEffect(() => subscribeToBoardEvents(mutate as MutateFn), [mutate]); }