diff --git a/src/backend/scrobblers/AbstractScrobbleClient.ts b/src/backend/scrobblers/AbstractScrobbleClient.ts index e6c48318..14104068 100644 --- a/src/backend/scrobblers/AbstractScrobbleClient.ts +++ b/src/backend/scrobblers/AbstractScrobbleClient.ts @@ -677,8 +677,10 @@ ${closestMatch.breakdowns.join('\n')}`); } else { eString = messageWithCauses(error); } - this.deadLetterScrobbles.push({id: nanoid(), retries: 0, error: eString, ...data}); + const deadData = {id: nanoid(), retries: 0, error: eString, ...data}; + this.deadLetterScrobbles.push(deadData); this.deadLetterScrobbles.sort((a, b) => sortByOldestPlayDate(a.play, b.play)); + this.emitEvent('deadLetter', {dead: deadData}); } diff --git a/src/backend/server/api.ts b/src/backend/server/api.ts index 99ac791e..b58905f4 100644 --- a/src/backend/server/api.ts +++ b/src/backend/server/api.ts @@ -148,6 +148,11 @@ export const setupApi = (app: ExpressWithAsync, logger: Logger, initialLogOutput session.push({event: eventName, ...payload}, payload.from); } }); + scrobbleClients.emitter.on('*', (payload: any, eventName: string) => { + if(payload.from !== undefined) { + session.push({event: eventName, ...payload}, payload.from); + } + }); }); setupTautulliRoutes(app, logger, scrobbleSources); diff --git a/src/client/App.tsx b/src/client/App.tsx index b7c4c380..b4b09582 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -5,13 +5,15 @@ import { createBrowserRouter, RouterProvider, useLocation, } from "react-router-dom"; -import { Provider } from 'react-redux' +import {connect, ConnectedProps, Provider} from 'react-redux' import './App.css'; import {store} from './store'; import Dashboard from "./dashboard/dashboard"; import RecentPage from "./recent/RecentPage"; import ScrobbledPage from "./scrobbled/ScrobbledPage"; import DeadPage from "./deadLetter/DeadPage"; +import {clientUpdate, sourceUpdate} from "./status/ducks"; +import {useEventSource, useEventSourceListener} from "@react-nano/use-event-source"; function NoMatch() { let location = useLocation(); @@ -46,6 +48,38 @@ const router = createBrowserRouter([ } ]); +const mapDispatchToProps = (dispatch) => { + return { + updateSource: (payload) => dispatch(sourceUpdate(payload)), + updateClient: (payload) => dispatch(clientUpdate(payload)) + } +} + +const connector = connect(null, mapDispatchToProps); + +type PropsFromRedux = ConnectedProps; + +const Global = (props: PropsFromRedux) => { + const { + updateSource, + updateClient + } = props; + + const [sourceEventSource, eventSourceStatus] = useEventSource("api/events", false); + useEventSourceListener(sourceEventSource, ['source', 'client'], evt => { + const data = JSON.parse(evt.data); + if(data.from === 'source') { + updateSource(data); + } else if(data.from === 'client') { + updateClient(data); + } + }, [updateSource, updateClient]); + + return ; +} + +const ConnectedGlobal = connector(Global); + function App() { return ( @@ -63,6 +97,7 @@ function App() {
+
diff --git a/src/client/deadLetter/deadLetterDucks.ts b/src/client/deadLetter/deadLetterDucks.ts index 275a8a59..af30c3b1 100644 --- a/src/client/deadLetter/deadLetterDucks.ts +++ b/src/client/deadLetter/deadLetterDucks.ts @@ -1,6 +1,7 @@ import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react/index"; import {DeadLetterScrobble, JsonPlayObject} from "../../core/Atomic"; import {createAction, createEntityAdapter, createSlice} from "@reduxjs/toolkit"; +import {ApiEventPayload, clientUpdate} from "../status/ducks"; type DeadResponse = DeadLetterScrobble[]; export const deadApi = createApi({ @@ -119,6 +120,12 @@ export const deadSlice = createSlice({ state = deadAdapter.getInitialState(); } ) + /*.addMatcher( + (action) => clientUpdate.match(action) && action.payload.event === 'deadLetter', + (state, action) => { + state.entities[(action.payload as ApiEventPayload).data.dead.id] = (action.payload as ApiEventPayload).data.dead; + } + )*/ } }); diff --git a/src/client/status/StatusSection.tsx b/src/client/status/StatusSection.tsx index 7f6d4bb5..647c590b 100644 --- a/src/client/status/StatusSection.tsx +++ b/src/client/status/StatusSection.tsx @@ -4,26 +4,13 @@ import StatusCardSkeleton from "../components/statusCard/StatusCardSkeleton"; import SourceStatusCard from "../components/statusCard/SourceStatusCard"; import ClientStatusCard from "../components/statusCard/ClientStatusCard"; import {useGetStatusQuery} from "./statusApi"; -import {clientAdapter, clientUpdate, sourceAdapter, sourceUpdate} from "./ducks"; +import {clientAdapter, sourceAdapter} from "./ducks"; import {RootState} from "../store"; -import {useEventSource, useEventSourceListener} from "@react-nano/use-event-source"; const StatusSection = (props: PropsFromRedux) => { const { - updateSource, - updateClient } = props; const {data, error, isLoading} = useGetStatusQuery(undefined); - const [sourceEventSource, eventSourceStatus] = useEventSource("api/events", false); - useEventSourceListener(sourceEventSource, ['source', 'client'], evt => { - const data = JSON.parse(evt.data); - if(data.from === 'source') { - updateSource(data); - } else if(data.from === 'client') { - updateClient(data); - } - }, [updateSource]); - return (
@@ -48,8 +35,6 @@ const mapStateToProps = (state: RootState) => { const mapDispatchToProps = (dispatch) => { return { - updateSource: (payload) => dispatch(sourceUpdate(payload)), - updateClient: (payload) => dispatch(clientUpdate(payload)) } } diff --git a/src/client/status/ducks.ts b/src/client/status/ducks.ts index 52eee732..64cd93ad 100644 --- a/src/client/status/ducks.ts +++ b/src/client/status/ducks.ts @@ -46,18 +46,24 @@ const sourceSlice = createSlice({ .addMatcher( (action) => sourceUpdate.match(action) && action.payload.event === 'discovered', (state, action) => { - state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; + if(state.entities[action.payload.id] !== undefined) { + state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; + } } ).addMatcher( (action) => sourceUpdate.match(action) && action.payload.event === 'playerUpdate', (state, action) => { - const playerState = action.payload.data as SourcePlayerJson; - state.entities[action.payload.id].players[playerState.platformId] = playerState; + if(state.entities[action.payload.id] !== undefined) { + const playerState = action.payload.data as SourcePlayerJson; + state.entities[action.payload.id].players[playerState.platformId] = playerState; + } }).addMatcher( (action) => sourceUpdate.match(action) && action.payload.event === 'playerDelete', (state, action) => { - const playerState = action.payload.data as {platformId: string}; - delete state.entities[action.payload.id].players[playerState.platformId]; + if(state.entities[action.payload.id] !== undefined) { + const playerState = action.payload.data as {platformId: string}; + delete state.entities[action.payload.id].players[playerState.platformId]; + } } ) } @@ -79,7 +85,17 @@ const clientSlice = createSlice({ .addMatcher( (action) => clientUpdate.match(action) && action.payload.event === 'scrobble', (state, action) => { - state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; + if(state.entities[action.payload.id] !== undefined) { + state.entities[action.payload.id].tracksDiscovered = state.entities[action.payload.id].tracksDiscovered + 1; + } + } + ) + .addMatcher( + (action) => clientUpdate.match(action) && action.payload.event === 'deadLetter', + (state, action) => { + if(state.entities[action.payload.id] !== undefined) { + state.entities[action.payload.id].deadLetterScrobbles = state.entities[action.payload.id].deadLetterScrobbles + 1; + } } ) }