diff options
author | Galen Guyer <galen@galenguyer.com> | 2022-04-25 20:21:03 -0400 |
---|---|---|
committer | Galen Guyer <galen@galenguyer.com> | 2022-04-25 20:21:03 -0400 |
commit | 6014afab0fabad2b7213fee67a1dc28257ba48e4 (patch) | |
tree | af3f0d137584b7aa882083a9fedbd7130bb94823 /sse | |
parent | defd715b7de572edcd61a23ba693fdb7ff3a7e2f (diff) |
use server sent events for live poll updates without page refresh
also just use the material theme directly because holy moly
themeswitcher is having a bad time
Diffstat (limited to 'sse')
-rw-r--r-- | sse/broker.go | 130 |
1 files changed, 130 insertions, 0 deletions
diff --git a/sse/broker.go b/sse/broker.go new file mode 100644 index 0000000..464b333 --- /dev/null +++ b/sse/broker.go @@ -0,0 +1,130 @@ +/* +The MIT License (MIT) + +Copyright (c) 2017-2021 Ismael Celis and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package sse + +import ( + "io" + "log" + "time" + + "github.com/gin-gonic/gin" +) + +const patience time.Duration = time.Second * 1 + +type ( + NotificationEvent struct { + EventName string + Payload interface{} + } + + NotifierChan chan NotificationEvent + + Broker struct { + + // Events are pushed to this channel by the main events-gathering routine + Notifier NotifierChan + + // New client connections + newClients chan NotifierChan + + // Closed client connections + closingClients chan NotifierChan + + // Client connections registry + clients map[NotifierChan]struct{} + } +) + +func NewBroker() (broker *Broker) { + // Instantiate a broker + return &Broker{ + Notifier: make(NotifierChan, 1), + newClients: make(chan NotifierChan), + closingClients: make(chan NotifierChan), + clients: make(map[NotifierChan]struct{}), + } +} + +func (broker *Broker) ServeHTTP(c *gin.Context) { + eventName := c.Param("topic") + + // Each connection registers its own message channel with the Broker's connections registry + messageChan := make(NotifierChan) + + // Signal the broker that we have a new connection + broker.newClients <- messageChan + + // Remove this client from the map of connected clients + // when this handler exits. + defer func() { + broker.closingClients <- messageChan + }() + + c.Stream(func(w io.Writer) bool { + // Emit Server Sent Events compatible + event := <-messageChan + + switch eventName { + case event.EventName: + c.SSEvent(event.EventName, event.Payload) + } + + // Flush the data immediately instead of buffering it for later. + c.Writer.Flush() + + return true + }) +} + +// Listen for new notifications and redistribute them to clients +func (broker *Broker) Listen() { + for { + select { + case s := <-broker.newClients: + + // A new client has connected. + // Register their message channel + broker.clients[s] = struct{}{} + log.Printf("Client added. %d registered clients", len(broker.clients)) + case s := <-broker.closingClients: + + // A client has dettached and we want to + // stop sending them messages. + delete(broker.clients, s) + log.Printf("Removed client. %d registered clients", len(broker.clients)) + case event := <-broker.Notifier: + + // We got a new event from the outside! + // Send event to all connected clients + for clientMessageChan := range broker.clients { + select { + case clientMessageChan <- event: + case <-time.After(patience): + log.Print("Skipping client.") + } + } + } + } +} |