diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 138b54f85..716291764 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -1,6 +1,5 @@ import 'core-js/es/string/to-well-formed.js'; // Polyfill for older browsers import './homebrew.less'; -import React from 'react'; import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router'; import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js'; diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 8df241d7b..d81b4d878 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -39,6 +39,18 @@ const SharePage = (props)=>{ document.addEventListener('keydown', handleControlKeys); fetchThemeBundle(undefined, setThemeBundle, brew.renderer, brew.theme); + // listen for changes in the brew version + const eventSource = new EventSource('/stream'); + eventSource.addEventListener('message', (evt)=>{ + const messageData = JSON.parse(evt.data); + + if(messageData.eventType == 'brewUpdated'){ + if(messageData.shareId == brew.shareId && messageData.version != brew.version) { + console.log(`brew has been updated, viewing ${brew.version}, new version is ${messageData.version}`); + } + } + }); + return ()=>{ document.removeEventListener('keydown', handleControlKeys); }; diff --git a/server/app.js b/server/app.js index eb078a877..714c4beba 100644 --- a/server/app.js +++ b/server/app.js @@ -1,4 +1,4 @@ -/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/ +/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/ // Set working directory to project root import { dirname } from 'path'; import { fileURLToPath } from 'url'; @@ -30,6 +30,8 @@ import contentNegotiation from './middleware/content-negotiation.js'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import forceSSL from './forcessl.mw.js'; + +import Stream from './eventStreamSource.js'; import dbCheck from './middleware/dbCheck.js'; import cors from 'cors'; @@ -124,10 +126,10 @@ export default async function createApp(vite) { }; app.use(pageRoutes({ - defaultMetaTags, + defaultMetaTags, HomebrewModel, sanitizeBrew, - })); + })); //Robots.txt app.get('/robots.txt', (req, res)=>{ @@ -182,6 +184,27 @@ export default async function createApp(vite) { } }); + // Create Event Stream source for pages to listen to + app.get('/stream', (req, res)=>{ + res.writeHead(200, { + 'Content-Type' : 'text/event-stream', + 'Cache-Control' : 'no-cache', + 'Connection' : 'keep-alive', + 'Content-Encoding' : 'none' + }); + + Stream.on('sendUpdate', (event, data)=>{ + console.log('Event:', event, '\nData:', data); + res.write(`data: ${JSON.stringify({ ...data, eventType: event })}\n\n`); + }); + }); + + // After Stream starts, send initStream event + setTimeout(()=>{ + Stream.emit('sendUpdate', 'initStream', { time: new Date }); + }, 1000); + + // Local only if(isLocalEnvironment){ // Login diff --git a/server/eventStreamSource.js b/server/eventStreamSource.js new file mode 100644 index 000000000..66fdb3354 --- /dev/null +++ b/server/eventStreamSource.js @@ -0,0 +1,9 @@ +import { EventEmitter } from 'events'; + +const Stream = new EventEmitter; + +export default { + emit : function(event) {return Stream.emit(event, ...([...arguments].slice(1)));}, // Arguments doesn't work for arrow functions + on : (event, listener)=>{return Stream.on(event, listener);}, + off : (event, listener)=>{return Stream.off(event, listener);} +}; \ No newline at end of file diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 3e3554b66..90543ceaa 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -21,6 +21,8 @@ const router = express.Router(); import { DEFAULT_BREW, DEFAULT_BREW_LOAD } from './brewDefaults.js'; import Themes from '../themes/themes.json' with { type: 'json' }; +import Stream from './eventStreamSource.js'; + const isStaticTheme = (renderer, themeName)=>{ return Themes[renderer]?.[themeName] !== undefined; }; @@ -500,6 +502,8 @@ const api = { saved.textBin = undefined; // Remove textBin from the saved object to save bandwidth + Stream.emit('sendUpdate', 'brewUpdated', { time: new Date, shareId: brew.shareId, version: brew.version }); + res.status(200).send(saved); }, deleteGoogleBrew : async (account, id, editId, res)=>{