1527 lines · 52.9 KB · TypeScript
RawDownload
import { useEffect, useState, type ReactNode } from "react";import { Link, useNavigate } from "@tanstack/react-router";import {  type CompleteSourceOAuthResult,  type CreateSourcePayload,  type InstanceConfig,  type Loadable,  type StartSourceOAuthPayload,  type SecretListItem,  type Source,  type UpdateSourcePayload,  useCreateSecret,  useCreateSource,  useInstanceConfig,  useRefreshSecrets,  useRemoveProviderAuthGrant,  useRemoveSource,  useSecrets,  useSources,  useStartSourceOAuth,  useSource,  useUpdateSource,} from "@executor/react";import { Badge } from "../components/ui/badge";import { Button } from "../components/ui/button";import { LoadableBlock } from "../components/loadable";import { SourceNotFoundState } from "../components/source-not-found-state";import { SourceFavicon } from "../components/source-favicon";import {  IconArrowLeft,  IconPencil,  IconPlus,  IconSpinner,  IconTrash,} from "../components/icons";import { cn } from "../lib/utils";import { sourceTemplates, type SourceTemplate } from "./source-templates";import { getDomain } from "tldts"; type StatusBannerState = {  tone: "info" | "success" | "error";  text: string;}; type SourceOAuthPopupMessage =  | {      type: "executor:oauth-result";      ok: true;      sessionId: string;      auth: CompleteSourceOAuthResult["auth"];    }  | {      type: "executor:oauth-result";      ok: false;      sessionId: string | null;      error: string;    }; const SOURCE_OAUTH_POPUP_RESULT_TIMEOUT_MS = 2 * 60_000;const SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX = "executor:oauth-result:"; const isSourceNotFoundLoadable = (loadable: Loadable<unknown>): boolean =>  loadable.status === "error"  && loadable.error.message.toLowerCase().includes("source not found"); type TransportValue = "" | "auto" | "streamable-http" | "sse"; type SourceFormState = {  name: string;  kind: Source["kind"];  endpoint: string;  namespace: string;  enabled: boolean;  transport: TransportValue;  queryParamsText: string;  headersText: string;  specUrl: string;  service: string;  version: string;  defaultHeadersText: string;  authKind: Source["auth"]["kind"];  authHeaderName: string;  authPrefix: string;  bearerProviderId: string;  bearerHandle: string;  oauthAccessProviderId: string;  oauthAccessHandle: string;  oauthRefreshProviderId: string;  oauthRefreshHandle: string;  managedAuth: Extract<Source["auth"], { kind: "provider_grant_ref" | "mcp_oauth" }> | null;}; const kindOptions: ReadonlyArray<Source["kind"]> = [  "mcp",  "openapi",  "graphql",  "google_discovery",  "internal",]; const transportOptions: ReadonlyArray<Exclude<TransportValue, "">> = [  "auto",  "streamable-http",  "sse",]; const authOptions: ReadonlyArray<Source["auth"]["kind"]> = [  "none",  "bearer",  "oauth2",  "provider_grant_ref",  "mcp_oauth",]; const trimToNull = (value: string): string | null => {  const trimmed = value.trim();  return trimmed.length > 0 ? trimmed : null;}; const namespaceFromUrl = (url: string): string => {  try {    const domain = getDomain(url);    if (!domain) return "";    const dot = domain.indexOf(".");    return dot > 0 ? domain.slice(0, dot) : domain;  } catch {    return "";  }}; const googleDiscoveryNamespace = (service: string): string =>  `google.${service.trim().toLowerCase().replace(/[^a-z0-9]+/g, ".").replace(/^\.+|\.+$/g, "")}`; const readStoredSourceOAuthPopupResult = (sessionId: string): SourceOAuthPopupMessage | null => {  if (typeof window === "undefined") {    return null;  }   const raw = window.localStorage.getItem(    `${SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX}${sessionId}`,  );  if (!raw) {    return null;  }   try {    return JSON.parse(raw) as SourceOAuthPopupMessage;  } catch {    return null;  }}; const clearStoredSourceOAuthPopupResult = (sessionId: string): void => {  if (typeof window === "undefined") {    return;  }   window.localStorage.removeItem(`${SOURCE_OAUTH_POPUP_RESULT_STORAGE_KEY_PREFIX}${sessionId}`);}; const startSourceOAuthPopup = async (input: {  authorizationUrl: string;  sessionId: string;}): Promise<CompleteSourceOAuthResult["auth"]> => {  if (typeof window === "undefined") {    throw new Error("OAuth popup is only available in a browser context");  }   clearStoredSourceOAuthPopupResult(input.sessionId);   const popup = window.open(    input.authorizationUrl,    "executor-source-oauth",    "popup=yes,width=520,height=720",  );   if (!popup) {    throw new Error("Popup blocked. Allow popups and try again.");  }   popup.focus();   return await new Promise<CompleteSourceOAuthResult["auth"]>((resolve, reject) => {    let settled = false;    let closedPoll = 0;    let resultTimeout = 0;     const cleanup = () => {      window.removeEventListener("message", onMessage);      if (closedPoll) {        window.clearInterval(closedPoll);      }      if (resultTimeout) {        window.clearTimeout(resultTimeout);      }      if (!popup.closed) {        popup.close();      }      clearStoredSourceOAuthPopupResult(input.sessionId);    };     const settleWithError = (message: string) => {      if (settled) {        return;      }       settled = true;      cleanup();      reject(new Error(message));    };     const settleFromPayload = (data: SourceOAuthPopupMessage) => {      if (!data.ok) {        settleWithError(data.error || "OAuth failed");        return;      }       if (settled) {        return;      }       settled = true;      cleanup();      resolve(data.auth);    };     const onMessage = (event: MessageEvent) => {      if (event.origin !== window.location.origin) {        return;      }       const data = event.data as SourceOAuthPopupMessage | undefined;      if (!data || data.type !== "executor:oauth-result") {        return;      }       if (data.ok && data.sessionId !== input.sessionId) {        return;      }       if (!data.ok && data.sessionId !== null && data.sessionId !== input.sessionId) {        return;      }       settleFromPayload(data);    };     window.addEventListener("message", onMessage);     resultTimeout = window.setTimeout(() => {      settleWithError("OAuth popup timed out before completion. Please try again.");    }, SOURCE_OAUTH_POPUP_RESULT_TIMEOUT_MS);     closedPoll = window.setInterval(() => {      const stored = readStoredSourceOAuthPopupResult(input.sessionId);      if (stored) {        settleFromPayload(stored);        return;      }      if (popup.closed) {        // Stop polling — only run one final deferred check to give the        // callback page time to write localStorage before we give up.        window.clearInterval(closedPoll);        closedPoll = 0;        window.setTimeout(() => {          const delayedStored = readStoredSourceOAuthPopupResult(input.sessionId);          if (delayedStored) {            settleFromPayload(delayedStored);            return;          }          settleWithError("OAuth popup was closed before completion.");        }, 1500);      }    }, 300);  });}; const stringMapToEditor = (value: Record<string, string> | null): string =>  value === null ? "" : JSON.stringify(value, null, 2); const readBindingStringMap = (  source: Source,  key: string,): Record<string, string> | null => {  const candidate = source.binding[key];  if (candidate === null || candidate === undefined || typeof candidate !== "object" || Array.isArray(candidate)) {    return null;  }   const entries = Object.entries(candidate as Record<string, unknown>);  return entries.every(([, value]) => typeof value === "string")    ? Object.fromEntries(entries as ReadonlyArray<readonly [string, string]>)    : null;}; const readBindingString = (source: Source, key: string): string =>  typeof source.binding[key] === "string" ? String(source.binding[key]) : ""; const readBindingTransport = (source: Source): TransportValue => {  const candidate = source.binding.transport;  return typeof candidate === "string" && (candidate === "auto" || candidate === "streamable-http" || candidate === "sse")    ? candidate    : "";}; const defaultFormState = (template?: SourceTemplate): SourceFormState => ({  name: template?.name ?? "",  kind: template?.kind ?? "openapi",  endpoint: template?.endpoint ?? "",  namespace: template    ? "service" in template      ? googleDiscoveryNamespace(template.service)      : namespaceFromUrl(template.endpoint)    : "",  enabled: true,  transport: template?.kind === "mcp" ? "auto" : "",  queryParamsText: "",  headersText: "",  specUrl: template && "specUrl" in template ? template.specUrl : "",  service: template && "service" in template ? template.service : "",  version: template && "version" in template ? template.version : "",  defaultHeadersText: "",  authKind: template?.kind === "google_discovery" ? "oauth2" : "none",  authHeaderName: "Authorization",  authPrefix: "Bearer ",  bearerProviderId: "",  bearerHandle: "",  oauthAccessProviderId: "",  oauthAccessHandle: "",  oauthRefreshProviderId: "",  oauthRefreshHandle: "",  managedAuth: null,}); const formStateFromSource = (source: Source): SourceFormState => ({  name: source.name,  kind: source.kind,  endpoint: source.endpoint,  namespace: source.namespace ?? "",  enabled: source.enabled,  transport: source.kind === "mcp" ? (readBindingTransport(source) || "auto") : "",  queryParamsText: stringMapToEditor(readBindingStringMap(source, "queryParams")),  headersText: stringMapToEditor(readBindingStringMap(source, "headers")),  specUrl: readBindingString(source, "specUrl"),  service: readBindingString(source, "service"),  version: readBindingString(source, "version"),  defaultHeadersText: stringMapToEditor(readBindingStringMap(source, "defaultHeaders")),  authKind: source.auth.kind,  authHeaderName:    source.auth.kind === "none" || source.auth.kind === "mcp_oauth"      ? "Authorization"      : source.auth.headerName,  authPrefix:    source.auth.kind === "none" || source.auth.kind === "mcp_oauth"      ? "Bearer "      : source.auth.prefix,  bearerProviderId: source.auth.kind === "bearer" ? source.auth.token.providerId : "",  bearerHandle: source.auth.kind === "bearer" ? source.auth.token.handle : "",  oauthAccessProviderId: source.auth.kind === "oauth2" ? source.auth.accessToken.providerId : "",  oauthAccessHandle: source.auth.kind === "oauth2" ? source.auth.accessToken.handle : "",  oauthRefreshProviderId:    source.auth.kind === "oauth2" && source.auth.refreshToken !== null      ? source.auth.refreshToken.providerId      : "",  oauthRefreshHandle:    source.auth.kind === "oauth2" && source.auth.refreshToken !== null      ? source.auth.refreshToken.handle      : "",  managedAuth:    source.auth.kind === "provider_grant_ref" || source.auth.kind === "mcp_oauth"      ? source.auth      : null,}); const parseJsonStringMap = (label: string, text: string): Record<string, string> | null => {  const trimmed = text.trim();  if (trimmed.length === 0) {    return null;  }   let parsed: unknown;  try {    parsed = JSON.parse(trimmed);  } catch (error) {    throw new Error(`${label} must be valid JSON.`);  }   if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {    throw new Error(`${label} must be a JSON object with string values.`);  }   const entries = Object.entries(parsed as Record<string, unknown>);  const normalized: Record<string, string> = {};  for (const [key, value] of entries) {    if (typeof value !== "string") {      throw new Error(`${label} must only contain string values.`);    }    normalized[key] = value;  }   return Object.keys(normalized).length === 0 ? null : normalized;}; const buildAuthPayload = (state: SourceFormState): CreateSourcePayload["auth"] => {  if (state.authKind === "none") {    return { kind: "none" };  }   if ((state.authKind === "provider_grant_ref" || state.authKind === "mcp_oauth") && state.managedAuth !== null) {    return state.managedAuth;  }   const headerName = state.authHeaderName.trim() || "Authorization";  const prefix = state.authPrefix;   if (state.authKind === "bearer") {    const providerId = state.bearerProviderId.trim();    const handle = state.bearerHandle.trim();    if (!providerId || !handle) {      throw new Error("Bearer auth requires a token. Select or create a secret.");    }     return {      kind: "bearer",      headerName,      prefix,      token: {        providerId,        handle,      },    };  }   const accessProviderId = state.oauthAccessProviderId.trim();  const accessHandle = state.oauthAccessHandle.trim();  if (!accessProviderId || !accessHandle) {    throw new Error("OAuth2 auth requires an access token. Select or create a secret.");  }   const refreshProviderId = trimToNull(state.oauthRefreshProviderId);  const refreshHandle = trimToNull(state.oauthRefreshHandle);  if ((refreshProviderId === null) !== (refreshHandle === null)) {    throw new Error("OAuth2 refresh token provider ID and handle must be set together.");  }   return {    kind: "oauth2",    headerName,    prefix,    accessToken: {      providerId: accessProviderId,      handle: accessHandle,    },    refreshToken:      refreshProviderId === null || refreshHandle === null        ? null        : {            providerId: refreshProviderId,            handle: refreshHandle,          },  };}; const buildRequestedSourceStatus = (state: SourceFormState): CreateSourcePayload["status"] => {  if (state.kind !== "mcp" && state.kind !== "openapi" && state.kind !== "graphql") {    return undefined;  }   return state.enabled ? "connected" : "draft";}; const buildSourcePayload = (state: SourceFormState): CreateSourcePayload => {  const name = state.name.trim();  const endpoint = state.endpoint.trim();   if (!name) {    throw new Error("Source name is required.");  }   if (!endpoint) {    throw new Error("Source endpoint is required.");  }   const shared = {    name,    kind: state.kind,    endpoint,    status: buildRequestedSourceStatus(state),    enabled: state.enabled,    namespace: trimToNull(state.namespace),    auth: buildAuthPayload(state),  } satisfies Pick<CreateSourcePayload, "name" | "kind" | "endpoint" | "status" | "enabled" | "namespace" | "auth">;   if (state.kind === "mcp") {    return {      ...shared,      binding: {        transport: state.transport === "" ? "auto" : state.transport,        queryParams: parseJsonStringMap("Query params", state.queryParamsText),        headers: parseJsonStringMap("Request headers", state.headersText),

Lines 1–500 of 1527.