Skip to content

Commit

Permalink
Add option to register magnet link hander in web version
Browse files Browse the repository at this point in the history
Issue #203
  • Loading branch information
qu1ck committed Jul 1, 2024
1 parent 3a64023 commit 872bb8c
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 8 deletions.
67 changes: 60 additions & 7 deletions src/components/modals/daemon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
*/

import type { NumberInputProps } from "@mantine/core";
import { Box, Button, Checkbox, Grid, Group, Loader, LoadingOverlay, NativeSelect, NumberInput, Tabs, Text, TextInput, Tooltip } from "@mantine/core";
import { Box, Button, Checkbox, Flex, Grid, Group, Loader, LoadingOverlay, NativeSelect, NumberInput, Tabs, Text, TextInput, Tooltip } from "@mantine/core";
import type { ServerConfig } from "config";
import { ConfigContext, ServerConfigContext } from "config";
import React, { useCallback, useContext, useEffect, useState } from "react";
import React, { useCallback, useContext, useEffect, useMemo, useState } from "react";
import type { ModalState } from "./common";
import { SaveCancelModal } from "./common";
import { useMutateSession, useSessionFull, useTestPort, useUpdateBlocklist } from "queries";
Expand Down Expand Up @@ -498,6 +498,51 @@ function QueuePanel({ form, session }: { form: UseFormReturnType<FormValues>, se
);
}

function MagnetHandlerPanel() {
const handlerUrl = useMemo(() => {
const handlerUrl = new URL(window.location.href);
handlerUrl.search = "add=%s";
return handlerUrl;
}, []);

const registerHandler = useCallback(() => {
navigator.registerProtocolHandler("magnet", handlerUrl.toString());
}, [handlerUrl]);

const unregisterHandler = useCallback(() => {
(navigator as any).unregisterProtocolHandler?.("magnet", handlerUrl.toString());
}, [handlerUrl]);

return (
<Grid align="center">
{window.location.protocol === "https:"

This comment has been minimized.

Copy link
@normanr

normanr Oct 10, 2024

window.isSecureContext is the preferred way to establish if a context is secure.

https: is not the only secure scope: "Locally-delivered resources such as those with http://127.0.0.1 URLs, http://localhost and http://*.localhost URLs (e.g. http://dev.whatever.localhost/), and file:// URLs are also considered to have been delivered securely." (from https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts)

This comment has been minimized.

Copy link
@qu1ck

qu1ck Oct 10, 2024

Author Member

That method throws if the url is not https
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler#exceptions

So even though localhost is considered secure it does not work unless it's https.

This comment has been minimized.

Copy link
@normanr

normanr Oct 10, 2024

☹️ thanks for checking.

? <>
<Grid.Col span={6}>
<Text>Register magnet protocol handler</Text>
</Grid.Col>
<Grid.Col span={6}>
<Flex justify="space-around">
<Button onClick={registerHandler}>Register</Button>
{typeof (navigator as any).unregisterProtocolHandler === "function" &&
<Button onClick={unregisterHandler}>Unregister</Button>}
</Flex>
</Grid.Col>
</>
: <Grid.Col>
<Text mb="md">
Registering magnet protocol handler is currently not available.
Browsers support this feature only in secure contexts, i.e. https websites.
</Text>
<Text>
Transmission does not natively support serving the web interface with https but you can
setup a reverse proxy with ssl termination in front of it and use a self signed or letsencrypt
provided free certificate to secure your transmission web endpoint.
</Text>
</Grid.Col>}
</Grid>
);
}

export function DaemonSettingsModal(props: ModalState) {
const { data: session, fetchStatus } = useSessionFull(props.opened);
const mutation = useMutateSession();
Expand Down Expand Up @@ -552,14 +597,17 @@ export function DaemonSettingsModal(props: ModalState) {
>
<Box pos="relative">
<LoadingOverlay visible={fetchStatus === "fetching"} overlayBlur={2} />
<Tabs defaultValue="polling" mih="25rem">
<Tabs defaultValue="polling" mih="28rem">
<Tabs.List>
<Tabs.Tab value="polling" p="lg">Polling</Tabs.Tab>
<Tabs.Tab value="download" p="lg">Download</Tabs.Tab>
<Tabs.Tab value="network" p="lg">Network</Tabs.Tab>
<Tabs.Tab value="bandwidth" p="lg">Bandwidth</Tabs.Tab>
<Tabs.Tab value="queue" p="lg">Queue</Tabs.Tab>
{!TAURI && <Tabs.Tab value="interface" p="lg">Interface</Tabs.Tab>}
{!TAURI && <>
<Tabs.Tab value="interface" p="lg">Interface</Tabs.Tab>
<Tabs.Tab value="magnethandler" p="lg">Magnet links</Tabs.Tab>
</>}
</Tabs.List>
{form.values.session !== undefined
? <>
Expand All @@ -583,9 +631,14 @@ export function DaemonSettingsModal(props: ModalState) {
<QueuePanel form={form} session={form.values.session} />
</Tabs.Panel>

{!TAURI && <Tabs.Panel value="interface" pt="md">
<InterfaceSettigsPanel form={form} />
</Tabs.Panel>}
{!TAURI && <>
<Tabs.Panel value="interface" pt="md">
<InterfaceSettigsPanel form={form} />
</Tabs.Panel>
<Tabs.Panel value="magnethandler" pt="md">
<MagnetHandlerPanel />
</Tabs.Panel>
</>}
</>
: <></>}
</Tabs>
Expand Down
9 changes: 8 additions & 1 deletion src/components/modals/servermodals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ function usePausingModalState(runUpdates: (run: boolean) => void): [boolean, ()
return [opened, pauseOpen, closeResume];
}

const urlAddParam = new URLSearchParams(window.location.search).get("add");
if (urlAddParam != null && urlAddParam !== "") {
const newUrl = new URL(window.location.href);
newUrl.search = "";
window.history.pushState("", "", newUrl.toString());
}

const ServerModals = React.forwardRef<ModalCallbacks, ServerModalsProps>(function ServerModals(props, ref) {
const [showAddMagnetModal, { open: openAddMagnetModal, close: closeAddMagnetModal }] = useDisclosure(false);
const [showAddTorrentModal, { open: openAddTorrentModal, close: closeAddTorrentModal }] = useDisclosure(false);
Expand All @@ -86,7 +93,7 @@ const ServerModals = React.forwardRef<ModalCallbacks, ServerModalsProps>(functio
const [magnetLink, setMagnetLink] = useState<string>();
const [torrent, setTorrent] = useState<string | File>();

const [addQueue, setAddQueue] = useState<Array<string | File>>([]);
const [addQueue, setAddQueue] = useState<Array<string | File>>(urlAddParam != null ? [urlAddParam] : []);

const enqueue = useCallback((paths: string[] | File[]) => {
setAddQueue([...addQueue, ...paths]);
Expand Down

0 comments on commit 872bb8c

Please sign in to comment.