mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-06-11 01:45:58 +00:00
Fix dev startup and channel connect popup
This commit is contained in:
@@ -15,19 +15,17 @@ import {
|
||||
useChannelProviders,
|
||||
useConnectChannelProvider,
|
||||
} from "@/core/channels/hooks";
|
||||
import {
|
||||
closeConnectWindow,
|
||||
openConnectUrl,
|
||||
prepareConnectWindow,
|
||||
} from "@/core/channels/open-connect-url";
|
||||
import type { ChannelProvider } from "@/core/channels/types";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { ChannelProviderIcon } from "./channel-provider-icon";
|
||||
|
||||
function openConnectUrl(url: string) {
|
||||
const opened = window.open(url, "_blank", "noopener,noreferrer");
|
||||
if (!opened) {
|
||||
window.location.assign(url);
|
||||
}
|
||||
}
|
||||
|
||||
function providerCanConnect(provider: ChannelProvider): boolean {
|
||||
return (
|
||||
provider.enabled &&
|
||||
@@ -97,8 +95,11 @@ export function WorkspaceChannelsList() {
|
||||
!provider.configured ? t.channels.unconfigured : undefined
|
||||
}
|
||||
onClick={() => {
|
||||
const connectWindow = prepareConnectWindow();
|
||||
connectMutation.mutate(provider.provider, {
|
||||
onSuccess: (result) => openConnectUrl(result.url),
|
||||
onSuccess: (result) =>
|
||||
openConnectUrl(result.url, connectWindow),
|
||||
onError: () => closeConnectWindow(connectWindow),
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -24,6 +24,11 @@ import {
|
||||
useConnectChannelProvider,
|
||||
useDisconnectChannelConnection,
|
||||
} from "@/core/channels/hooks";
|
||||
import {
|
||||
closeConnectWindow,
|
||||
openConnectUrl,
|
||||
prepareConnectWindow,
|
||||
} from "@/core/channels/open-connect-url";
|
||||
import type { ChannelConnection, ChannelProvider } from "@/core/channels/types";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -32,13 +37,6 @@ import { ChannelProviderIcon } from "../channels/channel-provider-icon";
|
||||
|
||||
import { SettingsSection } from "./settings-section";
|
||||
|
||||
function openConnectUrl(url: string) {
|
||||
const opened = window.open(url, "_blank", "noopener,noreferrer");
|
||||
if (!opened) {
|
||||
window.location.assign(url);
|
||||
}
|
||||
}
|
||||
|
||||
function getProviderDescription(
|
||||
provider: ChannelProvider,
|
||||
descriptions: Record<string, string>,
|
||||
@@ -144,8 +142,11 @@ function ChannelProviderItem({
|
||||
disabled={!canConnect || isConnecting}
|
||||
title={!provider.configured ? t.channels.unconfigured : undefined}
|
||||
onClick={() => {
|
||||
const connectWindow = prepareConnectWindow();
|
||||
connectMutation.mutate(provider.provider, {
|
||||
onSuccess: (result) => openConnectUrl(result.url),
|
||||
onSuccess: (result) =>
|
||||
openConnectUrl(result.url, connectWindow),
|
||||
onError: () => closeConnectWindow(connectWindow),
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
export type ChannelConnectWindow = Window | null;
|
||||
|
||||
export function prepareConnectWindow(): ChannelConnectWindow {
|
||||
const opened = window.open("about:blank", "_blank");
|
||||
if (opened) {
|
||||
opened.opener = null;
|
||||
}
|
||||
return opened;
|
||||
}
|
||||
|
||||
export function openConnectUrl(
|
||||
url: string,
|
||||
connectWindow: ChannelConnectWindow = prepareConnectWindow(),
|
||||
) {
|
||||
if (connectWindow && !connectWindow.closed) {
|
||||
connectWindow.location.replace(url);
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.assign(url);
|
||||
}
|
||||
|
||||
export function closeConnectWindow(connectWindow: ChannelConnectWindow) {
|
||||
if (connectWindow && !connectWindow.closed) {
|
||||
connectWindow.close();
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,19 @@ function mockChannelsAPI(page: Page) {
|
||||
body: JSON.stringify({ connections: [] }),
|
||||
});
|
||||
});
|
||||
|
||||
void page.route("**/api/channels/slack/connect", (route) => {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
provider: "slack",
|
||||
mode: "oauth",
|
||||
url: "http://localhost:3000/mock-slack-oauth?client_id=dev&state=test",
|
||||
expires_in: 600,
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test.describe("IM channels", () => {
|
||||
@@ -73,5 +86,16 @@ test.describe("IM channels", () => {
|
||||
await expect(page.getByText("Telegram direct messages")).toBeVisible();
|
||||
await expect(page.getByText("Slack workspace messages")).toBeVisible();
|
||||
await expect(page.getByText("Discord server messages")).toBeVisible();
|
||||
|
||||
const dialog = page.getByRole("dialog", { name: "Settings" });
|
||||
const connectButtons = dialog.getByRole("button", { name: "Connect" });
|
||||
await expect(connectButtons).toHaveCount(3);
|
||||
|
||||
const popupPromise = page.waitForEvent("popup");
|
||||
await connectButtons.nth(1).click();
|
||||
const popup = await popupPromise;
|
||||
await expect(page).toHaveURL(/\/workspace\/chats\/new/);
|
||||
await expect(popup).toHaveURL(/\/mock-slack-oauth/);
|
||||
await popup.close();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
|
||||
import {
|
||||
closeConnectWindow,
|
||||
openConnectUrl,
|
||||
prepareConnectWindow,
|
||||
} from "@/core/channels/open-connect-url";
|
||||
|
||||
type PopupStub = {
|
||||
closed: boolean;
|
||||
close: ReturnType<typeof vi.fn>;
|
||||
location: {
|
||||
replace: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
opener: unknown;
|
||||
};
|
||||
|
||||
function stubWindow(openResult: PopupStub | null) {
|
||||
const assign = vi.fn();
|
||||
const open = vi.fn(() => openResult);
|
||||
vi.stubGlobal("window", {
|
||||
open,
|
||||
location: { assign },
|
||||
});
|
||||
return { assign, open };
|
||||
}
|
||||
|
||||
function makePopup(): PopupStub {
|
||||
return {
|
||||
closed: false,
|
||||
close: vi.fn(),
|
||||
location: { replace: vi.fn() },
|
||||
opener: {},
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("channel connect window helpers", () => {
|
||||
test("opens a blank tab synchronously and detaches opener", () => {
|
||||
const popup = makePopup();
|
||||
const { open } = stubWindow(popup);
|
||||
|
||||
const prepared = prepareConnectWindow();
|
||||
|
||||
expect(open).toHaveBeenCalledWith("about:blank", "_blank");
|
||||
expect(prepared).toBe(popup);
|
||||
expect(popup.opener).toBeNull();
|
||||
});
|
||||
|
||||
test("navigates a prepared popup without opening another window", () => {
|
||||
const popup = makePopup();
|
||||
const { assign, open } = stubWindow(null);
|
||||
|
||||
openConnectUrl(
|
||||
"https://t.me/deerflow_bot?start=state",
|
||||
popup as unknown as Window,
|
||||
);
|
||||
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
expect(assign).not.toHaveBeenCalled();
|
||||
expect(popup.location.replace).toHaveBeenCalledWith(
|
||||
"https://t.me/deerflow_bot?start=state",
|
||||
);
|
||||
});
|
||||
|
||||
test("falls back to current-window navigation when no popup is available", () => {
|
||||
const { assign } = stubWindow(null);
|
||||
|
||||
openConnectUrl("https://slack.com/oauth/v2/authorize");
|
||||
|
||||
expect(assign).toHaveBeenCalledWith("https://slack.com/oauth/v2/authorize");
|
||||
});
|
||||
|
||||
test("closes a prepared popup on connect failure", () => {
|
||||
const popup = makePopup();
|
||||
|
||||
closeConnectWindow(popup as unknown as Window);
|
||||
|
||||
expect(popup.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user