From f17fbaff49d84e6ed4e120df2d14a26b10001f45 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Fri, 7 Feb 2025 21:44:54 +1300 Subject: [PATCH 1/8] Add event-stream route --- server/app.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/server/app.js b/server/app.js index 4dec6b4c4..f586b760c 100644 --- a/server/app.js +++ b/server/app.js @@ -37,6 +37,8 @@ import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import forceSSL from './forcessl.mw.js'; +import EventEmitter from 'events'; + const sanitizeBrew = (brew, accessType)=>{ brew._id = undefined; @@ -352,7 +354,7 @@ app.get('/user/:username', async (req, res, next)=>{ app.put('/api/user/rename', async (req, res)=>{ const { username, newUsername } = req.body; const ownAccount = req.account && (req.account.username == newUsername); - + if(!username || !newUsername) return res.status(400).json({ error: 'Username and newUsername are required.' }); if(!ownAccount) @@ -512,6 +514,27 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ return next(); })); +// Event Stream +const Stream = new EventEmitter; + +// After Stream starts, send initStream event +setTimeout(()=>{ + Stream.emit('push', 'initStream', { time: (new Date).toString() }); +}, 1000); + +app.get('/stream', (req, res)=>{ + res.writeHead(200, { + 'Content-Type' : 'text/event-stream', + 'Cache-Control' : 'no-cache', + 'Connection' : 'keep-alive' + }); + + Stream.on('push', (event, data)=>{ + // console.log('Event:', event, 'Data:', data); + res.write(`data: ${JSON.stringify({ ...data, eventType: event })}\n\n`); + }); +}); + // Local only if(isLocalEnvironment){ // Login From 4669c5d48922a5adf7cd7e91b3b327ac5fcdd745 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Fri, 7 Feb 2025 21:45:27 +1300 Subject: [PATCH 2/8] Add reload when initStream message is received --- client/homebrew/homebrew.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index 415390498..b8e8aa474 100644 --- a/client/homebrew/homebrew.jsx +++ b/client/homebrew/homebrew.jsx @@ -66,6 +66,18 @@ const Homebrew = createClass({ return {}; }, + componentDidMount : function(){ + const eventSource = new EventSource('/stream'); + + eventSource.addEventListener('message', (evt)=>{ + const messageData = JSON.parse(evt.data); + + if(messageData.eventType == 'initStream'){ + window.location.reload(); + } + }); + }, + render : function (){ return ( From 0a6434fbf347ef9284caf212cf123953ebb3158f Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Fri, 7 Feb 2025 21:46:28 +1300 Subject: [PATCH 3/8] Increase max-lines on app.js --- server/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app.js b/server/app.js index f586b760c..11a109843 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": 750, "skipBlankLines": true, "skipComments": true}]*/ // Set working directory to project root import { dirname } from 'path'; import { fileURLToPath } from 'url'; From 4ce1cf5eece6fd13a3e7a16ee3fa175870775d1f Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Tue, 11 Feb 2025 09:58:00 +1300 Subject: [PATCH 4/8] Move EventStream to external module --- server/app.js | 28 ++++++++++++++-------------- server/eventStreamSource.js | 9 +++++++++ 2 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 server/eventStreamSource.js diff --git a/server/app.js b/server/app.js index 11a109843..e06fda2a5 100644 --- a/server/app.js +++ b/server/app.js @@ -37,7 +37,7 @@ import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import forceSSL from './forcessl.mw.js'; -import EventEmitter from 'events'; +import Stream from './eventStreamSource.js'; const sanitizeBrew = (brew, accessType)=>{ @@ -514,27 +514,27 @@ app.get('/account', asyncHandler(async (req, res, next)=>{ return next(); })); -// Event Stream -const Stream = new EventEmitter; - -// After Stream starts, send initStream event -setTimeout(()=>{ - Stream.emit('push', 'initStream', { time: (new Date).toString() }); -}, 1000); - +// 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-Type' : 'text/event-stream', + 'Cache-Control' : 'no-cache', + 'Connection' : 'keep-alive', + 'Content-Encoding' : 'none' }); - Stream.on('push', (event, data)=>{ - // console.log('Event:', event, 'Data:', data); + 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 From 97ebdf7ae679e86b6881e36027a7aa031cb843d1 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Tue, 11 Feb 2025 09:59:14 +1300 Subject: [PATCH 5/8] Add brew update detection to Share Page --- client/homebrew/pages/sharePage/sharePage.jsx | 12 ++++++++++++ server/homebrew.api.js | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index e9c5540a2..a3fdcdf34 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -46,6 +46,18 @@ const SharePage = (props)=>{ brew.theme ); + 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/homebrew.api.js b/server/homebrew.api.js index 9a479732e..57815e384 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -16,6 +16,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; }; @@ -398,6 +400,8 @@ const api = { const after = await afterSave(); if(!after) return; + Stream.emit('sendUpdate', 'brewUpdated', { time: new Date, shareId: brew.shareId, version: brew.version }); + res.status(200).send(saved); }, deleteGoogleBrew : async (account, id, editId, res)=>{ From 2723f3e96f4b5464d15f504d33caa8e1a959de25 Mon Sep 17 00:00:00 2001 From: "G.Ambatte" Date: Sun, 20 Sep 2026 15:48:29 +1200 Subject: [PATCH 6/8] Remove live reload functionality --- client/homebrew/homebrew.jsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx index a9165a00f..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, { useEffect } from 'react'; import { BrowserRouter as Router, Routes, Route, useParams, useSearchParams } from 'react-router'; import { updateLocalStorage } from './utils/updateLocalStorage/updateLocalStorageKeys.js'; @@ -46,18 +45,6 @@ const Homebrew = (props)=>{ global.config = config; global.enablev4 = enablev4; - useEffect(()=>{ - const eventSource = new EventSource('/stream'); - - eventSource.addEventListener('message', (evt)=>{ - const messageData = JSON.parse(evt.data); - - if(messageData.eventType == 'initStream'){ - console.log('window.location.reload()'); - } - }); - }, []); - const backgroundObject = ()=>{ if(config?.deployment || (config?.local && config?.development)) { const bgText = config?.deployment || 'Local'; From 84b47b37e23c254c62f982980d4ccae9c38af60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= <5ecleric.naturalcrit@gmail.com> Date: Sun, 20 Sep 2026 20:31:18 +0200 Subject: [PATCH 7/8] Update client/homebrew/pages/sharePage/sharePage.jsx --- client/homebrew/pages/sharePage/sharePage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx index 7d662fb9a..d81b4d878 100644 --- a/client/homebrew/pages/sharePage/sharePage.jsx +++ b/client/homebrew/pages/sharePage/sharePage.jsx @@ -39,8 +39,8 @@ 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); From dfe8ebfe1bb1299399c35855d516e59674566d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Losada=20Hern=C3=A1ndez?= <5ecleric.naturalcrit@gmail.com> Date: Sun, 20 Sep 2026 20:32:29 +0200 Subject: [PATCH 8/8] Update server/app.js --- server/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app.js b/server/app.js index 06efbe7db..714c4beba 100644 --- a/server/app.js +++ b/server/app.js @@ -1,4 +1,4 @@ -/*eslint max-lines: ["warn", {"max": 750, "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';