Unified · Split
| @@ -1,6 +1,7 @@ | |||
| 1 | import { Link, Outlet, useLocation } from "@tanstack/react-router"; | 1 | import { Link, Outlet, useLocation } from "@tanstack/react-router"; |
| 2 | import { useCallback, useEffect, useRef, useState } from "react"; | 2 | import { useCallback, useEffect, useRef, useState } from "react"; |
| 3 | import { useAtomRefresh } from "@effect/atom-react"; | 3 | import { useAtomRefresh } from "@effect/atom-react"; |
| 4 | import { Data, Effect, Exit } from "effect"; | ||
| 4 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; | 5 | import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; |
| 5 | import { sourcesAtom, toolsAtom } from "@executor-js/react/api/atoms"; | 6 | import { sourcesAtom, toolsAtom } from "@executor-js/react/api/atoms"; |
| 6 | import { useSourcesWithPending } from "@executor-js/react/api/optimistic"; | 7 | import { useSourcesWithPending } from "@executor-js/react/api/optimistic"; |
| @@ -29,6 +30,10 @@ type UpdateChannel = "latest" | "beta"; · 22 unmodified lines | |||
| 29 | 30 | ||
| 30 | const EXECUTOR_DIST_TAGS_PATH = "/v1/app/npm/dist-tags"; | 31 | const EXECUTOR_DIST_TAGS_PATH = "/v1/app/npm/dist-tags"; |
| 31 | 32 | ||
| 33 | class LatestVersionCheckError extends Data.TaggedError("LatestVersionCheckError")<{ | ||
| 34 | readonly cause: unknown; | ||
| 35 | }> {} | ||
| 36 | |||
| 32 | type ParsedVersion = { | 37 | type ParsedVersion = { |
| 33 | readonly major: number; | 38 | readonly major: number; |
| 34 | readonly minor: number; | 39 | readonly minor: number; |
| @@ -95,15 +100,22 @@ function useLatestVersion(currentVersion: string) { · 60 unmodified lines | |||
| 95 | 100 | ||
| 96 | useEffect(() => { | 101 | useEffect(() => { |
| 97 | let cancelled = false; | 102 | let cancelled = false; |
| 98 | fetch(EXECUTOR_DIST_TAGS_PATH) | 103 | |
| 99 | .then(async (res) => { | 104 | const latestVersionEffect = Effect.tryPromise({ |
| 100 | if (!res.ok) throw new Error(`Failed to load dist tags: ${res.status}`); | 105 | try: async () => { |
| 101 | return res.json() as Promise<Partial<Record<UpdateChannel, string>>>; | 106 | const res = await fetch(EXECUTOR_DIST_TAGS_PATH); |
| 102 | }) | 107 | if (!res.ok) return {}; |
| 103 | .then((data) => { | 108 | return (await res.json()) as Partial<Record<UpdateChannel, string>>; |
| 104 | if (!cancelled) setLatestVersion(data[channel] ?? null); | 109 | }, |
| 105 | }) | 110 | catch: (cause) => new LatestVersionCheckError({ cause }), |
| 106 | .catch(() => {}); | 111 | }); |
| 112 | |||
| 113 | void Effect.runPromiseExit(latestVersionEffect).then((exit) => { | ||
| 114 | if (!cancelled && Exit.isSuccess(exit)) { | ||
| 115 | setLatestVersion(exit.value[channel] ?? null); | ||
| 116 | } | ||
| 117 | }); | ||
| 118 | |||
| 107 | return () => { | 119 | return () => { |
| 108 | cancelled = true; | 120 | cancelled = true; |
| 109 | }; | 121 | }; |