131 lines · 4.6 KB · TypeScript
RawDownload
import { useCallback } from "react";import { useAtomSet, useAtomValue } from "@effect/atom-react";import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { connectionsAtom } from "@executor-js/react/api/atoms";import { useScope, useUserScope } from "@executor-js/react/api/scope-context";import { connectionWriteKeys, sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";import {  OAuthSignInButton,  oauthCallbackUrl,  oauthConnectionId,  useOAuthPopupFlow,  type OAuthCompletionPayload,} from "@executor-js/react/plugins/oauth-sign-in";import { slugifyNamespace } from "@executor-js/react/plugins/source-identity";import { ConnectionId, ScopeId } from "@executor-js/sdk/core"; import { graphqlSourceAtom, graphqlSourceBindingsAtom, setGraphqlSourceBinding } from "./atoms";import type {  ConfiguredGraphqlCredentialValue,  GraphqlSourceBindingRef,  HeaderValue,} from "../sdk/types"; const valuesForOAuth = (  values: Record<string, ConfiguredGraphqlCredentialValue>,  bindings: readonly GraphqlSourceBindingRef[],): Record<string, HeaderValue> | undefined => {  const bySlot = new Map(bindings.map((binding) => [binding.slot, binding]));  const out: Record<string, HeaderValue> = {};  for (const [name, value] of Object.entries(values)) {    if (typeof value === "string") {      out[name] = value;      continue;    }    const binding = bySlot.get(value.slot);    if (binding?.value.kind === "secret") {      out[name] = value.prefix        ? { secretId: binding.value.secretId, prefix: value.prefix }        : { secretId: binding.value.secretId };    } else if (binding?.value.kind === "text") {      out[name] = binding.value.text;    }  }  return Object.keys(out).length > 0 ? out : undefined;}; export default function GraphqlSignInButton(props: { sourceId: string }) {  const scopeId = useScope();  const userScopeId = useUserScope();  const sourceResult = useAtomValue(graphqlSourceAtom(scopeId, props.sourceId));  const source =    AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null;  const sourceScope = source ? ScopeId.make(source.scope) : scopeId;  const bindingsResult = useAtomValue(    graphqlSourceBindingsAtom(scopeId, props.sourceId, sourceScope),  );  const connectionsResult = useAtomValue(connectionsAtom(scopeId));  const setBinding = useAtomSet(setGraphqlSourceBinding, { mode: "promise" });  const oauth = useOAuthPopupFlow({    popupName: "graphql-oauth",  });   const oauth2 = source?.auth.kind === "oauth2" ? source.auth : null;  const bindings = AsyncResult.isSuccess(bindingsResult) ? bindingsResult.value : null;  const connectionBinding = bindings?.find(    (binding) => oauth2 !== null && binding.slot === oauth2.connectionSlot,  );  const boundConnectionId =    connectionBinding?.value.kind === "connection" ? connectionBinding.value.connectionId : null;  const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : null;  const isConnected =    boundConnectionId !== null &&    connections !== null &&    connections.some((c: { readonly id: string }) => c.id === boundConnectionId);   const handleSignIn = useCallback(async () => {    if (!source || !oauth2) return;    const namespaceSlug = slugifyNamespace(source.namespace) || "graphql";    await oauth.start({      payload: {        endpoint: source.endpoint,        redirectUrl: oauthCallbackUrl(),        connectionId: oauthConnectionId({          pluginId: "graphql",          namespace: namespaceSlug,        }),        headers: valuesForOAuth(source.headers, bindings ?? []),        queryParams: valuesForOAuth(source.queryParams, bindings ?? []),        tokenScope: userScopeId,        strategy: { kind: "dynamic-dcr" },        pluginId: "graphql",        identityLabel: `${source.name.trim() || source.namespace || "GraphQL"} OAuth`,      },      onSuccess: async (result: OAuthCompletionPayload) => {        await setBinding({          params: { scopeId },          payload: {            sourceId: props.sourceId,            sourceScope,            scope: userScopeId,            slot: oauth2.connectionSlot,            value: { kind: "connection", connectionId: ConnectionId.make(result.connectionId) },          },          reactivityKeys: [...sourceWriteKeys, ...connectionWriteKeys],        });      },    });  }, [    source,    oauth2,    bindings,    scopeId,    props.sourceId,    sourceScope,    setBinding,    oauth,    userScopeId,  ]);   if (!oauth2) return null;   return (    <OAuthSignInButton      busy={oauth.busy}      error={oauth.error}      isConnected={isConnected}      onSignIn={() => void handleSignIn()}    />  );}