diff --git a/background/index.ts b/background/index.ts index 65cd6b8..ec9b529 100644 --- a/background/index.ts +++ b/background/index.ts @@ -7,6 +7,11 @@ import { getPort } from "@plasmohq/messaging/background" import { Storage } from "@plasmohq/storage" import { skip as pressToSkip } from "~background/messages/api/skip" +import { + requestServerSelection, + type ServerSelectionRequest, + type ServerSelectionResponse +} from "~background/messages/api/select-server" import { STORAGE_SETTINGS } from "~constants" import { getFullUrl } from "~options/components/RemoteSettings" import { defaultSettings, type Settings } from "~options/types" @@ -288,13 +293,88 @@ function handleRemoteDownload( info: DownloadInfo, settings: Settings ): Function | undefined { - const server = settings.remote.servers.find( - (server) => getFullUrl(server) === settings.remote.selectedServer - ) - if (!server) { - return + // If only one server, no servers, or manual selection is disabled, use existing logic + if (settings.remote.servers.length <= 1 || !settings.remote.requireManualSelection) { + const server = settings.remote.servers.find( + (server) => getFullUrl(server) === settings.remote.selectedServer + ) + if (!server) { + return + } + return createDownloadTask(info, server, settings) + } + + // Multiple servers available and manual selection is enabled - show server selector + return async () => { + try { + // Show server selector overlay + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }) + if (tabs.length === 0) { + // Fallback to default server if no active tab + const defaultServer = settings.remote.servers.find( + (server) => getFullUrl(server) === settings.remote.selectedServer + ) + if (defaultServer) { + await createDownloadTask(info, defaultServer, settings)() + } + return + } + + const tabId = tabs[0].id! + const requestId = tabId.toString() + + // Send message to content script to show server selector + await chrome.tabs.sendMessage(tabId, { + name: "show-server-selector", + body: { + servers: settings.remote.servers, + downloadInfo: { + url: info.url, + filename: info.filename + }, + defaultServer: settings.remote.selectedServer + } + }) + + // Wait for server selection + const response = await requestServerSelection(requestId, { + servers: settings.remote.servers, + downloadInfo: { + url: info.url, + filename: info.filename + } + }) + + if (response.cancelled || !response.selectedServer) { + return + } + + // Find selected server and create download task + const selectedServer = settings.remote.servers.find( + (server) => getFullUrl(server) === response.selectedServer + ) + + if (selectedServer) { + await createDownloadTask(info, selectedServer, settings)() + } + } catch (error) { + console.error("Server selection failed:", error) + // Fallback to default server + const defaultServer = settings.remote.servers.find( + (server) => getFullUrl(server) === settings.remote.selectedServer + ) + if (defaultServer) { + await createDownloadTask(info, defaultServer, settings)() + } + } } +} +function createDownloadTask( + info: DownloadInfo, + server: Server, + settings: Settings +): Function { return async () => { const client = new Client({ host: getFullUrl(server), diff --git a/background/messages/api/select-server.ts b/background/messages/api/select-server.ts new file mode 100644 index 0000000..b99ad08 --- /dev/null +++ b/background/messages/api/select-server.ts @@ -0,0 +1,54 @@ +import type { PlasmoMessaging } from "@plasmohq/messaging" + +export interface ServerSelectionRequest { + servers: Server[] + downloadInfo: { + url: string + filename: string + } +} + +export interface ServerSelectionResponse { + selectedServer: string | null + cancelled: boolean +} + +// Store pending server selections +const pendingSelections = new Map void + reject: (error: Error) => void +}>() + +export function requestServerSelection( + requestId: string, + data: ServerSelectionRequest +): Promise { + return new Promise((resolve, reject) => { + pendingSelections.set(requestId, { resolve, reject }) + + // Timeout after 30 seconds + setTimeout(() => { + if (pendingSelections.has(requestId)) { + pendingSelections.delete(requestId) + resolve({ selectedServer: null, cancelled: true }) + } + }, 30000) + }) +} + +const handler: PlasmoMessaging.MessageHandler = ( + req, + res +) => { + const requestId = req.sender?.tab?.id?.toString() || "unknown" + const pending = pendingSelections.get(requestId) + + if (pending) { + pendingSelections.delete(requestId) + pending.resolve(req.body) + } + + res.send() +} + +export default handler \ No newline at end of file diff --git a/contents/server-selector.tsx b/contents/server-selector.tsx new file mode 100644 index 0000000..180d089 --- /dev/null +++ b/contents/server-selector.tsx @@ -0,0 +1,193 @@ +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Radio, + Typography +} from "@mui/material" +import { useState, useEffect } from "react" + +import { sendToBackground } from "@plasmohq/messaging" + +import Theme from "~components/theme" +import { getFullUrl } from "~options/components/RemoteSettings" +import type { ServerSelectionResponse } from "~background/messages/api/select-server" + +export interface ServerSelectorProps { + servers: Server[] + downloadInfo: { + url: string + filename: string + } + defaultServer?: string + onSelection: (response: ServerSelectionResponse) => void +} + +function ServerSelector({ + servers, + downloadInfo, + defaultServer, + onSelection +}: ServerSelectorProps) { + const [selectedServer, setSelectedServer] = useState( + defaultServer || (servers.length > 0 ? getFullUrl(servers[0]) : "") + ) + + const handleProceed = () => { + onSelection({ + selectedServer, + cancelled: false + }) + } + + const handleCancel = () => { + onSelection({ + selectedServer: null, + cancelled: true + }) + } + + return ( + + + + + {chrome.i18n.getMessage("select_server")} + + + {chrome.i18n.getMessage("select_server_desc")} + + + + + + {downloadInfo.filename || downloadInfo.url} + + + + {servers.map((server, index) => { + const fullUrl = getFullUrl(server) + return ( + + setSelectedServer(fullUrl)}> + + + + + + + ) + })} + + + + + + + + + ) +} + +interface ServerSelectorState { + show: boolean + servers: Server[] + downloadInfo: { + url: string + filename: string + } + defaultServer?: string +} + +function PlasmoOverlay() { + const [state, setState] = useState({ + show: false, + servers: [], + downloadInfo: { url: "", filename: "" } + }) + + useEffect(() => { + const messageListener = ( + message: any, + sender: chrome.runtime.MessageSender, + sendResponse: (response: any) => void + ) => { + if (message.name === "show-server-selector") { + setState({ + show: true, + servers: message.body.servers, + downloadInfo: message.body.downloadInfo, + defaultServer: message.body.defaultServer + }) + sendResponse({ success: true }) + } + } + + chrome.runtime.onMessage.addListener(messageListener) + return () => chrome.runtime.onMessage.removeListener(messageListener) + }, []) + + const handleSelection = async (response: ServerSelectionResponse) => { + setState((prev) => ({ ...prev, show: false })) + + // Send selection back to background script + await sendToBackground({ + name: "api/select-server", + body: response + }) + } + + if (!state.show) { + return <> + } + + return ( + + ) +} + +export default PlasmoOverlay \ No newline at end of file diff --git a/locales/en/messages.json b/locales/en/messages.json index 34bd1ed..7f800c1 100644 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -122,6 +122,12 @@ "download_notification_desc": { "message": "Show browser notification when sending download tasks" }, + "require_manual_server_selection": { + "message": "Require Manual Server Selection" + }, + "require_manual_server_selection_desc": { + "message": "Always show server selection dialog before starting downloads when multiple servers are configured" + }, "add": { "message": "Add" }, @@ -157,5 +163,17 @@ }, "ctrl_disable_capture_desc": { "message": "Hold %key% key to temporarily disable download capture" + }, + "select_server": { + "message": "Select Server" + }, + "select_server_desc": { + "message": "Choose which server to use for this download" + }, + "use_default_server": { + "message": "Use Default" + }, + "proceed": { + "message": "Proceed" } } diff --git a/locales/zh/messages.json b/locales/zh/messages.json index bd8f8c3..451ffd3 100644 --- a/locales/zh/messages.json +++ b/locales/zh/messages.json @@ -119,6 +119,12 @@ "download_notification_desc": { "message": "在推送下载任务时显示浏览器通知" }, + "require_manual_server_selection": { + "message": "需要手动选择服务器" + }, + "require_manual_server_selection_desc": { + "message": "当配置了多个服务器时,在开始下载前总是显示服务器选择对话框" + }, "add": { "message": "添加" }, @@ -157,5 +163,17 @@ }, "ctrl_disable_capture_desc": { "message": "按住 %key% 键时临时禁用下载捕获" + }, + "select_server": { + "message": "选择服务器" + }, + "select_server_desc": { + "message": "选择用于此次下载的服务器" + }, + "use_default_server": { + "message": "使用默认" + }, + "proceed": { + "message": "继续" } } diff --git a/options/components/RemoteSettings.tsx b/options/components/RemoteSettings.tsx index 6b2e518..8ddfb0c 100644 --- a/options/components/RemoteSettings.tsx +++ b/options/components/RemoteSettings.tsx @@ -242,6 +242,24 @@ export default function RemoteSettings() { /> + + {renderLabel( + chrome.i18n.getMessage("require_manual_server_selection"), + chrome.i18n.getMessage("require_manual_server_selection_desc") + )} + { + setSettings({ + ...settings, + remote: { ...settings.remote, requireManualSelection: e.target.checked } + }) + showTip() + }} + disabled={!settings.remote.enabled || settings.remote.servers.length <= 1} + /> + + {chrome.i18n.getMessage("download_servers")} diff --git a/options/types.ts b/options/types.ts index 5675f59..7ff1861 100644 --- a/options/types.ts +++ b/options/types.ts @@ -20,6 +20,7 @@ export interface Settings { selectedServer: string servers: Server[] notification: boolean + requireManualSelection: boolean } } @@ -44,6 +45,7 @@ export const defaultSettings: Settings = { enabled: false, selectedServer: "", servers: [], - notification: true + notification: true, + requireManualSelection: false } }