From 8e09e8c6121f03244a1f25281b506a90bcb355d7 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 18 Mar 2026 22:47:51 -0500 Subject: feat: integrate multistep auth flows into desktop app (#18103) --- .../app/src/components/dialog-connect-provider.tsx | 150 ++++++++++++++++++--- 1 file changed, 134 insertions(+), 16 deletions(-) (limited to 'packages/app/src/components') diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx index b042205cf..e4fe9e7c4 100644 --- a/packages/app/src/components/dialog-connect-provider.tsx +++ b/packages/app/src/components/dialog-connect-provider.tsx @@ -15,7 +15,6 @@ import { Link } from "@/components/link" import { useLanguage } from "@/context/language" import { useGlobalSDK } from "@/context/global-sdk" import { useGlobalSync } from "@/context/global-sync" -import { usePlatform } from "@/context/platform" import { DialogSelectModel } from "./dialog-select-model" import { DialogSelectProvider } from "./dialog-select-provider" @@ -23,7 +22,6 @@ export function DialogConnectProvider(props: { provider: string }) { const dialog = useDialog() const globalSync = useGlobalSync() const globalSDK = useGlobalSDK() - const platform = usePlatform() const language = useLanguage() const alive = { value: true } @@ -49,13 +47,14 @@ export function DialogConnectProvider(props: { provider: string }) { const [store, setStore] = createStore({ methodIndex: undefined as undefined | number, authorization: undefined as undefined | ProviderAuthAuthorization, - state: "pending" as undefined | "pending" | "complete" | "error", + state: "pending" as undefined | "pending" | "complete" | "error" | "prompt", error: undefined as string | undefined, }) type Action = | { type: "method.select"; index: number } | { type: "method.reset" } + | { type: "auth.prompt" } | { type: "auth.pending" } | { type: "auth.complete"; authorization: ProviderAuthAuthorization } | { type: "auth.error"; error: string } @@ -77,6 +76,11 @@ export function DialogConnectProvider(props: { provider: string }) { draft.error = undefined return } + if (action.type === "auth.prompt") { + draft.state = "prompt" + draft.error = undefined + return + } if (action.type === "auth.pending") { draft.state = "pending" draft.error = undefined @@ -120,7 +124,7 @@ export function DialogConnectProvider(props: { provider: string }) { return fallback } - async function selectMethod(index: number) { + async function selectMethod(index: number, inputs?: Record) { if (timer.current !== undefined) { clearTimeout(timer.current) timer.current = undefined @@ -130,6 +134,10 @@ export function DialogConnectProvider(props: { provider: string }) { dispatch({ type: "method.select", index }) if (method.type === "oauth") { + if (method.prompts?.length && !inputs) { + dispatch({ type: "auth.prompt" }) + return + } dispatch({ type: "auth.pending" }) const start = Date.now() await globalSDK.client.provider.oauth @@ -137,6 +145,7 @@ export function DialogConnectProvider(props: { provider: string }) { { providerID: props.provider, method: index, + inputs, }, { throwOnError: true }, ) @@ -163,6 +172,122 @@ export function DialogConnectProvider(props: { provider: string }) { } } + function OAuthPromptsView() { + const [formStore, setFormStore] = createStore({ + value: {} as Record, + index: 0, + }) + + const prompts = createMemo(() => method()?.prompts ?? []) + const matches = (prompt: NonNullable[number]>, value: Record) => { + if (!prompt.when) return true + const actual = value[prompt.when.key] + if (actual === undefined) return false + return prompt.when.op === "eq" ? actual === prompt.when.value : actual !== prompt.when.value + } + const current = createMemo(() => { + const all = prompts() + const index = all.findIndex((prompt, index) => index >= formStore.index && matches(prompt, formStore.value)) + if (index === -1) return + return { + index, + prompt: all[index], + } + }) + const valid = createMemo(() => { + const item = current() + if (!item || item.prompt.type !== "text") return false + const value = formStore.value[item.prompt.key] ?? "" + return value.trim().length > 0 + }) + + async function next(index: number, value: Record) { + if (store.methodIndex === undefined) return + const next = prompts().findIndex((prompt, i) => i > index && matches(prompt, value)) + if (next !== -1) { + setFormStore("index", next) + return + } + await selectMethod(store.methodIndex, value) + } + + async function handleSubmit(e: SubmitEvent) { + e.preventDefault() + const item = current() + if (!item || item.prompt.type !== "text") return + if (!valid()) return + await next(item.index, formStore.value) + } + + const item = () => current() + const text = createMemo(() => { + const prompt = item()?.prompt + if (!prompt || prompt.type !== "text") return + return prompt + }) + const select = createMemo(() => { + const prompt = item()?.prompt + if (!prompt || prompt.type !== "select") return + return prompt + }) + + return ( +
+ + + { + const prompt = text() + if (!prompt) return + setFormStore("value", prompt.key, value) + }} + /> + + + +
+
{select()?.message}
+
+ x.value} + current={select()?.options.find((x) => x.value === formStore.value[select()!.key])} + onSelect={(value) => { + if (!value) return + const prompt = select() + if (!prompt) return + const nextValue = { + ...formStore.value, + [prompt.key]: value.value, + } + setFormStore("value", prompt.key, value.value) + void next(item()!.index, nextValue) + }} + > + {(option) => ( +
+
+ + {option.label} + {option.hint} +
+ )} + +
+
+ + + + ) + } + let listRef: ListRef | undefined function handleKey(e: KeyboardEvent) { if (e.key === "Enter" && e.target instanceof HTMLInputElement) { @@ -301,7 +426,7 @@ export function DialogConnectProvider(props: { provider: string }) { error={formStore.error} />
@@ -314,12 +439,6 @@ export function DialogConnectProvider(props: { provider: string }) { error: undefined as string | undefined, }) - onMount(() => { - if (store.authorization?.method === "code" && store.authorization?.url) { - platform.openLink(store.authorization.url) - } - }) - async function handleSubmit(e: SubmitEvent) { e.preventDefault() @@ -368,7 +487,7 @@ export function DialogConnectProvider(props: { provider: string }) { error={formStore.error} /> @@ -386,10 +505,6 @@ export function DialogConnectProvider(props: { provider: string }) { onMount(() => { void (async () => { - if (store.authorization?.url) { - platform.openLink(store.authorization.url) - } - const result = await globalSDK.client.provider.oauth .callback({ providerID: props.provider, @@ -470,6 +585,9 @@ export function DialogConnectProvider(props: { provider: string }) {
+ + +
-- cgit v1.2.3