Files
StarPilot/openpilot/starpilot/galaxy/web/js/startup.js
T

55 lines
3.0 KiB
JavaScript

export async function requestJson(url, { fetcher = (...args) => fetch(...args), timeout = 8000,
later = (fn, ms) => setTimeout(fn, ms), cancel = id => clearTimeout(id), request = {}, withResponse = false } = {}) {
if (globalThis.StarPilotApp?.bluetooth?.()) timeout = Math.max(timeout, 120000)
const controller = new AbortController()
let timer
let rejectCanceled
const abort = () => { controller.abort(); rejectCanceled?.(Object.assign(new Error("Request canceled"), { name: "AbortError" })) }
request.signal?.addEventListener("abort", abort, { once: true })
try {
return await Promise.race([
new Promise((_, reject) => { rejectCanceled = reject; if (request.signal?.aborted) abort() }),
(async () => {
const response = await fetcher(url, { ...request, cache: "no-store", credentials: "same-origin", signal: controller.signal })
if (!response.ok && !withResponse) {
const body = await response.json?.().catch(() => null)
const error = new Error(typeof body?.error === "string" ? body.error : "Galaxy could not be reached")
error.status = response.status
error.code = body?.code
throw error
}
const data = await response.json()
return withResponse ? { response, data } : data
})(),
new Promise((_, reject) => {
timer = later(() => { controller.abort(); reject(Object.assign(new Error("Galaxy took too long to respond"), { name: "TimeoutError" })) }, timeout)
}),
])
} finally {
request.signal?.removeEventListener("abort", abort)
cancel(timer)
controller.abort()
}
}
const validTool = (tool, root = false) => typeof tool?.path === "string" && tool.path.startsWith("/") &&
typeof tool.name === "string" && typeof tool.icon === "string" && typeof tool.description === "string" &&
(root ? ["always", "partial-preview", "local-only", "unavailable"].includes(tool.availability) :
tool.availability === undefined || ["always", "partial-preview", "local-only", "unavailable"].includes(tool.availability)) &&
(tool.visibility === undefined || tool.visibility === "authenticated") &&
(tool.tab === undefined || typeof tool.tab === "string") &&
(tool.sampleDescription === undefined || typeof tool.sampleDescription === "string") &&
(tool.children === undefined || Array.isArray(tool.children) && tool.children.every(child => validTool(child)))
export async function loadCatalog(options) {
const [catalog, runtime] = await Promise.all([
requestJson("./data/catalog.json", options), requestJson("./data/runtime.json", options),
])
if (runtime?.schemaVersion !== 1 || !["sample", "local"].includes(runtime.monitor)) throw new Error("Invalid Galaxy runtime")
if (catalog?.mode !== "offline-preview" || !Array.isArray(catalog.tools) || !catalog.tools.every(tool => validTool(tool, true))) {
throw new Error("Invalid Galaxy catalog")
}
return { tools: catalog.tools.slice().sort((a, b) => a.name.localeCompare(b.name)), mode: runtime.monitor,
instance: typeof runtime.instance === "string" ? runtime.instance : null }
}