From b966f85ecc5124732056bd53a243efcd1588c78b Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Thu, 9 Feb 2023 23:03:06 -0800 Subject: [PATCH] Spit and polish * Track the window focus/blur events. Leaving the tab while in a channel now means you may still hear sound effects in that channel. * Add a CORS JSON API /v1/statistics to get details from the server about who is online. The CORSHosts whitelist in the settings.toml limits domain access to the endpoint. --- pkg/api.go | 54 ++++++++++++++++++++++++++++++++++++++++ pkg/config/config.go | 6 ++++- pkg/server.go | 1 + web/static/css/chat.css | 20 +++++++++++++++ web/static/js/BareRTC.js | 53 +++++++++++++++++++++++++++++---------- web/templates/chat.html | 29 +++++++++++++++++---- 6 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 pkg/api.go diff --git a/pkg/api.go b/pkg/api.go new file mode 100644 index 0000000..abd354a --- /dev/null +++ b/pkg/api.go @@ -0,0 +1,54 @@ +package barertc + +import ( + "encoding/json" + "net/http" + + "git.kirsle.net/apps/barertc/pkg/config" +) + +// Statistics (/api/statistics) returns info about the users currently logged onto the chat, +// for your website to call via CORS. The URL to your site needs to be in the CORSHosts array +// of your settings.toml. +func (s *Server) Statistics() http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Handle the CORS header from your trusted domains. + if origin := r.Header.Get("Origin"); origin != "" { + var found bool + for _, allowed := range config.Current.CORSHosts { + if allowed == origin { + found = true + } + } + + if found { + w.Header().Set("Access-Control-Allow-Origin", origin) + } + } + + var result = struct { + UserCount int + Usernames []string + }{ + Usernames: []string{}, + } + + // Count all users + collect unique usernames. + var unique = map[string]struct{}{} + for _, sub := range s.IterSubscribers() { + if sub.authenticated { + result.UserCount++ + if _, ok := unique[sub.Username]; ok { + continue + } + result.Usernames = append(result.Usernames, sub.Username) + unique[sub.Username] = struct{}{} + } + } + + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + enc.Encode(result) + }) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 88c700e..f1280af 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -12,7 +12,7 @@ import ( // Version of the config format - when new fields are added, it will attempt // to write the settings.toml to disk so new defaults populate. -var currentVersion = 1 +var currentVersion = 2 // Config for your BareRTC app. type Config struct { @@ -27,6 +27,7 @@ type Config struct { Title string Branding string WebsiteURL string + CORSHosts []string UseXForwardedFor bool @@ -59,6 +60,9 @@ func DefaultConfig() Config { Title: "BareRTC", Branding: "BareRTC", WebsiteURL: "https://www.example.com", + CORSHosts: []string{ + "https://www.example.com", + }, PublicChannels: []Channel{ { ID: "lobby", diff --git a/pkg/server.go b/pkg/server.go index f54f62e..fa708c2 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -33,6 +33,7 @@ func (s *Server) Setup() error { mux.Handle("/", IndexPage()) mux.Handle("/about", AboutPage()) mux.Handle("/ws", s.WebSocket()) + mux.Handle("/api/statistics", s.Statistics()) mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static")))) s.mux = mux diff --git a/web/static/css/chat.css b/web/static/css/chat.css index 007e88a..8536c02 100644 --- a/web/static/css/chat.css +++ b/web/static/css/chat.css @@ -155,6 +155,26 @@ body { margin: 5px; } +.video-feeds.x1 > .feed { + width: 252px; + height: 168px; +} + +.video-feeds.x2 > .feed { + width: 336px; + height: 224px; +} + +.video-feeds.x3 > .feed { + width: 504px; + height: 336px; +} + +.video-feeds.x4 > .feed { + width: 672px; + height: 448px; +} + .video-feeds > .feed > video { width: 100%; height: 100%; diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 46c3afe..502f5b0 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -12,7 +12,9 @@ const app = Vue.createApp({ delimiters: ['[[', ']]'], data() { return { - busy: false, + // busy: false, // TODO: not used + windowFocused: true, // browser tab is active + windowFocusedAt: new Date(), // Website configuration provided by chat.html template. config: { @@ -37,6 +39,7 @@ const app = Vue.createApp({ channel: "lobby", username: "", //"test", message: "", + typingNotifDebounce: null, // WebSocket connection. ws: { @@ -58,6 +61,17 @@ const app = Vue.createApp({ // Who all is watching me? map of users. watching: {}, + + // Scaling setting for the videos drawer, so the user can + // embiggen the webcam sizes so a suitable size. + videoScale: "", + videoScaleOptions: [ + [ "", "Default size" ], + [ "x1", "50% larger videos" ], + [ "x2", "2x larger videos" ], + [ "x3", "3x larger videos" ], + [ "x4", "4x larger videos (not recommended)" ], + ], }, // WebRTC sessions with other users. @@ -87,7 +101,7 @@ const app = Vue.createApp({ historyScrollbox: null, DMs: {}, - // Responsive CSS for mobile. + // Responsive CSS controls for mobile. responsive: { leftDrawerOpen: false, rightDrawerOpen: false, @@ -122,11 +136,23 @@ const app = Vue.createApp({ $right: document.querySelector(".right-column"), }; + // Reset CSS overrides for responsive display on any window size change. In effect, + // making the chat panel the current screen again on phone rotation. window.addEventListener("resize", () => { - // Reset CSS overrides for responsive display on any window size change. this.resetResponsiveCSS(); }); + // Listen for window focus/unfocus events. Being on a different browser tab, for + // sound effect alert purposes, counts as not being "in" that chat channel when + // a message comes in. + window.addEventListener("focus", () => { + this.windowFocused = true; + this.windowFocusedAt = new Date(); + }); + window.addEventListener("blur", () => { + this.windowFocused = false; + }) + for (let channel of this.config.channels) { this.initHistory(channel.ID); } @@ -222,6 +248,10 @@ const app = Vue.createApp({ this.message = ""; }, + sendTypingNotification() { + // TODO + }, + // Sync the current user state (such as video broadcasting status) to // the backend, which will reload everybody's Who List. sendMe() { @@ -293,12 +323,12 @@ const app = Vue.createApp({ // Handle messages sent in chat. onMessage(msg) { - // Play sound effects if this is not the active channel. + // Play sound effects if this is not the active channel or the window is not focused. if (msg.channel.indexOf("@") === 0) { - if (msg.channel !== this.channel) { + if (msg.channel !== this.channel || !this.windowFocused) { this.playSound("DM"); } - } else if (msg.channel !== this.channel) { + } else if (msg.channel !== this.channel || !this.windowFocused) { this.playSound("Chat"); } @@ -306,7 +336,6 @@ const app = Vue.createApp({ channel: msg.channel, username: msg.username, message: msg.message, - at: msg.at, }); }, @@ -328,7 +357,6 @@ const app = Vue.createApp({ action: msg.action, username: msg.username, message: msg.message, - at: msg.at, }); } @@ -340,7 +368,6 @@ const app = Vue.createApp({ action: msg.action, username: msg.username, message: msg.message, - at: msg.at, }); } }, @@ -428,7 +455,6 @@ const app = Vue.createApp({ username: msg.username || 'Internal Server Error', message: msg.message, isChatServer: true, - at: new Date(), }); break; case "ping": @@ -861,7 +887,7 @@ const app = Vue.createApp({ }; } }, - pushHistory({ channel, username, message, action = "message", at, isChatServer, isChatClient }) { + pushHistory({ channel, username, message, action = "message", isChatServer, isChatClient }) { // Default channel = your current channel. if (!channel) { channel = this.channel; @@ -876,7 +902,7 @@ const app = Vue.createApp({ action: action, username: username, message: message, - at: at || new Date(), + at: new Date(), isChatServer, isChatClient, }); @@ -961,7 +987,8 @@ const app = Vue.createApp({ seconds = String(date.getSeconds()).padStart(2, '0'), ampm = hours >= 11 ? "pm" : "am"; - return `${(hours%12)+1}:${minutes}:${seconds} ${ampm}`; + let hour = hours%12 || 12; + return `${(hour)}:${minutes}:${seconds} ${ampm}`; }, /** diff --git a/web/templates/chat.html b/web/templates/chat.html index e2271be..cf4b963 100644 --- a/web/templates/chat.html +++ b/web/templates/chat.html @@ -56,6 +56,27 @@
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+

Sounds

@@ -328,7 +349,7 @@
-
+
@@ -448,12 +469,10 @@
+ placeholder="Message" + @keydown="sendTypingNotification()">
-
- -