client/web: start using swr for some fetching

Adds swr to the web client, and starts by using it from the
useNodeData hook.

Updates #10261

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
Sonia Appasamy
2023-12-05 18:03:05 -05:00
committed by Sonia Appasamy
parent 014ae98297
commit 95655405b8
9 changed files with 70 additions and 68 deletions

View File

@ -28,11 +28,10 @@ export default function useAuth() {
const loadAuth = useCallback(() => {
setLoading(true)
return apiFetch("/auth", "GET")
.then((r) => r.json())
return apiFetch<AuthResponse>("/auth", "GET")
.then((d) => {
setData(d)
switch ((d as AuthResponse).authNeeded) {
switch (d.authNeeded) {
case AuthType.synology:
fetch("/webman/login.cgi")
.then((r) => r.json())
@ -53,15 +52,16 @@ export default function useAuth() {
}, [])
const newSession = useCallback(() => {
return apiFetch("/auth/session/new", "GET")
.then((r) => r.json())
return apiFetch<{ authUrl?: string }>("/auth/session/new", "GET")
.then((d) => {
if (d.authUrl) {
window.open(d.authUrl, "_blank")
return apiFetch("/auth/session/wait", "GET")
}
})
.then(() => loadAuth())
.then(() => {
loadAuth()
})
.catch((error) => {
console.error(error)
})
@ -70,7 +70,7 @@ export default function useAuth() {
useEffect(() => {
loadAuth().then((d) => {
if (
!d.canManageNode &&
!d?.canManageNode &&
new URLSearchParams(window.location.search).get("check") === "now"
) {
newSession()

View File

@ -33,8 +33,7 @@ export default function useExitNodes(node: NodeData, filter?: string) {
const [data, setData] = useState<ExitNode[]>([])
useEffect(() => {
apiFetch("/exit-nodes", "GET")
.then((r) => r.json())
apiFetch<ExitNode[]>("/exit-nodes", "GET")
.then((r) => setData(r))
.catch((err) => {
alert("Failed operation: " + err.message)

View File

@ -6,6 +6,7 @@ import { apiFetch, incrementMetric, setUnraidCsrfToken } from "src/api"
import { ExitNode, noExitNode, runAsExitNode } from "src/hooks/exit-nodes"
import { VersionInfo } from "src/hooks/self-update"
import { assertNever } from "src/utils/util"
import useSWR from "swr"
export type NodeData = {
Profile: UserProfile
@ -120,19 +121,12 @@ type RoutesPOSTData = {
// useNodeData returns basic data about the current node.
export default function useNodeData() {
const [data, setData] = useState<NodeData>()
const { data, mutate } = useSWR<NodeData>("/data")
const [isPosting, setIsPosting] = useState<boolean>(false)
const refreshData = useCallback(
() =>
apiFetch("/data", "GET")
.then((r) => r.json())
.then((d: NodeData) => {
setData(d)
setUnraidCsrfToken(d.IsUnraid ? d.UnraidToken : undefined)
})
.catch((error) => console.error(error)),
[setData]
useEffect(
() => setUnraidCsrfToken(data?.IsUnraid ? data.UnraidToken : undefined),
[data]
)
const prefsPATCH = useCallback(
@ -147,12 +141,12 @@ export default function useNodeData() {
// then make the prefs PATCH. If the request fails,
// data will be updated to it's previous value in
// onComplete below.
setData(optimisticUpdates)
mutate(optimisticUpdates, false)
}
const onComplete = () => {
setIsPosting(false)
refreshData() // refresh data after PATCH finishes
mutate() // refresh data after PATCH finishes
}
return apiFetch("/local/v0/prefs", "PATCH", d)
@ -163,7 +157,7 @@ export default function useNodeData() {
throw err
})
},
[setIsPosting, refreshData, setData, data]
[data, mutate]
)
const routesPOST = useCallback(
@ -171,7 +165,7 @@ export default function useNodeData() {
setIsPosting(true)
const onComplete = () => {
setIsPosting(false)
refreshData() // refresh data after POST finishes
mutate() // refresh data after POST finishes
}
const updateMetrics = () => {
// only update metrics if values have changed
@ -195,26 +189,7 @@ export default function useNodeData() {
throw err
})
},
[setIsPosting, refreshData, data?.AdvertisingExitNode]
)
useEffect(
() => {
// Initial data load.
refreshData()
// Refresh on browser tab focus.
const onVisibilityChange = () => {
document.visibilityState === "visible" && refreshData()
}
window.addEventListener("visibilitychange", onVisibilityChange)
return () => {
// Cleanup browser tab listener.
window.removeEventListener("visibilitychange", onVisibilityChange)
}
},
// Run once.
[refreshData]
[mutate, data?.AdvertisingExitNode]
)
const nodeUpdaters: NodeUpdaters = useMemo(
@ -245,5 +220,5 @@ export default function useNodeData() {
]
)
return { data, refreshData, nodeUpdaters, isPosting }
return { data, refreshData: mutate, nodeUpdaters, isPosting }
}

View File

@ -61,9 +61,8 @@ export function useInstallUpdate(currentVersion: string, cv?: VersionInfo) {
let timer: NodeJS.Timeout | undefined
function poll() {
apiFetch("/local/v0/update/progress", "GET")
.then((res) => res.json())
.then((res: UpdateProgress[]) => {
apiFetch<UpdateProgress[]>("/local/v0/update/progress", "GET")
.then((res) => {
// res contains a list of UpdateProgresses that is strictly increasing
// in size, so updateMessagesRead keeps track (across calls of poll())
// of how many of those we have already read. This is why it is not