Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 85 additions & 5 deletions background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand Down
54 changes: 54 additions & 0 deletions background/messages/api/select-server.ts
Original file line number Diff line number Diff line change
@@ -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<string, {
resolve: (response: ServerSelectionResponse) => void
reject: (error: Error) => void
}>()

export function requestServerSelection(
requestId: string,
data: ServerSelectionRequest
): Promise<ServerSelectionResponse> {
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<ServerSelectionResponse, void> = (
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
193 changes: 193 additions & 0 deletions contents/server-selector.tsx
Original file line number Diff line number Diff line change
@@ -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<string>(
defaultServer || (servers.length > 0 ? getFullUrl(servers[0]) : "")
)

const handleProceed = () => {
onSelection({
selectedServer,
cancelled: false
})
}

const handleCancel = () => {
onSelection({
selectedServer: null,
cancelled: true
})
}

return (
<Theme>
<Dialog
open={true}
disableEscapeKeyDown
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
position: "fixed",
top: "20px",
left: "50%",
transform: "translateX(-50%)",
m: 0,
minWidth: "400px"
}
}}>
<DialogTitle>
<Typography variant="h6">
{chrome.i18n.getMessage("select_server")}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
{chrome.i18n.getMessage("select_server_desc")}
</Typography>
</DialogTitle>
<DialogContent sx={{ pb: 1 }}>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
{downloadInfo.filename || downloadInfo.url}
</Typography>
</Box>
<List sx={{ bgcolor: "action.hover", borderRadius: 1 }}>
{servers.map((server, index) => {
const fullUrl = getFullUrl(server)
return (
<ListItem key={fullUrl} disablePadding>
<ListItemButton onClick={() => setSelectedServer(fullUrl)}>
<ListItemIcon>
<Radio
checked={selectedServer === fullUrl}
value={fullUrl}
/>
</ListItemIcon>
<ListItemText
primary={fullUrl}
secondary={
defaultServer === fullUrl
? `(${chrome.i18n.getMessage("use_default_server")})`
: undefined
}
/>
</ListItemButton>
</ListItem>
)
})}
</List>
</DialogContent>
<DialogActions>
<Button onClick={handleCancel} color="inherit">
{chrome.i18n.getMessage("cancel")}
</Button>
<Button
onClick={handleProceed}
variant="contained"
disabled={!selectedServer}>
{chrome.i18n.getMessage("proceed")}
</Button>
</DialogActions>
</Dialog>
</Theme>
)
}

interface ServerSelectorState {
show: boolean
servers: Server[]
downloadInfo: {
url: string
filename: string
}
defaultServer?: string
}

function PlasmoOverlay() {
const [state, setState] = useState<ServerSelectorState>({
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<ServerSelectionResponse, void>({
name: "api/select-server",
body: response
})
}

if (!state.show) {
return <></>
}

return (
<ServerSelector
servers={state.servers}
downloadInfo={state.downloadInfo}
defaultServer={state.defaultServer}
onSelection={handleSelection}
/>
)
}

export default PlasmoOverlay
18 changes: 18 additions & 0 deletions locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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"
}
}
Loading
Loading