Fix OAuth prompt dismissal on terminal status
This commit is contained in:
1 parent
6413a9374d
commit
c1349fceab
3 files changed
+126
-39
No files matched your search
@@ -1,28 +1,12 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import type { DesktopEvent } from "@shared/types";
|
import type { DesktopEvent } from "@shared/types";
|
||||||
import { ActionButton, Modal } from "../../ui";
|
import { ActionButton, Modal } from "../../ui";
|
||||||
import { useT } from "../../../i18n";
|
import { useT } from "../../../i18n";
|
||||||
|
import { nextOAuthPromptState, type PendingOAuthPrompt } from "./oauth-prompt-events";
|
||||||
|
|
||||||
const desktop = (typeof window !== "undefined" ? window.agentCompany : null) as
|
const desktop = (typeof window !== "undefined" ? window.agentCompany : null) as
|
||||||
| (Window["agentCompany"] | null);
|
| (Window["agentCompany"] | null);
|
||||||
|
|
||||||
type PendingPrompt =
|
|
||||||
| {
|
|
||||||
kind: "prompt";
|
|
||||||
connectorId: string;
|
|
||||||
requestId: string;
|
|
||||||
message: string;
|
|
||||||
placeholder?: string;
|
|
||||||
allowEmpty?: boolean;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
kind: "manual-code";
|
|
||||||
connectorId: string;
|
|
||||||
requestId: string;
|
|
||||||
authUrl?: string;
|
|
||||||
instructions?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens for `oauth:prompt-request` and `oauth:manual-code-request` push
|
* Listens for `oauth:prompt-request` and `oauth:manual-code-request` push
|
||||||
* events and renders a single modal at a time. The modal collects the input
|
* events and renders a single modal at a time. The modal collects the input
|
||||||
@@ -30,32 +14,17 @@ type PendingPrompt =
|
|||||||
*/
|
*/
|
||||||
export function OAuthPromptDialog() {
|
export function OAuthPromptDialog() {
|
||||||
const t = useT();
|
const t = useT();
|
||||||
const [pending, setPending] = useState<PendingPrompt | null>(null);
|
const [pending, setPending] = useState<PendingOAuthPrompt | null>(null);
|
||||||
|
const pendingRef = useRef<PendingOAuthPrompt | null>(null);
|
||||||
const [value, setValue] = useState("");
|
const [value, setValue] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!desktop) return;
|
if (!desktop) return;
|
||||||
const unsubscribe = desktop.subscribe((event: DesktopEvent) => {
|
const unsubscribe = desktop.subscribe((event: DesktopEvent) => {
|
||||||
if (event.type === "oauth:prompt-request") {
|
const next = nextOAuthPromptState(pendingRef.current, event);
|
||||||
setPending({
|
if (next !== pendingRef.current) setValue("");
|
||||||
kind: "prompt",
|
pendingRef.current = next;
|
||||||
connectorId: event.connectorId,
|
setPending(next);
|
||||||
requestId: event.requestId,
|
|
||||||
message: event.message,
|
|
||||||
placeholder: event.placeholder,
|
|
||||||
allowEmpty: event.allowEmpty,
|
|
||||||
});
|
|
||||||
setValue("");
|
|
||||||
} else if (event.type === "oauth:manual-code-request") {
|
|
||||||
setPending({
|
|
||||||
kind: "manual-code",
|
|
||||||
connectorId: event.connectorId,
|
|
||||||
requestId: event.requestId,
|
|
||||||
authUrl: event.authUrl,
|
|
||||||
instructions: event.instructions,
|
|
||||||
});
|
|
||||||
setValue("");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, []);
|
}, []);
|
||||||
@@ -78,6 +47,7 @@ export function OAuthPromptDialog() {
|
|||||||
code: value.trim(),
|
code: value.trim(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
pendingRef.current = null;
|
||||||
setPending(null);
|
setPending(null);
|
||||||
setValue("");
|
setValue("");
|
||||||
};
|
};
|
||||||
@@ -85,6 +55,7 @@ export function OAuthPromptDialog() {
|
|||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
if (!desktop || !pending) return;
|
if (!desktop || !pending) return;
|
||||||
await desktop.cancelOAuth({ connectorId: pending.connectorId });
|
await desktop.cancelOAuth({ connectorId: pending.connectorId });
|
||||||
|
pendingRef.current = null;
|
||||||
setPending(null);
|
setPending(null);
|
||||||
setValue("");
|
setValue("");
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { DesktopEvent } from "@shared/types";
|
||||||
|
|
||||||
|
export type PendingOAuthPrompt =
|
||||||
|
| {
|
||||||
|
kind: "prompt";
|
||||||
|
connectorId: string;
|
||||||
|
requestId: string;
|
||||||
|
message: string;
|
||||||
|
placeholder?: string;
|
||||||
|
allowEmpty?: boolean;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "manual-code";
|
||||||
|
connectorId: string;
|
||||||
|
requestId: string;
|
||||||
|
authUrl?: string;
|
||||||
|
instructions?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function nextOAuthPromptState(
|
||||||
|
current: PendingOAuthPrompt | null,
|
||||||
|
event: DesktopEvent,
|
||||||
|
): PendingOAuthPrompt | null {
|
||||||
|
if (event.type === "oauth:prompt-request") {
|
||||||
|
return {
|
||||||
|
kind: "prompt",
|
||||||
|
connectorId: event.connectorId,
|
||||||
|
requestId: event.requestId,
|
||||||
|
message: event.message,
|
||||||
|
placeholder: event.placeholder,
|
||||||
|
allowEmpty: event.allowEmpty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "oauth:manual-code-request") {
|
||||||
|
return {
|
||||||
|
kind: "manual-code",
|
||||||
|
connectorId: event.connectorId,
|
||||||
|
requestId: event.requestId,
|
||||||
|
authUrl: event.authUrl,
|
||||||
|
instructions: event.instructions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type === "oauth:status" && current?.connectorId === event.connectorId) {
|
||||||
|
return event.status === "in_progress" || event.status === "pending"
|
||||||
|
? current
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { DesktopEvent } from "@shared/types";
|
||||||
|
import {
|
||||||
|
nextOAuthPromptState,
|
||||||
|
type PendingOAuthPrompt,
|
||||||
|
} from "../../src/renderer/components/settings/dialogs/oauth-prompt-events";
|
||||||
|
|
||||||
|
describe("OAuth prompt event state", () => {
|
||||||
|
const pendingManualCode: PendingOAuthPrompt = {
|
||||||
|
kind: "manual-code",
|
||||||
|
connectorId: "openai-codex",
|
||||||
|
requestId: "request-1",
|
||||||
|
authUrl: "https://example.com/oauth",
|
||||||
|
instructions: "Authorize",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("closes a stale manual-code dialog when the same connector becomes active", () => {
|
||||||
|
const event: DesktopEvent = {
|
||||||
|
type: "oauth:status",
|
||||||
|
connectorId: "openai-codex",
|
||||||
|
status: "active",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(nextOAuthPromptState(pendingManualCode, event)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the dialog open for in-progress status events", () => {
|
||||||
|
const event: DesktopEvent = {
|
||||||
|
type: "oauth:status",
|
||||||
|
connectorId: "openai-codex",
|
||||||
|
status: "in_progress",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(nextOAuthPromptState(pendingManualCode, event)).toEqual(pendingManualCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignores terminal status events for a different connector", () => {
|
||||||
|
const event: DesktopEvent = {
|
||||||
|
type: "oauth:status",
|
||||||
|
connectorId: "claude-pro",
|
||||||
|
status: "active",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(nextOAuthPromptState(pendingManualCode, event)).toEqual(pendingManualCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("opens manual-code requests from the event payload", () => {
|
||||||
|
const event: DesktopEvent = {
|
||||||
|
type: "oauth:manual-code-request",
|
||||||
|
connectorId: "openai-codex",
|
||||||
|
requestId: "request-2",
|
||||||
|
authUrl: "https://example.com/code",
|
||||||
|
instructions: "Copy code",
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(nextOAuthPromptState(null, event)).toEqual({
|
||||||
|
kind: "manual-code",
|
||||||
|
connectorId: "openai-codex",
|
||||||
|
requestId: "request-2",
|
||||||
|
authUrl: "https://example.com/code",
|
||||||
|
instructions: "Copy code",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user