import { ControlPlaneLayout } from "./ControlPlaneLayout.tsx"; import { useEffect, useRef, useState } from "octane"; import { Button } from "octane-kumo/components/button"; import { LayerCard } from "octane-kumo/components/layer-card"; import { messages, type ErrorCode } from "../errors.ts"; import { useInstallationStatus } from "./installation-status.tsx"; import { InstallationStatus } from "./InstallationStatus.tsx"; import { ProviderSetup } from "./ProviderSetup.tsx"; import { DomainSetup } from "./DomainSetup.tsx"; import { UpdateInstallation } from "./UpdateInstallation.tsx"; import { PublisherQueryProvider, usePublisherSession, } from "./PublisherQueryProvider"; import { usePublisherIdentityQuery } from "./queries/use-publisher-identity-query"; import { useConnectionQuery, connectionOptions, } from "./queries/use-connection-query"; import { PublisherReadError } from "./publisher-request"; export function Onboarding() { return ( ); } function PublisherApp() { const [callback, setCallback] = useState<{ error: string | null; notice: string | null; }>({ error: null, notice: null }); const [updateInstallationId, setUpdateInstallationId] = useState< string | null | undefined >(undefined); const [providerInstallationId, setProviderInstallationId] = useState< string | null | undefined >(undefined); const [domainInstallationId, setDomainInstallationId] = useState< string | null | undefined >(undefined); useEffect(() => { const params = new URLSearchParams(window.location.search); setCallback({ error: params.get("error"), notice: params.get("notice") }); if (params.has("error") || params.has("notice")) window.history.replaceState(null, "", "/connect"); const installationId = params.get("installationId"); const updateId = params.get("updateInstallation"); setUpdateInstallationId( updateId && /^[a-f0-9]{32}$/.test(updateId) ? updateId : null, ); setProviderInstallationId( params.get("enableProvider") === "openrouter" && installationId && /^[a-f0-9]{32}$/.test(installationId) ? installationId : null, ); const domainId = params.get("configureDomain"); setDomainInstallationId( domainId && /^[a-f0-9]{32}$/.test(domainId) ? domainId : null, ); }, []); const session = usePublisherSession(); const identity = usePublisherIdentityQuery( updateInstallationId ?? domainInstallationId ?? providerInstallationId ?? null, updateInstallationId !== undefined, ); const owner = session.getSnapshot(); const ownerKey = String(owner.epoch); if (updateInstallationId) return ( ); if (providerInstallationId) return ( ); if (domainInstallationId) return ( ); return ( ); } function StandardOnboarding({ deferred, identity, callback, }: { deferred: boolean; callback: { error: string | null; notice: string | null }; identity: ReturnType; }) { const session = usePublisherSession(); const installations = useInstallationStatus(identity); const [busy, setBusy] = useState(false); const [commandError, setError] = useState(null); const [notice, setNotice] = useState(""); const generation = useRef(0); const epoch = session.getSnapshot().epoch; const query = useConnectionQuery({ enabled: !deferred && !busy, bootstrap: true, }); const connection = query.error instanceof PublisherReadError && query.error.status === 401 ? null : (query.data ?? null); const loading = !deferred && !session.getSnapshot().signedOut && !query.data && (identity.isFetching || query.isFetching); const message = (value: unknown): ErrorCode => typeof value === "string" && Object.hasOwn(messages, value) ? (value as ErrorCode) : "temporarily_unavailable"; const error = commandError ?? (query.error instanceof PublisherReadError ? query.error.code === "not_connected" ? null : message(query.error.code) : query.error ? "temporarily_unavailable" : connection && !connection.accounts.length ? "no_accounts" : null); const load = () => { setError(null); if (!session.getSnapshot().ownerSubject) void identity.refetch(); void query.refetch(); }; useEffect(() => { if (deferred) return; const callbackError = callback.error; if (callbackError) { setError(message(callbackError)); } if (callback.notice === "revocation_pending") setNotice( "You are signed out of Flarebot. Cloudflare could not confirm revocation; remove Flarebot from your Cloudflare authorized applications to revoke access immediately.", ); return () => { generation.current++; }; }, [deferred, callback]); async function select(accountId: string) { if ( busy || loading || !navigator.onLine || !session.getSnapshot().ownerSubject ) return; const version = ++generation.current; setBusy(true); setError(null); setNotice(""); try { await session.client.cancelQueries({ queryKey: connectionOptions(session).queryKey, }); if ( version !== generation.current || session.getSnapshot().epoch !== epoch ) return; const response = await fetch("/api/account", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ accountId }).toString(), signal: AbortSignal.timeout(15_000), }); const data = await response.json(); if ( version !== generation.current || session.getSnapshot().epoch !== epoch ) return; if (!response.ok) { setError(message(data.error)); if (data.error === "reauthorization_required") void session.client.invalidateQueries({ queryKey: connectionOptions(session).queryKey, }); } else { if (data.ownerSubject !== session.getSnapshot().ownerSubject) { session.identify(data.ownerSubject); return; } await session.client.cancelQueries({ queryKey: connectionOptions(session).queryKey, }); if ( version !== generation.current || session.getSnapshot().epoch !== epoch ) return; session.client.setQueryData( connectionOptions(session).queryKey, (current) => current ? { ...current, selectedAccountId: data.selectedAccountId } : current, ); setNotice("Account selected. New installations will use this account."); } } catch { if ( version === generation.current && session.getSnapshot().epoch === epoch ) setError("temporarily_unavailable"); } finally { if ( version === generation.current && session.getSnapshot().epoch === epoch ) setBusy(false); } } const setupRequired = error === "oauth_setup_required" || error === "oauth_capability_unavailable"; return (

Cloudflare account

Choose where your agent runs and manage your connection.

{connection ? "Choose an account" : "Authorize Cloudflare"}

{connection ? "Select an account available with your authorization. Access is checked again when you select it." : "Connect to Cloudflare to authorize deployment and choose where Flarebot will run."}

{loading &&

Checking your connection…

} {error &&

{messages[error]}

} {notice &&

{notice}

} {connection && (
    {connection.accounts.map((account) => (
  • {account.name}

    {account.id}

  • ))}
)}
{!loading && !setupRequired && (
)} {!loading && (error === "temporarily_unavailable" || error === "account_denied" || setupRequired) && ( )} {connection && (
installations.forget()} >
)}

Permissions and access

Understand what your Cloudflare connection allows.

What you authorize

Account selection and deployment of Flarebot’s Worker, static assets, workers.dev address, and Sandbox container. Cloudflare shows the exact publisher-reviewed permissions before you authorize.

Deployment authorization is short-lived. You reconnect for later installation or update operations. Selecting an account does not deploy resources.

); }