From 1f4e1a69a3f59cf1ab10c7fce15ec27dd98d3cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= Date: Wed, 15 Jan 2025 11:47:42 +0100 Subject: [PATCH 1/5] Open start page automatically when starting lobby MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use server-sent events to automatically open the start page when the host starts the lobby. Since they are one-sided, keep them alive as long the lobby is alive. If an extension joins a different lobby, it automatically closes its current eventstream (which the server wont know about) and wont receive any updates from the old lobby. Signed-off-by: Victor Löfgren --- wikiweaver-ext/background.js | 27 ++++- .../cmd/main/wikiweaver-server.go | 108 ++++++++++++++++-- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/wikiweaver-ext/background.js b/wikiweaver-ext/background.js index e4b3b37..c2fcf4d 100644 --- a/wikiweaver-ext/background.js +++ b/wikiweaver-ext/background.js @@ -1,5 +1,7 @@ const defaultdomain = "https://wikiweaver.stuffontheinter.net"; +var eventSource = null; + function Matches(url, filters) { for (let filter of filters) { if (url.match(filter)) { @@ -140,6 +142,22 @@ async function HandleMessageConnect(msg) { if (response.Success) { await SetPageCount(0); await SetUserIdForLobby(options.code, response.UserID); + + if (eventSource != null) { + eventSource.close(); + eventSource = null; + } + + // Server sent event reference: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events + eventSource = new EventSource(`${options.url}/api/ext/events?code=${options.code}&userid=${response.UserID}`); + eventSource.addEventListener("start", (e) => { + const data = JSON.parse(e.data); + + chrome.tabs.create({ + active: true, + url: Urlify(data.StartPage) + }) + }); } await UpdateBadge(response.Success); @@ -188,6 +206,11 @@ async function SendPOSTRequestToServer(url, endpoint, body) { return response; } +function Urlify(InString) { + // Turns an id back into a URL + return "https://en.wikipedia.org/wiki/" + InString +} + async function GetWikipediaArticleTitle(url) { title = decodeURIComponent(url) .split("/") @@ -214,14 +237,14 @@ async function SearchForWikipediaTitle(title) { }; url = url + "?origin=*"; - Object.keys(params).forEach(function (key) { + Object.keys(params).forEach(function(key) { url += "&" + key + "=" + params[key]; }); response = await fetch(url) .then((response) => response.json()) .then((json) => json) - .catch(function (error) { + .catch(function(error) { return { error: error }; }); diff --git a/wikiweaver-server/cmd/main/wikiweaver-server.go b/wikiweaver-server/cmd/main/wikiweaver-server.go index c0bf0f0..e5f7366 100644 --- a/wikiweaver-server/cmd/main/wikiweaver-server.go +++ b/wikiweaver-server/cmd/main/wikiweaver-server.go @@ -47,6 +47,7 @@ type WebClient struct { } type ExtClient struct { + Evt http.ResponseWriter UserID string Username string Clicks int @@ -95,12 +96,24 @@ func (l *Lobby) hasHost() bool { return false } -func (l *Lobby) Broadcast(v interface{}) { +func (l *Lobby) BroadcastToWeb(v interface{}) { for _, wc := range l.WebClients { wc.sendWithWarningOnFail(v) } } +func (l *Lobby) BroadcastToExt(msgResponseExt string) { + for _, ec := range l.ExtClients { + if ec.Evt == nil { + log.Printf("failed to send event to ext %s", ec.UserID) + continue + } + + ec.Evt.Write([]byte(msgResponseExt)) + ec.Evt.(http.Flusher).Flush() + } +} + func (l *Lobby) removeWebClient(wcToRemove *WebClient) { for i := len(l.WebClients) - 1; i >= 0; i-- { if l.WebClients[i] == wcToRemove { @@ -119,6 +132,16 @@ func (l *Lobby) GetExtClientFromUsername(usernameToCheck string) *ExtClient { return nil } +func (l *Lobby) getExtClientByUserid(userid string) *ExtClient { + for _, extClient := range l.ExtClients { + if userid == extClient.UserID { + return extClient + } + } + + return nil +} + var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -424,7 +447,7 @@ func HandleMessageEnd(lobby *Lobby, wc *WebClient, buf []byte) { Countdown: int(lobby.Countdown.Seconds()), } - lobby.Broadcast(msgResponse) + lobby.BroadcastToWeb(msgResponse) } func HandleMessagePing(lobby *Lobby, wc *WebClient, buf []byte) { @@ -481,7 +504,7 @@ func HandleMessageReset(lobby *Lobby, wc *WebClient, buf []byte) { Success: true, } - lobby.Broadcast(msgResponse) + lobby.BroadcastToWeb(msgResponse) } type StartFromWebMessage struct { @@ -499,6 +522,10 @@ type StartToWebMessage struct { StartTime int } +type StartEvtToExt struct { + StartPage string +} + func HandleMessageStart(lobby *Lobby, wc *WebClient, buf []byte) { lobby.mu.Lock() defer lobby.mu.Unlock() @@ -554,7 +581,7 @@ func HandleMessageStart(lobby *Lobby, wc *WebClient, buf []byte) { log.Printf("web client %s started lobby %s with pages '%s' to '%s' (%.0f seconds)", wc.conn.RemoteAddr(), lobby.Code, lobby.StartPage, lobby.GoalPage, lobby.Countdown.Seconds()) - msgResponse = StartToWebMessage{ + msgResponseWeb := StartToWebMessage{ Message: Message{ "start", }, @@ -564,8 +591,22 @@ func HandleMessageStart(lobby *Lobby, wc *WebClient, buf []byte) { Countdown: int(lobby.Countdown.Seconds()), StartTime: int(lobby.StartTime.Unix()), } + lobby.BroadcastToWeb(msgResponseWeb) - lobby.Broadcast(msgResponse) + msgResponseExt := StartEvtToExt{ + StartPage: lobby.StartPage, + } + lobby.BroadcastToExt(constructExtEvtResponse("start", msgResponseExt)) +} + +func constructExtEvtResponse(event string, i interface{}) string { + response, err := json.Marshal(i) + if err != nil { + log.Printf("failed to marshal event to extension (%+v): %s", response, err) + return ": invalid object\n\n" + } + + return fmt.Sprintf("event:%s\ndata:%s\n\n", event, response) } func webClientListener(lobby *Lobby, wc *WebClient) { @@ -747,7 +788,7 @@ func handleExtJoin(w http.ResponseWriter, r *http.Request) { Success: true, } - lobby.Broadcast(resetMessage) + lobby.BroadcastToWeb(resetMessage) lobby.State = Reset } @@ -773,7 +814,7 @@ func handleExtJoin(w http.ResponseWriter, r *http.Request) { Username: request.Username, } - lobby.Broadcast(joinToWebMessage) + lobby.BroadcastToWeb(joinToWebMessage) successResponse := JoinToExtResponse{ Success: true, @@ -924,7 +965,7 @@ func handleExtPage(w http.ResponseWriter, r *http.Request) { log.Printf("forwarding page to %d web clients: %+v", len(lobby.WebClients), pageToWebMessage) - lobby.Broadcast(pageToWebMessage) + lobby.BroadcastToWeb(pageToWebMessage) successResponse := PageToExtResponse{ Success: true, @@ -933,6 +974,56 @@ func handleExtPage(w http.ResponseWriter, r *http.Request) { } } +func storeEvtReference(code string, userid string, w http.ResponseWriter) { + lobby := globalState.Lobbies[code] + + if lobby == nil { + log.Printf("lobby %s doesnt exist anymore, userid %s event listening wont work, weird", code, userid) + return + } + + lobby.mu.Lock() + defer lobby.mu.Unlock() + + extClient := lobby.getExtClientByUserid(userid) + + if extClient == nil { + log.Printf("failed to find userid %s in lobby %s, event listening wont work, weird", userid, lobby.Code) + return + } + + extClient.mu.Lock() + defer extClient.mu.Unlock() + + extClient.Evt = w +} + +func handleExtEvents(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Expose-Headers", "Content-Type") + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + + userid := r.URL.Query().Get("userid") + code := r.URL.Query().Get("code") + + storeEvtReference(code, userid, w) + + flusher, ok := w.(http.Flusher) + if !ok { + fmt.Printf("weird, we cant flush response writer for userid %s", userid) + return + } + + // Keep the event stream alive as long as the lobby + for globalState.Lobbies[code] != nil { + fmt.Fprintf(w, ": keep-alive\n\n") + flusher.Flush() + time.Sleep(30 * time.Second) + } +} + func readWords(wordsFilepath string) []string { contents, err := os.ReadFile(wordsFilepath) if err != nil { @@ -980,6 +1071,7 @@ func main() { http.HandleFunc("/api/ext/join", handleExtJoin) http.HandleFunc("/api/ext/page", handleExtPage) + http.HandleFunc("/api/ext/events", handleExtEvents) address := "0.0.0.0" if dev { From bbe58968817b8e11edea65416691109e94917f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= Date: Fri, 17 Jan 2025 14:53:22 +0100 Subject: [PATCH 2/5] Always set local storage url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Victor Löfgren --- wikiweaver-ext/background.js | 10 +++++++--- wikiweaver-ext/options/options.js | 14 +++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/wikiweaver-ext/background.js b/wikiweaver-ext/background.js index c2fcf4d..382bd77 100644 --- a/wikiweaver-ext/background.js +++ b/wikiweaver-ext/background.js @@ -188,9 +188,6 @@ chrome.runtime.onMessage.addListener(async (msg) => { async function SendPOSTRequestToServer(url, endpoint, body) { console.log("sent:", body); - if (url === "") { - url = defaultdomain; - } let response = await fetch(`${url}${endpoint}`, { method: "POST", body: JSON.stringify(body), @@ -340,3 +337,10 @@ async function UpdateBadge(success) { chrome.action.setBadgeBackgroundColor({ color: color }); chrome.action.setBadgeText({ text: String(await GetPageCount()) }); } + +chrome.runtime.onInstalled.addListener(async () => { + let options = await chrome.storage.local.get(); + + const url = options.url || defaultdomain; + await chrome.storage.local.set({ url }); +}); diff --git a/wikiweaver-ext/options/options.js b/wikiweaver-ext/options/options.js index 975bd18..26ab3d5 100644 --- a/wikiweaver-ext/options/options.js +++ b/wikiweaver-ext/options/options.js @@ -4,15 +4,19 @@ async function init(e) { async function restore() { const options = await chrome.storage.local.get() - document.querySelector("#url").value = options.url || ""; + document.querySelector("#url").value = options.url; } async function save(e) { e.preventDefault(); - await chrome.storage.local.set({ - url: document.querySelector("#url").value.toLowerCase(), - }); - // todo: show saved succeeded in some way + + const urlElem = document.querySelector("#url"); + + const url = new URL(urlElem.value.toLowerCase() || urlElem.placeholder); + + await chrome.storage.local.set({ url: url.origin }); + // TODO: show saved succeeded in some way + } document.addEventListener("DOMContentLoaded", () => init(), false); From f7d949cbf1216dcc34aa0bd4108025f7634b8a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= Date: Fri, 17 Jan 2025 16:33:17 +0100 Subject: [PATCH 3/5] Add option to disable auto open start page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Victor Löfgren --- wikiweaver-ext/background.js | 18 +++++++++++------- wikiweaver-ext/options/options.html | 8 ++++++++ wikiweaver-ext/options/options.js | 10 ++++++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/wikiweaver-ext/background.js b/wikiweaver-ext/background.js index 382bd77..8413258 100644 --- a/wikiweaver-ext/background.js +++ b/wikiweaver-ext/background.js @@ -150,13 +150,16 @@ async function HandleMessageConnect(msg) { // Server sent event reference: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events eventSource = new EventSource(`${options.url}/api/ext/events?code=${options.code}&userid=${response.UserID}`); - eventSource.addEventListener("start", (e) => { + eventSource.addEventListener("start", async (e) => { const data = JSON.parse(e.data); - - chrome.tabs.create({ - active: true, - url: Urlify(data.StartPage) - }) + const options = await chrome.storage.local.get(); + + if (options.autoOpenStartPage) { + chrome.tabs.create({ + active: true, + url: Urlify(data.StartPage) + }) + } }); } @@ -342,5 +345,6 @@ chrome.runtime.onInstalled.addListener(async () => { let options = await chrome.storage.local.get(); const url = options.url || defaultdomain; - await chrome.storage.local.set({ url }); + const autoOpenStartPage = options.autoOpenStartPage || true; + await chrome.storage.local.set({ url, autoOpenStartPage }); }); diff --git a/wikiweaver-ext/options/options.html b/wikiweaver-ext/options/options.html index b2a9657..055c2be 100644 --- a/wikiweaver-ext/options/options.html +++ b/wikiweaver-ext/options/options.html @@ -18,6 +18,14 @@ + + + + + + + + diff --git a/wikiweaver-ext/options/options.js b/wikiweaver-ext/options/options.js index 26ab3d5..720ad70 100644 --- a/wikiweaver-ext/options/options.js +++ b/wikiweaver-ext/options/options.js @@ -11,12 +11,18 @@ async function save(e) { e.preventDefault(); const urlElem = document.querySelector("#url"); + const autoOpenElem = document.querySelector("#auto-open-start-page"); const url = new URL(urlElem.value.toLowerCase() || urlElem.placeholder); - await chrome.storage.local.set({ url: url.origin }); - // TODO: show saved succeeded in some way + await chrome.storage.local.set( + { + url: url.origin, + autoOpenStartPage: autoOpenElem.checked, + } + ); + // TODO: show saved succeeded in some way } document.addEventListener("DOMContentLoaded", () => init(), false); From ac05b0c971d0f0a0b60f7b5a4354f21ee0bff322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= Date: Fri, 17 Jan 2025 17:03:41 +0100 Subject: [PATCH 4/5] Add "open start page"-button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Victor Löfgren --- wikiweaver-ext/background.js | 2 ++ wikiweaver-ext/popup/popup.html | 4 ++++ wikiweaver-ext/popup/popup.js | 25 ++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/wikiweaver-ext/background.js b/wikiweaver-ext/background.js index 8413258..a9aa4c0 100644 --- a/wikiweaver-ext/background.js +++ b/wikiweaver-ext/background.js @@ -154,6 +154,8 @@ async function HandleMessageConnect(msg) { const data = JSON.parse(e.data); const options = await chrome.storage.local.get(); + chrome.storage.session.set({ startPage: data.StartPage }); + if (options.autoOpenStartPage) { chrome.tabs.create({ active: true, diff --git a/wikiweaver-ext/popup/popup.html b/wikiweaver-ext/popup/popup.html index 3b1ee71..36d2ef9 100644 --- a/wikiweaver-ext/popup/popup.html +++ b/wikiweaver-ext/popup/popup.html @@ -27,6 +27,10 @@ open lobby open_in_new +