diff --git a/frontend/src/App.js b/frontend/src/App.js index a16c72d1..def311ff 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -22,6 +22,7 @@ import useAuthStore from './store/auth'; import Alert from './components/Alert'; import FloatingVideo from './components/FloatingVideo'; import SuperuserForm from './components/forms/SuperuserForm'; +import { WebsocketProvider } from './WebSocket'; const drawerWidth = 240; const miniDrawerWidth = 60; @@ -79,60 +80,65 @@ const App = () => { return ( - - + + + - - - {isAuthenticated ? ( - <> - } /> - } /> - } /> - } /> - } /> - } /> - - ) : ( - } /> - )} - - } - /> - + + + {isAuthenticated ? ( + <> + } /> + } /> + } /> + } + /> + } /> + } /> + + ) : ( + } /> + )} + + } + /> + + - - - - + + + + ); }; diff --git a/frontend/src/WebSocket.js b/frontend/src/WebSocket.js new file mode 100644 index 00000000..a1fa1aaa --- /dev/null +++ b/frontend/src/WebSocket.js @@ -0,0 +1,78 @@ +import React, { + useState, + useEffect, + useRef, + createContext, + useContext, +} from 'react'; +import useStreamsStore from './store/streams'; +import useAlertStore from './store/alerts'; + +export const WebsocketContext = createContext(false, null, () => {}); + +export const WebsocketProvider = ({ children }) => { + const [isReady, setIsReady] = useState(false); + const [val, setVal] = useState(null); + + const { showAlert } = useAlertStore(); + + const ws = useRef(null); + + useEffect(() => { + let wsUrl = `ws://${window.location.host}/ws/`; + if (process.env.REACT_APP_ENV_MODE == 'dev') { + wsUrl = `ws://${window.location.hostname}:8001/ws/`; + } + + const socket = new WebSocket(wsUrl); + + socket.onopen = () => { + console.log('websocket connected'); + setIsReady(true); + }; + + // Reconnection logic + socket.onclose = () => { + setIsReady(false); + setTimeout(() => { + const reconnectWs = new WebSocket(wsUrl); + reconnectWs.onopen = () => setIsReady(true); + }, 3000); // Attempt to reconnect every 3 seconds + }; + + socket.onmessage = async (event) => { + event = JSON.parse(event.data); + switch (event.type) { + case 'm3u_refresh': + if (event.message?.success) { + useStreamsStore.getState().fetchStreams(); + showAlert(event.message.message, 'success'); + } + break; + + default: + console.error(`Unknown websocket event type: ${event.type}`); + break; + } + }; + + ws.current = socket; + + return () => { + socket.close(); + }; + }, []); + + const ret = [isReady, val, ws.current?.send.bind(ws.current)]; + + return ( + + {children} + + ); +}; + +export const useWebSocket = () => { + const socket = useContext(WebsocketContext); + return socket; +}; diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index abe9db7a..8b99d601 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -100,7 +100,7 @@ const Sidebar = ({ open, miniDrawerWidth, drawerWidth, toggleDrawer }) => { {isAuthenticated && ( - + diff --git a/frontend/src/components/forms/LoginForm.js b/frontend/src/components/forms/LoginForm.js index 2272d6f4..f7b4445e 100644 --- a/frontend/src/components/forms/LoginForm.js +++ b/frontend/src/components/forms/LoginForm.js @@ -66,7 +66,7 @@ const LoginForm = () => { justifyContent="center" direction="column" > - + { size="small" /> - + { size="small" /> - + diff --git a/frontend/src/components/tables/StreamsTable.js b/frontend/src/components/tables/StreamsTable.js index ca8e0c60..6f4fcd4d 100644 --- a/frontend/src/components/tables/StreamsTable.js +++ b/frontend/src/components/tables/StreamsTable.js @@ -141,7 +141,7 @@ const StreamsTable = () => { size="small" color="warning" onClick={() => editStream(row.original)} - disabled={row.original.m3u_account} + disabled={row.original.m3u_account ? true : false} sx={{ p: 0 }} > diff --git a/frontend/src/store/auth.js b/frontend/src/store/auth.js index 0b9f2f8a..8904e489 100644 --- a/frontend/src/store/auth.js +++ b/frontend/src/store/auth.js @@ -29,7 +29,6 @@ const useAuthStore = create((set, get) => ({ setIsAuthenticated: (isAuthenticated) => set({ isAuthenticated }), initData: async () => { - console.log('fetching data'); await Promise.all([ useChannelsStore.getState().fetchChannels(), useChannelsStore.getState().fetchChannelGroups(), @@ -43,7 +42,6 @@ const useAuthStore = create((set, get) => ({ }, getToken: async () => { - const expiration = localStorage.getItem('tokenExpiration'); const tokenExpiration = localStorage.getItem('tokenExpiration'); let accessToken = null; if (isTokenExpired(tokenExpiration)) { diff --git a/frontend/src/store/settings.js b/frontend/src/store/settings.js index b35dbb7c..5dffbed6 100644 --- a/frontend/src/store/settings.js +++ b/frontend/src/store/settings.js @@ -12,7 +12,6 @@ const useSettingsStore = create((set) => ({ try { const settings = await api.getSettings(); const env = await api.getEnvironmentSettings(); - console.log(env); set({ settings: settings.reduce((acc, setting) => { acc[setting.key] = setting; @@ -22,7 +21,6 @@ const useSettingsStore = create((set) => ({ environment: env, }); } catch (error) { - console.error('Failed to fetch settings:', error); set({ error: 'Failed to load settings.', isLoading: false }); } }, diff --git a/frontend/src/store/streams.js b/frontend/src/store/streams.js index 215a17c8..a594b8a7 100644 --- a/frontend/src/store/streams.js +++ b/frontend/src/store/streams.js @@ -1,5 +1,5 @@ -import { create } from "zustand"; -import api from "../api"; +import { create } from 'zustand'; +import api from '../api'; const useStreamsStore = create((set) => ({ streams: [], @@ -12,8 +12,8 @@ const useStreamsStore = create((set) => ({ const streams = await api.getStreams(); set({ streams: streams, isLoading: false }); } catch (error) { - console.error("Failed to fetch streams:", error); - set({ error: "Failed to load streams.", isLoading: false }); + console.error('Failed to fetch streams:', error); + set({ error: 'Failed to load streams.', isLoading: false }); } },