diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 7d0151c..a484406 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -14,7 +14,7 @@ on:
pull_request:
branches: [ master ]
schedule:
- - cron : '0 1 * * *'
+ - cron : '0 1 * * 0'
env:
# Use docker.io for Docker Hub if empty
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 16e907b..71e322a 100755
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -32,7 +32,7 @@ import { AppBar } from './components/AppBar'
import { Drawer } from './components/Drawer'
import { toggleListView } from './features/settings/settingsSlice'
import { RootState, store } from './stores/store'
-import { formatGiB, getWebSocketEndpoint } from './utils'
+import { formatGiB } from './utils'
function AppContent() {
const [open, setOpen] = useState(false)
@@ -41,8 +41,6 @@ function AppContent() {
const status = useSelector((state: RootState) => state.status)
const dispatch = useDispatch()
- const socket = useMemo(() => new WebSocket(getWebSocketEndpoint()), [])
-
const mode = settings.theme
const theme = useMemo(() =>
createTheme({
@@ -170,10 +168,10 @@ function AppContent() {
>
- } />
+ } />
}>
-
+
} />
diff --git a/frontend/src/Home.tsx b/frontend/src/Home.tsx
index bbe1449..931d2b3 100644
--- a/frontend/src/Home.tsx
+++ b/frontend/src/Home.tsx
@@ -25,17 +25,13 @@ import { DownloadsListView } from './components/DownloadsListView'
import FormatsGrid from './components/FormatsGrid'
import { CliArguments } from './features/core/argsParser'
import I18nBuilder from './features/core/intl'
-import { RPCClient } from './features/core/rpcClient'
+import { RPCClient, socket$ } from './features/core/rpcClient'
import { connected, setFreeSpace } from './features/status/statusSlice'
import { RootState } from './stores/store'
-import type { DLMetadata, RPCResult } from './types'
+import type { DLMetadata, RPCResponse, RPCResult } from './types'
import { isValidURL, toFormatArgs } from './utils'
-type Props = {
- socket: WebSocket
-}
-
-export default function Home({ socket }: Props) {
+export default function Home() {
// redux state
const settings = useSelector((state: RootState) => state.settings)
const status = useSelector((state: RootState) => state.status)
@@ -60,9 +56,11 @@ export default function Home({ socket }: Props) {
const [showBackdrop, setShowBackdrop] = useState(true)
const [showToast, setShowToast] = useState(true)
+ const [socketHasError, setSocketHasError] = useState(false)
+
// memos
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
- const client = useMemo(() => new RPCClient(socket), [settings.serverAddr, settings.serverPort])
+ const client = useMemo(() => new RPCClient(), [settings.serverAddr, settings.serverPort])
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
// refs
@@ -73,12 +71,19 @@ export default function Home({ socket }: Props) {
/* WebSocket connect event handler*/
useEffect(() => {
- socket.onopen = () => {
- dispatch(connected())
- setCustomArgs(localStorage.getItem('last-input-args') ?? '')
- setFilenameOverride(localStorage.getItem('last-filename-override') ?? '')
- }
- }, [])
+ const sub = socket$.subscribe({
+ next: () => {
+ dispatch(connected())
+ setCustomArgs(localStorage.getItem('last-input-args') ?? '')
+ setFilenameOverride(localStorage.getItem('last-filename-override') ?? '')
+ },
+ complete: () => {
+ setSocketHasError(true)
+ setShowBackdrop(false)
+ },
+ })
+ return () => sub.unsubscribe()
+ }, [socket$])
useEffect(() => {
if (status.connected) {
@@ -93,21 +98,23 @@ export default function Home({ socket }: Props) {
}, [])
useEffect(() => {
- socket.onmessage = (event) => {
- const res = client.decode(event.data)
- switch (typeof res.result) {
- case 'object':
- setActiveDownloads(
- (res.result ?? [])
- .filter((r: RPCResult) => !!r.info.url)
- .sort((a: RPCResult, b: RPCResult) => a.info.title.localeCompare(b.info.title))
- )
- break
- default:
- break
- }
+ if (status.connected) {
+ const sub = socket$.subscribe((event: RPCResponse) => {
+ switch (typeof event.result) {
+ case 'object':
+ setActiveDownloads(
+ (event.result ?? [])
+ .filter((r) => !!r.info.url)
+ .sort((a, b) => a.info.title.localeCompare(b.info.title))
+ )
+ break
+ default:
+ break
+ }
+ })
+ return () => sub.unsubscribe()
}
- }, [])
+ }, [socket$, status.connected])
useEffect(() => {
if (activeDownloads && activeDownloads.length >= 0) {
@@ -122,18 +129,6 @@ export default function Home({ socket }: Props) {
})
}, [])
- const [socketHasError, setSocketHasError] = useState(false)
-
- useEffect(() => {
- socket.onerror = () => {
- setSocketHasError(true)
- setShowBackdrop(false)
- }
- return () => {
- socket.onerror = null
- }
- }, [socket])
-
/* -------------------- callbacks-------------------- */
/**
diff --git a/frontend/src/Settings.tsx b/frontend/src/Settings.tsx
index c32fdf1..6dd71a9 100644
--- a/frontend/src/Settings.tsx
+++ b/frontend/src/Settings.tsx
@@ -40,7 +40,7 @@ import { updated } from './features/status/statusSlice'
import { RootState } from './stores/store'
import { validateDomain, validateIP } from './utils'
-export default function Settings({ socket }: { socket: WebSocket }) {
+export default function Settings() {
const dispatch = useDispatch()
const status = useSelector((state: RootState) => state.status)
@@ -50,7 +50,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language])
- const client = useMemo(() => new RPCClient(socket), [])
+ const client = useMemo(() => new RPCClient(), [])
const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
const serverAddr$ = useMemo(() => new Subject(), [])
@@ -79,6 +79,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
useEffect(() => {
const sub = serverPort$
.pipe(
+ debounceTime(500),
map(val => Number(val)),
takeWhile(val => isFinite(val) && val <= 65535),
)
diff --git a/frontend/src/components/ArchiveResult.tsx b/frontend/src/components/ArchiveResult.tsx
index 2d575de..a71a65a 100644
--- a/frontend/src/components/ArchiveResult.tsx
+++ b/frontend/src/components/ArchiveResult.tsx
@@ -1,5 +1,12 @@
-import { Card, CardActionArea, CardContent, CardMedia, Skeleton, Typography } from "@mui/material";
-import { ellipsis } from "../utils";
+import {
+ Card,
+ CardActionArea,
+ CardContent,
+ CardMedia,
+ Skeleton,
+ Typography
+} from '@mui/material'
+import { ellipsis } from '../utils'
type Props = {
title: string,
diff --git a/frontend/src/components/DownloadsCardView.tsx b/frontend/src/components/DownloadsCardView.tsx
index 5239b9d..dea7d17 100644
--- a/frontend/src/components/DownloadsCardView.tsx
+++ b/frontend/src/components/DownloadsCardView.tsx
@@ -1,5 +1,6 @@
import { Grid } from "@mui/material"
import { Fragment } from "react"
+
import type { RPCResult } from "../types"
import { StackableResult } from "./StackableResult"
diff --git a/frontend/src/components/DownloadsListView.tsx b/frontend/src/components/DownloadsListView.tsx
index 79a18ee..95235ae 100644
--- a/frontend/src/components/DownloadsListView.tsx
+++ b/frontend/src/components/DownloadsListView.tsx
@@ -1,6 +1,18 @@
-import { Button, CircularProgress, Grid, LinearProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"
-import { RPCResult } from "../types"
+import {
+ Button,
+ Grid,
+ LinearProgress,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography
+} from "@mui/material"
import { ellipsis, formatSpeedMiB, roundMiB } from "../utils"
+import type { RPCResult } from "../types"
type Props = {
downloads: RPCResult[]
diff --git a/frontend/src/components/FormatsGrid.tsx b/frontend/src/components/FormatsGrid.tsx
index 0b98173..18f18d0 100644
--- a/frontend/src/components/FormatsGrid.tsx
+++ b/frontend/src/components/FormatsGrid.tsx
@@ -1,5 +1,5 @@
import { Button, ButtonGroup, Grid, Paper, Typography } from "@mui/material"
-import type { DLMetadata } from "../types"
+import type { DLMetadata } from '../types'
type Props = {
downloadFormats: DLMetadata
diff --git a/frontend/src/features/core/rpcClient.ts b/frontend/src/features/core/rpcClient.ts
index ca0928f..92e0996 100644
--- a/frontend/src/features/core/rpcClient.ts
+++ b/frontend/src/features/core/rpcClient.ts
@@ -1,13 +1,14 @@
-import type { RPCRequest, RPCResponse, DLMetadata } from "../../types"
+import type { DLMetadata, RPCRequest, RPCResponse } from '../../types'
-import { getHttpRPCEndpoint } from '../../utils'
+import { webSocket } from 'rxjs/webSocket'
+import { getHttpRPCEndpoint, getWebSocketEndpoint } from '../../utils'
+
+export const socket$ = webSocket(getWebSocketEndpoint())
export class RPCClient {
- private socket: WebSocket
private seq: number
- constructor(socket: WebSocket) {
- this.socket = socket
+ constructor() {
this.seq = 0
}
@@ -16,7 +17,10 @@ export class RPCClient {
}
private send(req: RPCRequest) {
- this.socket.send(JSON.stringify(req))
+ socket$.next({
+ ...req,
+ id: this.incrementSeq(),
+ })
}
private async sendHTTP(req: RPCRequest) {
@@ -35,7 +39,6 @@ export class RPCClient {
public download(url: string, args: string, pathOverride = '', renameTo = '') {
if (url) {
this.send({
- id: this.incrementSeq(),
method: 'Service.Exec',
params: [{
URL: url.split("?list").at(0)!,
@@ -50,7 +53,6 @@ export class RPCClient {
public formats(url: string) {
if (url) {
return this.sendHTTP({
- id: this.incrementSeq(),
method: 'Service.Formats',
params: [{
URL: url.split("?list").at(0)!,
@@ -61,7 +63,6 @@ export class RPCClient {
public running() {
this.send({
- id: this.incrementSeq(),
method: 'Service.Running',
params: [],
})
@@ -101,8 +102,4 @@ export class RPCClient {
params: []
})
}
-
- public decode(data: any): RPCResponse {
- return JSON.parse(data)
- }
}
\ No newline at end of file
diff --git a/frontend/src/types.d.ts b/frontend/src/types/index.d.ts
similarity index 100%
rename from frontend/src/types.d.ts
rename to frontend/src/types/index.d.ts
diff --git a/server/rx/extensions.go b/server/rx/extensions.go
index 5c5880c..54600d0 100644
--- a/server/rx/extensions.go
+++ b/server/rx/extensions.go
@@ -11,6 +11,7 @@ import "time"
//
// Debounce emits a string from the source channel only after a particular
// time span determined a Go Interval
+//
// --A--B--CD--EFG-------|>
//
// -t-> |>
diff --git a/server/server.go b/server/server.go
index 4a262b3..12b6897 100644
--- a/server/server.go
+++ b/server/server.go
@@ -34,6 +34,10 @@ func RunBlocking(port int, frontend fs.FS) {
// RPC handlers
// websocket
app.Get("/ws-rpc", websocket.New(func(c *websocket.Conn) {
+ c.WriteMessage(websocket.TextMessage, []byte(`{
+ "status": "connected"
+ }`))
+
for {
mtype, reader, err := c.NextReader()
if err != nil {