code refactoring, switch to rxjs websocket wrapper

This commit is contained in:
2023-04-19 14:14:15 +02:00
parent 621164589f
commit fa7cd1a691
12 changed files with 82 additions and 66 deletions

View File

@@ -14,7 +14,7 @@ on:
pull_request: pull_request:
branches: [ master ] branches: [ master ]
schedule: schedule:
- cron : '0 1 * * *' - cron : '0 1 * * 0'
env: env:
# Use docker.io for Docker Hub if empty # Use docker.io for Docker Hub if empty

View File

@@ -32,7 +32,7 @@ import { AppBar } from './components/AppBar'
import { Drawer } from './components/Drawer' import { Drawer } from './components/Drawer'
import { toggleListView } from './features/settings/settingsSlice' import { toggleListView } from './features/settings/settingsSlice'
import { RootState, store } from './stores/store' import { RootState, store } from './stores/store'
import { formatGiB, getWebSocketEndpoint } from './utils' import { formatGiB } from './utils'
function AppContent() { function AppContent() {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
@@ -41,8 +41,6 @@ function AppContent() {
const status = useSelector((state: RootState) => state.status) const status = useSelector((state: RootState) => state.status)
const dispatch = useDispatch() const dispatch = useDispatch()
const socket = useMemo(() => new WebSocket(getWebSocketEndpoint()), [])
const mode = settings.theme const mode = settings.theme
const theme = useMemo(() => const theme = useMemo(() =>
createTheme({ createTheme({
@@ -170,10 +168,10 @@ function AppContent() {
> >
<Toolbar /> <Toolbar />
<Routes> <Routes>
<Route path="/" element={<Home socket={socket} />} /> <Route path="/" element={<Home />} />
<Route path="/settings" element={ <Route path="/settings" element={
<Suspense fallback={<CircularProgress />}> <Suspense fallback={<CircularProgress />}>
<Settings socket={socket} /> <Settings />
</Suspense> </Suspense>
} /> } />
</Routes> </Routes>

View File

@@ -25,17 +25,13 @@ import { DownloadsListView } from './components/DownloadsListView'
import FormatsGrid from './components/FormatsGrid' import FormatsGrid from './components/FormatsGrid'
import { CliArguments } from './features/core/argsParser' import { CliArguments } from './features/core/argsParser'
import I18nBuilder from './features/core/intl' 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 { connected, setFreeSpace } from './features/status/statusSlice'
import { RootState } from './stores/store' import { RootState } from './stores/store'
import type { DLMetadata, RPCResult } from './types' import type { DLMetadata, RPCResponse, RPCResult } from './types'
import { isValidURL, toFormatArgs } from './utils' import { isValidURL, toFormatArgs } from './utils'
type Props = { export default function Home() {
socket: WebSocket
}
export default function Home({ socket }: Props) {
// redux state // redux state
const settings = useSelector((state: RootState) => state.settings) const settings = useSelector((state: RootState) => state.settings)
const status = useSelector((state: RootState) => state.status) const status = useSelector((state: RootState) => state.status)
@@ -60,9 +56,11 @@ export default function Home({ socket }: Props) {
const [showBackdrop, setShowBackdrop] = useState(true) const [showBackdrop, setShowBackdrop] = useState(true)
const [showToast, setShowToast] = useState(true) const [showToast, setShowToast] = useState(true)
const [socketHasError, setSocketHasError] = useState(false)
// memos // memos
const i18n = useMemo(() => new I18nBuilder(settings.language), [settings.language]) 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]) const cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [settings.cliArgs])
// refs // refs
@@ -73,12 +71,19 @@ export default function Home({ socket }: Props) {
/* WebSocket connect event handler*/ /* WebSocket connect event handler*/
useEffect(() => { useEffect(() => {
socket.onopen = () => { const sub = socket$.subscribe({
dispatch(connected()) next: () => {
setCustomArgs(localStorage.getItem('last-input-args') ?? '') dispatch(connected())
setFilenameOverride(localStorage.getItem('last-filename-override') ?? '') setCustomArgs(localStorage.getItem('last-input-args') ?? '')
} setFilenameOverride(localStorage.getItem('last-filename-override') ?? '')
}, []) },
complete: () => {
setSocketHasError(true)
setShowBackdrop(false)
},
})
return () => sub.unsubscribe()
}, [socket$])
useEffect(() => { useEffect(() => {
if (status.connected) { if (status.connected) {
@@ -93,21 +98,23 @@ export default function Home({ socket }: Props) {
}, []) }, [])
useEffect(() => { useEffect(() => {
socket.onmessage = (event) => { if (status.connected) {
const res = client.decode(event.data) const sub = socket$.subscribe((event: RPCResponse<RPCResult[]>) => {
switch (typeof res.result) { switch (typeof event.result) {
case 'object': case 'object':
setActiveDownloads( setActiveDownloads(
(res.result ?? []) (event.result ?? [])
.filter((r: RPCResult) => !!r.info.url) .filter((r) => !!r.info.url)
.sort((a: RPCResult, b: RPCResult) => a.info.title.localeCompare(b.info.title)) .sort((a, b) => a.info.title.localeCompare(b.info.title))
) )
break break
default: default:
break break
} }
})
return () => sub.unsubscribe()
} }
}, []) }, [socket$, status.connected])
useEffect(() => { useEffect(() => {
if (activeDownloads && activeDownloads.length >= 0) { 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-------------------- */ /* -------------------- callbacks-------------------- */
/** /**

View File

@@ -40,7 +40,7 @@ import { updated } from './features/status/statusSlice'
import { RootState } from './stores/store' import { RootState } from './stores/store'
import { validateDomain, validateIP } from './utils' import { validateDomain, validateIP } from './utils'
export default function Settings({ socket }: { socket: WebSocket }) { export default function Settings() {
const dispatch = useDispatch() const dispatch = useDispatch()
const status = useSelector((state: RootState) => state.status) 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 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 cliArgs = useMemo(() => new CliArguments().fromString(settings.cliArgs), [])
const serverAddr$ = useMemo(() => new Subject<string>(), []) const serverAddr$ = useMemo(() => new Subject<string>(), [])
@@ -79,6 +79,7 @@ export default function Settings({ socket }: { socket: WebSocket }) {
useEffect(() => { useEffect(() => {
const sub = serverPort$ const sub = serverPort$
.pipe( .pipe(
debounceTime(500),
map(val => Number(val)), map(val => Number(val)),
takeWhile(val => isFinite(val) && val <= 65535), takeWhile(val => isFinite(val) && val <= 65535),
) )

View File

@@ -1,5 +1,12 @@
import { Card, CardActionArea, CardContent, CardMedia, Skeleton, Typography } from "@mui/material"; import {
import { ellipsis } from "../utils"; Card,
CardActionArea,
CardContent,
CardMedia,
Skeleton,
Typography
} from '@mui/material'
import { ellipsis } from '../utils'
type Props = { type Props = {
title: string, title: string,

View File

@@ -1,5 +1,6 @@
import { Grid } from "@mui/material" import { Grid } from "@mui/material"
import { Fragment } from "react" import { Fragment } from "react"
import type { RPCResult } from "../types" import type { RPCResult } from "../types"
import { StackableResult } from "./StackableResult" import { StackableResult } from "./StackableResult"

View File

@@ -1,6 +1,18 @@
import { Button, CircularProgress, Grid, LinearProgress, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material" import {
import { RPCResult } from "../types" Button,
Grid,
LinearProgress,
Paper,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography
} from "@mui/material"
import { ellipsis, formatSpeedMiB, roundMiB } from "../utils" import { ellipsis, formatSpeedMiB, roundMiB } from "../utils"
import type { RPCResult } from "../types"
type Props = { type Props = {
downloads: RPCResult[] downloads: RPCResult[]

View File

@@ -1,5 +1,5 @@
import { Button, ButtonGroup, Grid, Paper, Typography } from "@mui/material" import { Button, ButtonGroup, Grid, Paper, Typography } from "@mui/material"
import type { DLMetadata } from "../types" import type { DLMetadata } from '../types'
type Props = { type Props = {
downloadFormats: DLMetadata downloadFormats: DLMetadata

View File

@@ -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<any>(getWebSocketEndpoint())
export class RPCClient { export class RPCClient {
private socket: WebSocket
private seq: number private seq: number
constructor(socket: WebSocket) { constructor() {
this.socket = socket
this.seq = 0 this.seq = 0
} }
@@ -16,7 +17,10 @@ export class RPCClient {
} }
private send(req: RPCRequest) { private send(req: RPCRequest) {
this.socket.send(JSON.stringify(req)) socket$.next({
...req,
id: this.incrementSeq(),
})
} }
private async sendHTTP<T>(req: RPCRequest) { private async sendHTTP<T>(req: RPCRequest) {
@@ -35,7 +39,6 @@ export class RPCClient {
public download(url: string, args: string, pathOverride = '', renameTo = '') { public download(url: string, args: string, pathOverride = '', renameTo = '') {
if (url) { if (url) {
this.send({ this.send({
id: this.incrementSeq(),
method: 'Service.Exec', method: 'Service.Exec',
params: [{ params: [{
URL: url.split("?list").at(0)!, URL: url.split("?list").at(0)!,
@@ -50,7 +53,6 @@ export class RPCClient {
public formats(url: string) { public formats(url: string) {
if (url) { if (url) {
return this.sendHTTP<DLMetadata>({ return this.sendHTTP<DLMetadata>({
id: this.incrementSeq(),
method: 'Service.Formats', method: 'Service.Formats',
params: [{ params: [{
URL: url.split("?list").at(0)!, URL: url.split("?list").at(0)!,
@@ -61,7 +63,6 @@ export class RPCClient {
public running() { public running() {
this.send({ this.send({
id: this.incrementSeq(),
method: 'Service.Running', method: 'Service.Running',
params: [], params: [],
}) })
@@ -101,8 +102,4 @@ export class RPCClient {
params: [] params: []
}) })
} }
public decode(data: any): RPCResponse<any> {
return JSON.parse(data)
}
} }

View File

@@ -11,6 +11,7 @@ import "time"
// //
// Debounce emits a string from the source channel only after a particular // Debounce emits a string from the source channel only after a particular
// time span determined a Go Interval // time span determined a Go Interval
//
// --A--B--CD--EFG-------|> // --A--B--CD--EFG-------|>
// //
// -t-> |> // -t-> |>

View File

@@ -34,6 +34,10 @@ func RunBlocking(port int, frontend fs.FS) {
// RPC handlers // RPC handlers
// websocket // websocket
app.Get("/ws-rpc", websocket.New(func(c *websocket.Conn) { app.Get("/ws-rpc", websocket.New(func(c *websocket.Conn) {
c.WriteMessage(websocket.TextMessage, []byte(`{
"status": "connected"
}`))
for { for {
mtype, reader, err := c.NextReader() mtype, reader, err := c.NextReader()
if err != nil { if err != nil {