feat(api): add command authentication attempts
This commit is contained in:
parent
a772767e9b
commit
b5177adb5b
18 changed files with 1138 additions and 16 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { TextAttributes } from "@opentui/core"
|
||||
import type {
|
||||
ConnectionInfo,
|
||||
IntegrationCommandConnectOutput,
|
||||
IntegrationInfo,
|
||||
IntegrationOauthConnectOutput,
|
||||
IntegrationOAuthMethod,
|
||||
|
|
@ -28,6 +29,7 @@ const INTEGRATION_PRIORITY: Record<string, number> = {
|
|||
|
||||
type ConnectMethod = Exclude<IntegrationInfo["methods"][number], { type: "env" }>
|
||||
type IntegrationAttempt = IntegrationOauthConnectOutput["data"]
|
||||
type CommandAttempt = IntegrationCommandConnectOutput["data"]
|
||||
type OnIntegrationConnected = (providerID?: string) => void
|
||||
|
||||
export function integrationOptions(list: IntegrationInfo[]) {
|
||||
|
|
@ -167,9 +169,130 @@ function openMethod(
|
|||
dialog.replace(() => <KeyMethod integration={integration} method={method} onConnected={onConnected} />)
|
||||
return
|
||||
}
|
||||
if (method.type === "command") {
|
||||
dialog.replace(() => <CommandStarting integration={integration} method={method} onConnected={onConnected} />)
|
||||
return
|
||||
}
|
||||
void beginOAuth(integration, method, dialog, onConnected)
|
||||
}
|
||||
|
||||
function CommandStarting(props: {
|
||||
integration: IntegrationInfo
|
||||
method: Extract<ConnectMethod, { type: "command" }>
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
|
||||
onMount(() => {
|
||||
void client.api.integration.command
|
||||
.connect({
|
||||
integrationID: props.integration.id,
|
||||
methodID: props.method.id,
|
||||
location: location(data),
|
||||
})
|
||||
.then((result) =>
|
||||
dialog.replace(() => (
|
||||
<CommandPending
|
||||
integration={props.integration}
|
||||
title={props.method.label}
|
||||
attempt={result.data}
|
||||
onConnected={props.onConnected}
|
||||
/>
|
||||
)),
|
||||
)
|
||||
.catch((cause) => {
|
||||
toast.show({ variant: "error", message: message(cause) })
|
||||
dialog.clear()
|
||||
})
|
||||
})
|
||||
|
||||
return <CommandView title={props.method.label} output="" message="Starting command..." />
|
||||
}
|
||||
|
||||
function CommandPending(props: {
|
||||
integration: IntegrationInfo
|
||||
title: string
|
||||
attempt: CommandAttempt
|
||||
onConnected?: OnIntegrationConnected
|
||||
}) {
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const client = useClient()
|
||||
const toast = useToast()
|
||||
const [output, setOutput] = createSignal("")
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
let settled = false
|
||||
|
||||
const poll = () => {
|
||||
void client.api.integration.command
|
||||
.status({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
})
|
||||
.then((result) => {
|
||||
const status = result.data
|
||||
if (status.status === "pending") {
|
||||
setOutput(status.message ?? "")
|
||||
timer = setTimeout(poll, 500)
|
||||
return
|
||||
}
|
||||
settled = true
|
||||
if (status.status === "complete") {
|
||||
void connected(props.integration, data, dialog, toast, props.onConnected)
|
||||
return
|
||||
}
|
||||
toast.show({
|
||||
variant: "error",
|
||||
message: status.status === "failed" ? status.message : "Authentication expired",
|
||||
})
|
||||
dialog.clear()
|
||||
})
|
||||
.catch((cause) => {
|
||||
settled = true
|
||||
toast.show({ variant: "error", message: message(cause) })
|
||||
dialog.clear()
|
||||
})
|
||||
}
|
||||
|
||||
onMount(poll)
|
||||
onCleanup(() => {
|
||||
if (timer) clearTimeout(timer)
|
||||
if (settled) return
|
||||
void client.api.integration.command.cancel({
|
||||
integrationID: props.integration.id,
|
||||
attemptID: props.attempt.attemptID,
|
||||
location: location(data),
|
||||
})
|
||||
})
|
||||
|
||||
return <CommandView title={props.title} output={output()} message="Waiting for command to finish..." />
|
||||
}
|
||||
|
||||
function CommandView(props: { title: string; output: string; message: string }) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
esc close
|
||||
</text>
|
||||
</box>
|
||||
<box backgroundColor={theme.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<text fg={theme.text}>{props.output}</text>
|
||||
</box>
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
||||
function KeyMethod(props: {
|
||||
integration: IntegrationInfo
|
||||
method: Extract<ConnectMethod, { type: "key" }>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue