Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Websocket #16

Merged
merged 1 commit into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions EsefexApi/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"esefexapi/api/middleware"
"esefexapi/api/routes"
"esefexapi/audioplayer"
"esefexapi/clientnotifiy"
"esefexapi/db"
"esefexapi/service"

Expand All @@ -22,19 +23,21 @@ type HttpApi struct {
handlers *routes.RouteHandlers
mw *middleware.Middleware
a audioplayer.IAudioPlayer
apiPort int
port int
cProto string
domain string
stop chan struct{}
ready chan struct{}
}

func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string) *HttpApi {
func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string, wsCN *clientnotifiy.WsClientNotifier, domain string) *HttpApi {
return &HttpApi{
handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto),
handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto, wsCN),
mw: middleware.NewMiddleware(dbs, ds),
a: plr,
apiPort: apiPort,
port: apiPort,
cProto: cProto,
domain: domain,
stop: make(chan struct{}, 1),
ready: make(chan struct{}),
}
Expand Down Expand Up @@ -67,10 +70,12 @@ func (api *HttpApi) run() {

router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./api/public/"))))

log.Printf("Webserver started on port %d (http://localhost:%d)\n", api.apiPort, api.apiPort)
router.Handle("/api/ws", cors(auth(h.GetWs()))).Methods("GET")

log.Printf("Webserver started on port %d (%s)\n", api.port, api.domain)

// nolint:errcheck
go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.apiPort), router)
go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.port), router)

close(api.ready)
<-api.stop
Expand Down
4 changes: 3 additions & 1 deletion EsefexApi/api/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ func (m *Middleware) Auth(next http.Handler) http.Handler {
return
}

userID := Ouser.Unwrap().ID

// Inject the user into the request context
ctx := context.WithValue(r.Context(), "user", Ouser)
ctx := context.WithValue(r.Context(), "user", userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
2 changes: 1 addition & 1 deletion EsefexApi/api/public/simpleui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Esefex Simple UI</title>
<script src="https://unpkg.com/[email protected]"></script>
<!-- <script src="https://unpkg.com/[email protected]"></script> -->
<script src="./index.js" defer></script>
<link rel="stylesheet" href="./index.css">
</head>
Expand Down
14 changes: 14 additions & 0 deletions EsefexApi/api/public/simpleui/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
async function init() {
// create a websocket connection to the server
let socket = new WebSocket(`ws://${window.location.host}/api/ws`);
socket.onopen = () => {
console.log('websocket connection established');
};
socket.addEventListener('message', async (event) => {
if (event.data != 'update') {
return;
}

// reload the page
window.location.reload();
});

const soundsDiv = document.getElementById('sounds');

let guildRequest = await fetch('/api/guild', {
Expand Down
29 changes: 29 additions & 0 deletions EsefexApi/api/routes/getws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package routes

import (
"esefexapi/types"
"log"
"net/http"

"github.com/gorilla/websocket"
)

var wsUpgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}

// api/ws
func (h *RouteHandlers) GetWs() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userID := r.Context().Value("user").(types.UserID)

conn, err := wsUpgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Error upgrading websocket: %v", err)
return
}

h.wsCN.AddConnection(userID, conn)
})
}
5 changes: 4 additions & 1 deletion EsefexApi/api/routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package routes

import (
"esefexapi/audioplayer"
"esefexapi/clientnotifiy"
"esefexapi/db"

"github.com/bwmarrin/discordgo"
Expand All @@ -11,14 +12,16 @@ type RouteHandlers struct {
dbs *db.Databases
a audioplayer.IAudioPlayer
ds *discordgo.Session
wsCN *clientnotifiy.WsClientNotifier
cProto string
}

func NewRouteHandlers(dbs *db.Databases, a audioplayer.IAudioPlayer, ds *discordgo.Session, cProto string) *RouteHandlers {
func NewRouteHandlers(dbs *db.Databases, a audioplayer.IAudioPlayer, ds *discordgo.Session, cProto string, wsCN *clientnotifiy.WsClientNotifier) *RouteHandlers {
return &RouteHandlers{
a: a,
dbs: dbs,
ds: ds,
cProto: cProto,
wsCN: wsCN,
}
}
8 changes: 6 additions & 2 deletions EsefexApi/bot/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bot

import (
"esefexapi/bot/commands"
"esefexapi/clientnotifiy"
"esefexapi/db"
"esefexapi/service"

Expand All @@ -16,14 +17,16 @@ var _ service.IService = &DiscordBot{}
type DiscordBot struct {
ds *discordgo.Session
cmdh *commands.CommandHandlers
cn clientnotifiy.IClientNotifier
stop chan struct{}
ready chan struct{}
}

func NewDiscordBot(ds *discordgo.Session, dbs *db.Databases, domain string) *DiscordBot {
func NewDiscordBot(ds *discordgo.Session, dbs *db.Databases, domain string, cn clientnotifiy.IClientNotifier) *DiscordBot {
return &DiscordBot{
ds: ds,
cmdh: commands.NewCommandHandlers(ds, dbs, domain),
cmdh: commands.NewCommandHandlers(ds, dbs, domain, cn),
cn: cn,
stop: make(chan struct{}, 1),
ready: make(chan struct{}),
}
Expand All @@ -40,6 +43,7 @@ func (b *DiscordBot) run() {
ready := b.WaitReady()

b.cmdh.RegisterComandHandlers()
b.RegisterClientUpdateHandlers()

err := ds.Open()
if err != nil {
Expand Down
27 changes: 27 additions & 0 deletions EsefexApi/bot/clientupdate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package bot

import (
"esefexapi/types"
"log"

"github.com/bwmarrin/discordgo"
)

func (b *DiscordBot) RegisterClientUpdateHandlers() {
b.ds.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
err := b.cn.UpdateNotificationUsers()
if err != nil {
log.Printf("Error notifying clients: %+v", err)
}
})

b.ds.AddHandler(func(s *discordgo.Session, r *discordgo.VoiceStateUpdate) {
userID := types.UserID(r.UserID)

err := b.cn.UpdateNotificationUsers(userID)
if err != nil {
log.Printf("Error notifying clients: %+v", err)
}
})

}
8 changes: 5 additions & 3 deletions EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"esefexapi/util"
"io"
"os"

"github.com/pkg/errors"
)

type CommandHashStore interface {
type ICommandHashStore interface {
GetCommandHash() (string, error)
SetCommandHash(hash string) error
}
Expand Down Expand Up @@ -39,13 +41,13 @@ func (f *FileCmdHashStore) GetCommandHash() (string, error) {
func (f *FileCmdHashStore) SetCommandHash(hash string) error {
file, err := os.Create(f.FilePath)
if err != nil {
return err
return errors.Wrap(err, "error creating file")
}
defer file.Close()

_, err = file.WriteString(hash)
if err != nil {
return err
return errors.Wrap(err, "error writing to file")
}

return nil
Expand Down
5 changes: 4 additions & 1 deletion EsefexApi/bot/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"esefexapi/bot/commands/cmdhandler"
"esefexapi/bot/commands/middleware"
"esefexapi/clientnotifiy"
"esefexapi/db"
"fmt"
"log"
Expand All @@ -29,16 +30,18 @@ type CommandHandlers struct {
dbs *db.Databases
domain string
mw *middleware.CommandMiddleware
cn clientnotifiy.IClientNotifier
Commands map[string]*discordgo.ApplicationCommand
Handlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)
}

func NewCommandHandlers(ds *discordgo.Session, dbs *db.Databases, domain string) *CommandHandlers {
func NewCommandHandlers(ds *discordgo.Session, dbs *db.Databases, domain string, cn clientnotifiy.IClientNotifier) *CommandHandlers {
c := &CommandHandlers{
ds: ds,
dbs: dbs,
domain: domain,
mw: middleware.NewCommandMiddleware(dbs),
cn: cn,
Commands: map[string]*discordgo.ApplicationCommand{},
Handlers: map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){},
}
Expand Down
3 changes: 3 additions & 0 deletions EsefexApi/bot/commands/sound.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ func (c *CommandHandlers) SoundUpload(s *discordgo.Session, i *discordgo.Interac
return nil, errors.Wrap(err, "Error adding sound")
}

guildID := types.GuildID(i.GuildID)
c.cn.UpdateNotificationGuilds(guildID)

log.Printf("Uploaded sound effect %v to guild %v", uid.SoundID, i.GuildID)
return &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Expand Down
124 changes: 124 additions & 0 deletions EsefexApi/clientnotifiy/clientnotifiy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package clientnotifiy

import (
"esefexapi/types"
"esefexapi/util/dcgoutil"

"github.com/bwmarrin/discordgo"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
)

type IClientNotifier interface {
// UpdateNotificationUsers notifies the clients that some data has been updated
// This should cause the client to refetch the data
// if users is empty, then all clients should be notified
// this function will handle the case where a user does not have any connections
UpdateNotificationUsers(users ...types.UserID) error
UpdateNotificationGuilds(guilds ...types.GuildID) error
UpdateNotificationChannels(channels ...types.ChannelID) error
}

var _ IClientNotifier = &WsClientNotifier{}

// implements ClientNotifier
type WsClientNotifier struct {
userConnections map[types.UserID][]*websocket.Conn
ds *discordgo.Session
stop chan struct{}
ready chan struct{}
}

func NewWsClientNotifier(ds *discordgo.Session) *WsClientNotifier {
return &WsClientNotifier{
userConnections: make(map[types.UserID][]*websocket.Conn),
ds: ds,
stop: make(chan struct{}),
ready: make(chan struct{}),
}
}

// UpdateNotificationChannels implements IClientNotifier.
func (w *WsClientNotifier) UpdateNotificationChannels(channels ...types.ChannelID) error {
for _, channel := range channels {
users, err := dcgoutil.ChannelUserIDs(w.ds, channel)
if err != nil {
return errors.Wrap(err, "error getting channel user ids")
}

err = w.UpdateNotificationUsers(users...)
if err != nil {
return errors.Wrap(err, "error updating notification")
}
}

return nil
}

// UpdateNotificationGuilds implements IClientNotifier.
func (w *WsClientNotifier) UpdateNotificationGuilds(guilds ...types.GuildID) error {
for _, guild := range guilds {
users, err := dcgoutil.GuildUserIDs(w.ds, guild)
if err != nil {
return errors.Wrap(err, "error getting channel user ids")
}

err = w.UpdateNotificationUsers(users...)
if err != nil {
return errors.Wrap(err, "error updating notification")
}
}

return nil
}

func (w *WsClientNotifier) UpdateNotificationUsers(users ...types.UserID) error {
if len(users) == 0 {
for k := range w.userConnections {
err := w.writeUpdate(k)
if err != nil {
return errors.Wrap(err, "error writing update")
}
}
return nil
}

for _, user := range users {
if _, ok := w.userConnections[user]; !ok {
continue
}

err := w.writeUpdate(user)
if err != nil {
return errors.Wrap(err, "error writing update")
}
}
return nil
}

func (w *WsClientNotifier) writeUpdate(user types.UserID) error {
var causedError error = nil

for _, conn := range w.userConnections[user] {
err := conn.WriteMessage(websocket.TextMessage, []byte("update"))
if err != nil {
conn.Close()
w.RemoveConnection(user, conn)
causedError = errors.Wrap(err, "error writing message to websocket, removing connection")
}
}
return causedError
}

func (w *WsClientNotifier) AddConnection(user types.UserID, conn *websocket.Conn) {
w.userConnections[user] = append(w.userConnections[user], conn)
}

func (w *WsClientNotifier) RemoveConnection(user types.UserID, conn *websocket.Conn) {
for i, c := range w.userConnections[user] {
if c == conn {
w.userConnections[user] = append(w.userConnections[user][:i], w.userConnections[user][i+1:]...)
break
}
}
}
Loading
Loading