React

Provider, hooks y el botón hospedado en una aplicación de React.

bash
npm install @andco/sdk @andco/sdk-react

Montar el provider

src/App.tsxtsx
import { AndcoProvider } from "@andco/sdk-react";

const clientOptions = {
  clientId: import.meta.env.VITE_ANDCO_CLIENT_ID,
  redirectTo: new URL("/", window.location.origin).href,
  initialScopes: ["openid", "email", "profile"],
} as const;

export function App() {
  return (
    <AndcoProvider clientOptions={clientOptions}>
      <Account />
    </AndcoProvider>
  );
}

El provider construye la instancia con un inicializador perezoso. Es seguro en StrictMode y bajo Suspense: construir no hace entrada ni salida, y el almacén de sesión solo empieza a cargar cuando algo se suscribe, cosa que React hace al confirmar el render, no al ejecutarlo.

Leer la sesión

src/Account.tsxtsx
import { useAndcoSession } from "@andco/sdk-react";

export function Account() {
  const session = useAndcoSession();

  if (session === undefined) return <Skeleton />;
  if (!session) return <SignIn />;
  return <p>{session.user.email}</p>;
}

Los tres estados importan. Tratar undefined como «sin sesión» hace parpadear la pantalla de ingreso en cada carga.

useAndcoUser() devuelve directamente la persona, con los mismos tres estados.

El botón

tsx
import { AndcoButton } from "@andco/sdk-react";

<AndcoButton
  authorization={{ scopes: ["openid", "email", "profile"] }}
  onComplete={() => console.log("done")}
  onError={(error) => setMessage(error.message)}
/>;

El botón es una superficie de Andco dentro de un iframe que React monta y desmonta. No acepta endpoints, clientId ni tema: eso lo toma de la instancia, para que no existan dos copias de un mismo valor que puedan discrepar.

Autenticar sin el botón

tsx
import { useAndco } from "@andco/sdk-react";

const andco = useAndco();

await andco.auth.signIn({ scopes: ["openid", "email"] });
await andco.auth.signOut();

Llamar a la API

andco.rest.http es un cliente tipado contra el contrato OpenAPI: la ruta se valida al compilar y data llega con la forma que declara el esquema.

tsx
const andco = useAndco();
const [accounts, setAccounts] = useState<{ id: string; name: string }[] | null>(null);

useEffect(() => {
  let alive = true;
  void andco.rest.http.GET("/accounts").then(({ data, error }) => {
    if (alive && !error) setAccounts(data.data);
  });
  return () => {
    alive = false;
  };
}, [andco]);

No hay un hook para esto a propósito. Un useEffect con el cliente del contexto es tan corto como un hook dedicado y no te obliga a aprender otra API.

Un fallo llega como error, nunca lanzado: es un AndcoAPIError con code para ramificar, status, y response por si necesitas las cabeceras. Un corte de red produce el mismo error, con status en undefined.

Con TanStack Query

Cuando ya usas TanStack Query, no hace falta envolver nada. andco.rest.http es un cliente de openapi-fetch, así que openapi-react-query lo toma tal cual y genera hooks tipados desde el mismo contrato:

bash
npm install @tanstack/react-query openapi-react-query
src/Accounts.tsxtsx
import type { AndCoRestPaths } from "@andco/protocol/transport";
import { useAndco } from "@andco/sdk-react";
import createQueryClient from "openapi-react-query";
import { useMemo } from "react";

export function Accounts() {
  const andco = useAndco();
  // `createQueryClient` es una factory de closures sin caché propia, así que reconstruirla al
  // cambiar la Identidad autorizada no descarta nada: la caché vive en el QueryClient de TanStack.
  const $api = useMemo(() => createQueryClient<AndCoRestPaths>(andco.rest.http), [andco]);
  const accounts = $api.useQuery("get", "/accounts");

  if (accounts.isLoading) return <Skeleton />;
  if (accounts.error) return <p>{accounts.error.message}</p>;
  return <List accounts={accounts.data.data} />;
}

AndCoRestPaths es obligatorio: TypeScript no puede deducir el mapa de rutas a partir de las firmas genéricas de un cliente, así que hay que nombrarlo.

El useMemo tampoco es opcional, pero fíjate en la dependencia: andco.rest, no andco. useAndco() devuelve una referencia nueva en cada cambio de Identidad Autorizada — inicio de sesión, cierre de sesión, un Grant ampliado — mientras que rest es la misma superficie antes y después de iniciar sesión. Memorizar sobre andco reconstruiría el cliente de consultas en cada inicio de sesión sin necesidad.

El error del hook viene tipado desde el contrato — error, required_scope, message. En ejecución es un AndcoAPIError; si necesitas code, status o response, redúcelo con error instanceof AndcoAPIError.

Monta QueryClientProvider por fuera de AndcoProvider. La caché es asunto de tu aplicación, no del SDK, y anidarla adentro sugiere que Andco la exige.

Mostrar un fallo del callback

tsx
useEffect(() => {
  void andco.session.ready.then(({ error }) => {
    if (error) setMessage(error.message);
  });
}, [andco]);

Una vuelta de callback que falla ocurre antes de que tu código llame a nada. Sin esto se ve igual que no tener sesión.