| 1 | import { useLayoutEffect, useRef, useState } from "react"; |
| 2 | import { app } from "../lib/bridge"; |
| 3 | import { useCommittedCommand } from "../lib/useCommittedCommand"; |
| 4 | import type { BackgroundRuntimeView, WorkspaceConflictView } from "../lib/types"; |
| 5 | import { createPollingOwner, type PollClock } from "./pollingOwner"; |
| 6 | import { trackAppOperation } from "./appLifecycleProbe"; |
| 7 | |
| 8 | const browserClock: PollClock = { |
| 9 | setTimeout: (callback, delay) => window.setTimeout(callback, delay), |
| 10 | clearTimeout: handle => window.clearTimeout(handle as number), |
| 11 | }; |
| 12 | export function useRuntimeStatus(input: { tabId?: string; sessionKey: string; running: boolean }, clock: PollClock = browserClock) { |
| 13 | const [backgroundRuntimes, setBackgroundRuntimes] = useState<BackgroundRuntimeView[]>([]); |
| 14 | const [conflict, setConflict] = useState<{ key: string; value: WorkspaceConflictView | null } | null>(null); |
| 15 | const background = useRef<ReturnType<typeof createPollingOwner<BackgroundRuntimeView[]>> | null>(null); |
| 16 | const refreshBackgroundRuntimes = useCommittedCommand(() => background.current?.refresh() ?? Promise.resolve()); |
| 17 | useLayoutEffect(() => { |
| 18 | const owner = createPollingOwner({ target: { kind: "application" }, periodMs: 1000, clock, |
| 19 | read: app.BackgroundRuntimes, publish: setBackgroundRuntimes, failed: () => {}, |
| 20 | }, trackAppOperation); |
| 21 | background.current = owner; |
| 22 | void owner.refresh(); |
| 23 | return () => { owner.dispose(); if (background.current === owner) background.current = null; }; |
| 24 | }, [clock]); |
| 25 | const { tabId, sessionKey, running } = input; |
| 26 | const key = JSON.stringify([tabId, sessionKey]); |
| 27 | const setWorkspaceConflict = useCommittedCommand((value: WorkspaceConflictView | null) => setConflict(value ? { key, value } : null)); |
| 28 | useLayoutEffect(() => { |
| 29 | setConflict(null); |
| 30 | if (!tabId || !running) return; |
| 31 | const owner = createPollingOwner({ target: { kind: "session", tabId, sessionKey }, periodMs: 500, clock, |
| 32 | read: () => app.WorkspaceConflictForTab(tabId), |
| 33 | publish: value => setConflict({ key, value: value.state === "none" ? null : value }), |
| 34 | failed: () => setConflict({ key, value: null }), |
| 35 | }, trackAppOperation); |
| 36 | void owner.refresh(); |
| 37 | return () => owner.dispose(); |
| 38 | }, [clock, key, running, sessionKey, tabId]); |
| 39 | return { backgroundRuntimes, refreshBackgroundRuntimes, setWorkspaceConflict, workspaceConflict: conflict?.key === key ? conflict.value : null }; |
| 40 | } |
| 41 |