From 9487595e0400e3410780aad568f594c8aa2338d5 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 6 Feb 2023 13:27:29 -0800 Subject: [PATCH] Timestamps, Sound Effects & Love --- Makefile | 7 +- cmd/BareRTC/main.go | 2 + pkg/config/config.go | 1 + pkg/handlers.go | 13 ++- pkg/messages.go | 11 ++- pkg/websocket.go | 27 ++++-- web/static/js/BareRTC.js | 137 +++++++++++++++++++++++++++- web/static/js/sounds.js | 35 +++++++ web/static/sfx/beep-6-96243.mp3 | Bin 0 -> 27360 bytes web/static/sfx/beep-sound-8333.mp3 | Bin 0 -> 4179 bytes web/static/sfx/bird-3-f-89236.mp3 | Bin 0 -> 12480 bytes web/static/sfx/credits.txt | 1 + web/static/sfx/ping-82822.mp3 | Bin 0 -> 26400 bytes web/static/sfx/sonar-ping-95840.mp3 | Bin 0 -> 113684 bytes web/templates/chat.html | 132 +++++++++++++++++++++++++-- 15 files changed, 338 insertions(+), 28 deletions(-) create mode 100644 web/static/js/sounds.js create mode 100644 web/static/sfx/beep-6-96243.mp3 create mode 100644 web/static/sfx/beep-sound-8333.mp3 create mode 100644 web/static/sfx/bird-3-f-89236.mp3 create mode 100644 web/static/sfx/credits.txt create mode 100644 web/static/sfx/ping-82822.mp3 create mode 100644 web/static/sfx/sonar-ping-95840.mp3 diff --git a/Makefile b/Makefile index a024dcf..8014bc0 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,7 @@ +.PHONY: run run: - go run cmd/BareRTC/main.go -debug \ No newline at end of file + go run cmd/BareRTC/main.go -debug + +.PHONY: build +build: + go build -o BareRTC cmd/BareRTC/main.go \ No newline at end of file diff --git a/cmd/BareRTC/main.go b/cmd/BareRTC/main.go index ceab369..4813b62 100644 --- a/cmd/BareRTC/main.go +++ b/cmd/BareRTC/main.go @@ -33,5 +33,7 @@ func main() { app := barertc.NewServer() app.Setup() + + log.Info("Listening at %s", address) panic(app.ListenAndServe(address)) } diff --git a/pkg/config/config.go b/pkg/config/config.go index b08b181..3705de8 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -19,6 +19,7 @@ type Config struct { } Title string + Branding string WebsiteURL string PublicChannels []Channel diff --git a/pkg/handlers.go b/pkg/handlers.go index fb09c38..025ee34 100644 --- a/pkg/handlers.go +++ b/pkg/handlers.go @@ -108,10 +108,11 @@ func (s *Server) OnMessage(sub *Subscriber, msg Message) { // Message to be echoed to the channel. var message = Message{ - Action: ActionMessage, - Channel: msg.Channel, - Username: sub.Username, - Message: markdown, + Action: ActionMessage, + Channel: msg.Channel, + Username: sub.Username, + Message: markdown, + Timestamp: time.Now(), } // Is this a DM? @@ -119,7 +120,9 @@ func (s *Server) OnMessage(sub *Subscriber, msg Message) { // Echo the message only to both parties. s.SendTo(sub.Username, message) message.Channel = "@" + sub.Username - s.SendTo(msg.Channel, message) + if err := s.SendTo(msg.Channel, message); err != nil { + sub.ChatServer("Your message could not be delivered: %s", err) + } return } diff --git a/pkg/messages.go b/pkg/messages.go index 1aacaf4..fb792f8 100644 --- a/pkg/messages.go +++ b/pkg/messages.go @@ -1,10 +1,13 @@ package barertc +import "time" + type Message struct { - Action string `json:"action,omitempty"` - Channel string `json:"channel,omitempty"` - Username string `json:"username,omitempty"` - Message string `json:"message,omitempty"` + Action string `json:"action,omitempty"` + Channel string `json:"channel,omitempty"` + Username string `json:"username,omitempty"` + Message string `json:"message,omitempty"` + Timestamp time.Time `json:"at,omitempty"` // JWT token for `login` actions. JWTToken string `json:"jwt,omitempty"` diff --git a/pkg/websocket.go b/pkg/websocket.go index e51b3f5..93870fa 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -239,23 +239,26 @@ func (s *Server) Broadcast(msg Message) { continue } - sub.SendJSON(Message{ - Action: msg.Action, - Channel: msg.Channel, - Username: msg.Username, - Message: msg.Message, - }) + sub.SendJSON(msg) } } // SendTo sends a message to a given username. -func (s *Server) SendTo(username string, msg Message) { +func (s *Server) SendTo(username string, msg Message) error { log.Debug("SendTo(%s): %+v", username, msg) username = strings.TrimPrefix(username, "@") s.subscribersMu.RLock() defer s.subscribersMu.RUnlock() + + // If no timestamp, add it. + if msg.Timestamp.IsZero() { + msg.Timestamp = time.Now() + } + + var found bool for _, sub := range s.IterSubscribers(true) { if sub.Username == username { + found = true sub.SendJSON(Message{ Action: msg.Action, Channel: msg.Channel, @@ -264,6 +267,11 @@ func (s *Server) SendTo(username string, msg Message) { }) } } + + if !found { + return fmt.Errorf("%s is not online", username) + } + return nil } // SendWhoList broadcasts the connected members to everybody in the room. @@ -292,8 +300,9 @@ func (s *Server) SendWhoList() { for _, sub := range subscribers { sub.SendJSON(Message{ - Action: ActionWhoList, - WhoList: users, + Action: ActionWhoList, + WhoList: users, + Timestamp: time.Now(), }) } } diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index 70e5b5f..d9c4193 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -18,6 +18,13 @@ const app = Vue.createApp({ config: { channels: PublicChannels, website: WebsiteURL, + sounds: { + available: SoundEffects, + settings: DefaultSounds, + ready: false, + audioContext: null, + audioTracks: {}, + } }, // User JWT settings if available. @@ -96,9 +103,15 @@ const app = Vue.createApp({ loginModal: { visible: false, }, + + settingsModal: { + visible: false, + }, } }, mounted() { + this.setupSounds(); + this.webcam.elem = document.querySelector("#localVideo"); this.historyScrollbox = document.querySelector("#chatHistory"); @@ -280,10 +293,20 @@ const app = Vue.createApp({ // Handle messages sent in chat. onMessage(msg) { + // Play sound effects if this is not the active channel. + if (msg.channel.indexOf("@") === 0) { + if (msg.channel !== this.channel) { + this.playSound("DM"); + } + } else if (msg.channel !== this.channel) { + this.playSound("Chat"); + } + this.pushHistory({ channel: msg.channel, username: msg.username, message: msg.message, + at: msg.at, }); }, @@ -293,6 +316,9 @@ const app = Vue.createApp({ if (msg.message.indexOf("has exited the room!") > -1) { // Clean up data about this user. this.onUserExited(msg); + this.playSound("Leave"); + } else { + this.playSound("Enter"); } // Push it to the history of all public channels. @@ -302,6 +328,19 @@ const app = Vue.createApp({ action: msg.action, username: msg.username, message: msg.message, + at: msg.at, + }); + } + + // Push also to any DM channels for this user. + let channel = "@" + msg.username; + if (this.channels[channel] != undefined) { + this.pushHistory({ + channel: channel, + action: msg.action, + username: msg.username, + message: msg.message, + at: msg.at, }); } }, @@ -320,8 +359,8 @@ const app = Vue.createApp({ this.ChatClient(`WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`); if (ev.code !== 1001) { - this.ChatClient("Reconnecting in 1s"); - setTimeout(this.dial, 1000); + this.ChatClient("Reconnecting in 5s"); + setTimeout(this.dial, 5000); } }); @@ -345,6 +384,13 @@ const app = Vue.createApp({ } let msg = JSON.parse(ev.data); + try { + // Cast timestamp to date. + msg.at = new Date(msg.at); + } catch(e) { + console.error("Parsing timestamp '%s' on msg: %s", msg.at, e); + } + switch (msg.action) { case "who": console.log("Got the Who List: %s", msg); @@ -384,7 +430,12 @@ const app = Vue.createApp({ username: msg.username || 'Internal Server Error', message: msg.message, isChatServer: true, + at: new Date(), }); + break; + case "ping": + console.debug("Received ping from server"); + break; default: console.error("Unexpected action: %s", JSON.stringify(msg)); } @@ -574,6 +625,14 @@ const app = Vue.createApp({ * Front-end web app concerns. */ + // Settings modal. + showSettings() { + this.settingsModal.visible = true; + }, + hideSettings() { + this.settingsModal.visible = false; + }, + // Set active chat room. setChannel(channel) { this.channel = typeof(channel) === "string" ? channel : channel.ID; @@ -803,7 +862,7 @@ const app = Vue.createApp({ }; } }, - pushHistory({ channel, username, message, action = "message", isChatServer, isChatClient }) { + pushHistory({ channel, username, message, action = "message", at, isChatServer, isChatClient }) { // Default channel = your current channel. if (!channel) { channel = this.channel; @@ -818,6 +877,7 @@ const app = Vue.createApp({ action: action, username: username, message: message, + at: at || new Date(), isChatServer, isChatClient, }); @@ -894,6 +954,77 @@ const app = Vue.createApp({ isChatClient: true, }); }, + + // Format a datetime nicely for chat timestamp. + prettyDate(date) { + let hours = date.getHours(), + minutes = String(date.getMinutes()).padStart(2, '0'), + seconds = String(date.getSeconds()).padStart(2, '0'), + ampm = hours >= 11 ? "pm" : "am"; + + return `${(hours%12)+1}:${minutes}:${seconds} ${ampm}`; + }, + + /** + * Sound effect concerns. + */ + + setupSounds() { + if (AudioContext) { + this.config.sounds.audioContext = new AudioContext(); + } else { + this.config.sounds.audioContext = window.AudioContext || window.webkitAudioContext; + } + if (!this.config.sounds.audioContext) { + console.error("Couldn't set up AudioContext! No sound effects will be supported."); + return; + } + + console.error("AudioContext:", this.config.sounds.audioContext); + + // Create