import { connectionServicePresentation } from "./connection-service-presentation"; import { ConnectionCatalog } from "./connection-catalog"; import { ConnectionLogo } from "./connection-logo"; import { useEffect, useState, type FormEvent } from "react"; import type { ConnectAccount, ConnectController, ConnectProvider } from "@opengeni/connect"; import { useConnect } from "./hooks/use-connect"; import { useOpenGeniText } from "./messages/runtime"; export type ConnectChooserProps = { controller: ConnectController; /** Exact host return string; no completion parameters are added. */ returnUrl: string; className?: string; presentation?: "select" | "catalog"; /** Compact mechanism picker when service discovery is already shown by the host. */ customOnly?: boolean; providerOnly?: boolean; compact?: boolean; /** Select a service from the unified discovery list. */ initialProviderId?: string; }; /** Catalog readiness is supplied by the authenticated backend, never inferred * from the existence of a provider definition. Host replaces the controller on * actor/workspace change. This form does not open OAuth windows itself. */ export function ConnectChooser(props: ConnectChooserProps) { // Remount scope-local selections synchronously when the controller changes, // before effects can expose a previous actor's inventory for one frame. const [scope, setScope] = useState(props.controller); const [generation, setGeneration] = useState(0); if (scope !== props.controller) { setScope(props.controller); setGeneration(generation + 1); } return ; } function ScopedChooser({ controller, returnUrl, className, presentation = "select", customOnly = false, providerOnly = false, compact = false, initialProviderId = "", }: ConnectChooserProps) { const t = useOpenGeniText(); const view = useConnect(controller); const [catalog, setCatalog] = useState(null); const [accounts, setAccounts] = useState([]); const [failed, setFailed] = useState(false); const [load, setLoad] = useState(0); const [query, setQuery] = useState(""); const [providerId, setProviderId] = useState(initialProviderId); const [ownership, setOwnership] = useState(""); useEffect(() => { const abort = new AbortController(); setCatalog(null); setAccounts([]); setFailed(false); setProviderId(initialProviderId); setOwnership(""); // Promise boundary also handles a synchronously throwing injected transport. void Promise.resolve() .then(() => { abort.signal.throwIfAborted(); return Promise.all([ controller.transport.catalog(controller.workspaceId, { signal: abort.signal }), presentation === "catalog" ? controller.transport.accounts(controller.workspaceId, { signal: abort.signal }) : Promise.resolve([] as ConnectAccount[]), ]); }) .then(([providers, inventory]) => { if (!abort.signal.aborted) { setCatalog( structuredClone( providers.filter( (entry) => entry.readiness === "available" && entry.ownership.length > 0 && (!providerOnly || (entry.family !== "mcp" && !entry.setup.some((kind) => ["installation", "openapi", "graphql"].includes(kind), ))) && (!customOnly || entry.family === "mcp" || entry.setup.some((kind) => ["installation", "openapi", "graphql"].includes(kind), )), ), ), ); setAccounts(structuredClone(inventory)); const initial = providers.find((entry) => entry.id === initialProviderId); if (initial) setOwnership( initial.ownership.includes("personal") && ["google", "microsoft"].includes(initial.family) ? "personal" : initial.ownership.includes("workspace") ? "workspace" : (initial.ownership[0] ?? ""), ); } }) .catch(() => { if (!abort.signal.aborted) setFailed(true); }); return () => abort.abort(); }, [controller, load, presentation, customOnly, providerOnly, initialProviderId]); const provider = catalog?.find((entry) => entry.id === providerId); const canBegin = provider?.readiness === "available" && (ownership === "personal" || ownership === "workspace") && provider.ownership.includes(ownership); const submit = (event: FormEvent) => { event.preventDefault(); if (!canBegin || view.busy || (ownership !== "personal" && ownership !== "workspace")) return; setFailed(false); try { void controller .begin({ providerId, ownership, returnUrl, idempotencyKey: crypto.randomUUID() }) .catch(() => setFailed(true)); } catch { setFailed(true); } }; // Optional adapters must not add a temporary section that disappears once // readiness arrives. The primary discovery surface already owns loading. if (compact && (!catalog || catalog.length === 0) && !failed) return null; return (
{failed &&

{t("connectChooser.connectionsCouldNotBeLoaded")}

} {!catalog && !failed &&

{t("connectChooser.loadingConnections")}

} {catalog?.length === 0 && (

{t("connectChooser.noConnectionsAreAvailable")}

)} {Boolean(catalog?.length) && (
{presentation === "catalog" && !provider ? ( <> {[ { name: t("connectChooser.services"), entries: catalog!.filter( (entry) => entry.readiness === "available" && entry.family !== "mcp" && !entry.setup.some((kind) => ["installation", "openapi", "graphql"].includes(kind), ), ), secondary: false, }, { name: t("connectChooser.customConnections"), entries: catalog!.filter( (entry) => entry.readiness === "available" && (entry.family === "mcp" || entry.setup.some((kind) => ["installation", "openapi", "graphql"].includes(kind), )), ), secondary: true, }, ].map((group) => { const entries = group.entries.filter((entry) => entry.label.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim()), ); if (!entries.length) return null; const rows = ( { const connected = accounts.some( (account) => account.providerId === entry.id && account.status === "connected", ); return { id: entry.id, name: entry.label, logo: ( ), options: [ { id: entry.id, name: entry.label, status: connected ? t("connectChooser.connected") : entry.readiness === "available" ? t("connectChooser.connect") : t("connectChooser.unavailable"), state: connected ? "added" : entry.readiness === "available" ? "available" : "unavailable", connected, onOpen: () => { if (!view.busy) { setProviderId(entry.id); setOwnership( entry.ownership.length === 1 ? entry.ownership[0]! : "", ); } }, }, ], }; })} /> ); return group.secondary && !customOnly ? (
{group.name} {entries.length} {rows}
) : (
{rows}
); })} {!catalog!.some((entry) => entry.label.toLocaleLowerCase().includes(query.toLocaleLowerCase().trim()), ) &&

{t("connectChooser.noServicesMatchYourSearch")}

} ) : null} {presentation === "catalog" && provider && !initialProviderId ? ( ) : null} {provider && ( <> {presentation === "catalog" ? provider.label : null} {provider.readiness !== "available" && (

{provider.reason ?? t("connectChooser.thisServiceIsNotAvailable")}

)} )}
)}
); }