From 60dd98a1e768ef2b0904c451cdda76672d0c7168 Mon Sep 17 00:00:00 2001 From: joeyak Date: Thu, 14 Mar 2019 15:21:53 -0400 Subject: [PATCH] Add infrastructure for the client to send formatted data to the server --- chatclient.go | 3 +- common/chatcommands.go | 52 +++++++++ common/chatdata.go | 236 +++++++++++++---------------------------- handlers.go | 16 ++- wasm/main_wasm.go | 65 +++++++++--- 5 files changed, 192 insertions(+), 180 deletions(-) create mode 100644 common/chatcommands.go diff --git a/chatclient.go b/chatclient.go index d8b2fe0..823762d 100644 --- a/chatclient.go +++ b/chatclient.go @@ -53,7 +53,8 @@ func ParseEmotes(msg string) string { } //Client has a new message to broadcast -func (cl *Client) NewMsg(msg string) { +func (cl *Client) NewMsg(data common.ClientData) { + msg := data.Message msg = html.EscapeString(msg) msg = removeDumbSpaces(msg) msg = strings.Trim(msg, " ") diff --git a/common/chatcommands.go b/common/chatcommands.go new file mode 100644 index 0000000..7723cea --- /dev/null +++ b/common/chatcommands.go @@ -0,0 +1,52 @@ +package common + +import "strings" + +const CommandNameSeparator = "," + +type ChatCommandNames []string + +func (c ChatCommandNames) String() string { + return strings.Join(c, CommandNameSeparator) +} + +// Names for commands +var ( + // User Commands + CNMe ChatCommandNames = []string{"me"} + CNHelp ChatCommandNames = []string{"help"} + CNCount ChatCommandNames = []string{"count"} + CNColor ChatCommandNames = []string{"color", "colour"} + CNWhoAmI ChatCommandNames = []string{"w", "whoami"} + CNAuth ChatCommandNames = []string{"auth"} + CNUsers ChatCommandNames = []string{"users"} + // Mod Commands + CNSv ChatCommandNames = []string{"sv"} + CNPlaying ChatCommandNames = []string{"playing"} + CNUnmod ChatCommandNames = []string{"unmod"} + CNKick ChatCommandNames = []string{"kick"} + CNBan ChatCommandNames = []string{"ban"} + CNUnban ChatCommandNames = []string{"unban"} + // Admin Commands + CNMod ChatCommandNames = []string{"mod"} + CNReloadPlayer ChatCommandNames = []string{"reloadplayer"} + CNReloadEmotes ChatCommandNames = []string{"reloademotes"} + CNModpass ChatCommandNames = []string{"modpass"} +) + +var ChatCommands = []ChatCommandNames{ + CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers, + CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, + CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, +} + +func GetFullChatCommand(c string) string { + for _, names := range ChatCommands { + for _, n := range names { + if c == n { + return names.String() + } + } + } + return "" +} diff --git a/common/chatdata.go b/common/chatdata.go index 1f80173..310fe4e 100644 --- a/common/chatdata.go +++ b/common/chatdata.go @@ -2,58 +2,60 @@ package common import ( "encoding/json" + "errors" "fmt" "strconv" - "strings" ) -const CommandNameSeparator = "," - -type ChatCommandNames []string - -func (c ChatCommandNames) String() string { - return strings.Join(c, CommandNameSeparator) +type DataInterface interface { + HTML() string } -// Names for commands -var ( - // User Commands - CNMe ChatCommandNames = []string{"me"} - CNHelp ChatCommandNames = []string{"help"} - CNCount ChatCommandNames = []string{"count"} - CNColor ChatCommandNames = []string{"color", "colour"} - CNWhoAmI ChatCommandNames = []string{"w", "whoami"} - CNAuth ChatCommandNames = []string{"auth"} - CNUsers ChatCommandNames = []string{"users"} - // Mod Commands - CNSv ChatCommandNames = []string{"sv"} - CNPlaying ChatCommandNames = []string{"playing"} - CNUnmod ChatCommandNames = []string{"unmod"} - CNKick ChatCommandNames = []string{"kick"} - CNBan ChatCommandNames = []string{"ban"} - CNUnban ChatCommandNames = []string{"unban"} - // Admin Commands - CNMod ChatCommandNames = []string{"mod"} - CNReloadPlayer ChatCommandNames = []string{"reloadplayer"} - CNReloadEmotes ChatCommandNames = []string{"reloademotes"} - CNModpass ChatCommandNames = []string{"modpass"} -) - -var ChatCommands = []ChatCommandNames{ - CNMe, CNHelp, CNCount, CNColor, CNWhoAmI, CNAuth, CNUsers, - CNSv, CNPlaying, CNUnmod, CNKick, CNBan, CNUnban, - CNMod, CNReloadPlayer, CNReloadEmotes, CNModpass, +type ChatData struct { + Hidden bool + Type DataType + Data json.RawMessage } -func GetFullChatCommand(c string) string { - for _, names := range ChatCommands { - for _, n := range names { - if c == n { - return names.String() - } - } +func (c ChatData) GetData() (DataInterface, error) { + var data DataInterface + var err error + + switch c.Type { + case DTInvalid: + return nil, errors.New("data type is invalid") + case DTChat: + d := DataMessage{} + err = json.Unmarshal(c.Data, &d) + data = d + case DTError: + d := DataError{} + err = json.Unmarshal(c.Data, &d) + data = d + case DTCommand: + d := DataCommand{} + err = json.Unmarshal(c.Data, &d) + data = d + case DTEvent: + d := DataEvent{} + err = json.Unmarshal(c.Data, &d) + data = d + case DTClient: + d := ClientData{} + err = json.Unmarshal(c.Data, &d) + data = d } - return "" + + return data, err +} + +func newChatData(hidden bool, dtype DataType, d DataInterface) (ChatData, error) { + rawData, err := json.Marshal(d) + return ChatData{ + Hidden: hidden, + Type: dtype, + Data: rawData, + }, err } type ClientData struct { @@ -61,11 +63,6 @@ type ClientData struct { Message string } -type ChatData struct { - Type DataType - Data DataInterface -} - type DataError struct { Message string } @@ -88,31 +85,6 @@ type DataEvent struct { Color string } -type DataInterface interface { - GetType() DataType - HTML() string -} - -func (c ClientData) GetType() DataType { - return DTClient -} - -func (d DataMessage) GetType() DataType { - return DTChat -} - -func (d DataError) GetType() DataType { - return DTError -} - -func (d DataCommand) GetType() DataType { - return DTCommand -} - -func (d DataEvent) GetType() DataType { - return DTEvent -} - type ClientDataType int const ( @@ -136,8 +108,7 @@ func ParseDataType(token json.Token) (DataType, error) { d := fmt.Sprintf("%.0f", token) val, err := strconv.ParseInt(d, 10, 32) if err != nil { - fmt.Printf("Invalid data type value: %q\n", d) - return DTInvalid, err + return DTInvalid, fmt.Errorf("invalid data type value: %q\n", d) } return DataType(val), nil } @@ -224,46 +195,45 @@ func (de DataCommand) HTML() string { } func EncodeMessage(name, color, msg string, msgtype MessageType) (string, error) { - d := ChatData{ - Type: DTChat, - Data: DataMessage{ - From: name, - Color: color, - Message: msg, - Type: msgtype, - }, + d, err := newChatData(false, DTChat, DataMessage{ + From: name, + Color: color, + Message: msg, + Type: msgtype, + }) + if err != nil { + return "", err } - j, err := jsonifyChatData(d) - return j, err + return jsonifyChatData(d) } func EncodeError(message string) (string, error) { - d := ChatData{ - Type: DTError, - Data: DataError{Message: message}, + d, err := newChatData(false, DTError, DataError{Message: message}) + if err != nil { + return "", err } return jsonifyChatData(d) } func EncodeCommand(command CommandType, args []string) (string, error) { - d := ChatData{ - Type: DTCommand, - Data: DataCommand{ - Command: command, - Arguments: args, - }, + d, err := newChatData(false, DTCommand, DataCommand{ + Command: command, + Arguments: args, + }) + if err != nil { + return "", err } return jsonifyChatData(d) } func EncodeEvent(event EventType, name, color string) (string, error) { - d := ChatData{ - Type: DTEvent, - Data: DataEvent{ - Event: event, - User: name, - Color: color, - }, + d, err := newChatData(false, DTEvent, DataEvent{ + Event: event, + User: name, + Color: color, + }) + if err != nil { + return "", err } return jsonifyChatData(d) } @@ -276,64 +246,8 @@ func jsonifyChatData(data ChatData) (string, error) { return string(raw), nil } -func DecodeData(rawjson string) (DataInterface, error) { - data := ChatData{} - decoder := json.NewDecoder(strings.NewReader(rawjson)) - - // Open bracket - _, err := decoder.Token() - if err != nil { - return nil, fmt.Errorf("Open bracket token error: %s", err) - } - - for decoder.More() { - key, err := decoder.Token() - if err != nil { - return nil, fmt.Errorf("Error decoding token: %s", err) - } - - if key.(string) == "Type" { - value, err := decoder.Token() - if err != nil { - return nil, fmt.Errorf("Error decoding data value: %q", err) - } - - data.Type, err = ParseDataType(value) - if err != nil { - return nil, fmt.Errorf("Error parsing data type: %d", data.Type) - } - } else { - - switch DataType(data.Type) { - case DTChat: - d := DataMessage{} - if err := decoder.Decode(&d); err != nil { - return nil, fmt.Errorf("Unable to decode DataMessage: %s", err) - } - return d, nil - case DTError: - d := DataError{} - if err := decoder.Decode(&d); err != nil { - return nil, fmt.Errorf("Unable to decode DataError: %s", err) - } - return d, nil - case DTCommand: - d := DataCommand{} - if err := decoder.Decode(&d); err != nil { - return nil, fmt.Errorf("Unable to decode DataCommand: %s", err) - } - return d, nil - case DTEvent: - d := DataEvent{} - if err := decoder.Decode(&d); err != nil { - return nil, fmt.Errorf("Unable to decode DataEvent: %s", err) - } - return d, nil - default: - return nil, fmt.Errorf("Invalid data type: %d", data.Type) - } - - } - } - return nil, fmt.Errorf("Incomplete data") +func DecodeData(rawjson string) (ChatData, error) { + var data ChatData + err := json.Unmarshal([]byte(rawjson), &data) + return data, err } diff --git a/handlers.go b/handlers.go index 1612bf4..12750ce 100644 --- a/handlers.go +++ b/handlers.go @@ -9,6 +9,8 @@ import ( "strings" "sync" + "github.com/zorchenhimer/MovieNight/common" + "github.com/gorilla/websocket" "github.com/nareix/joy4/av/avutil" "github.com/nareix/joy4/av/pubsub" @@ -106,15 +108,15 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { //first message has to be the name // loop through name since websocket is opened once for client == nil { - _, msg, err := conn.ReadMessage() + var data common.ClientData + err := conn.ReadJSON(&data) if err != nil { fmt.Printf("[handler] Client closed connection: %s\n", conn.RemoteAddr().String()) conn.Close() return } - name := string(msg) - client, err = chat.Join(name, uid) + client, err = chat.Join(data.Message, uid) if err != nil { switch err.(type) { case UserFormatError, UserTakenError: @@ -133,12 +135,13 @@ func wsHandler(w http.ResponseWriter, r *http.Request) { //then watch for incoming messages for { - _, msg, err := conn.ReadMessage() + var data common.ClientData + err := conn.ReadJSON(&data) if err != nil { //if error then assuming that the connection is closed client.Exit() return } - client.NewMsg(string(msg)) + client.NewMsg(data) } }() @@ -173,6 +176,9 @@ func handleIndexTemplate(w http.ResponseWriter, r *http.Request) { data.Title += " - video" } + // Force browser to replace cache since file was not changed + w.Header().Set("Cache-Control", "no-cache, must-revalidate") + err = t.Execute(w, data) if err != nil { fmt.Printf("[ERR] could not execute file, %v", err) diff --git a/wasm/main_wasm.go b/wasm/main_wasm.go index 75e03b0..076764c 100644 --- a/wasm/main_wasm.go +++ b/wasm/main_wasm.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "time" @@ -10,20 +11,31 @@ import ( func recieve(v []js.Value) { if len(v) == 0 { - fmt.Printf("No data received") + fmt.Println("No data received") return } - data, err := common.DecodeData(v[0].String()) + chat, err := common.DecodeData(v[0].String()) if err != nil { fmt.Printf("Error decoding data: %s\n", err) js.Call("appendMessages", v) return } - switch data.GetType() { - case common.DTChat, common.DTError, common.DTEvent: - js.Call("appendMessages", data.HTML()) + data, err := chat.GetData() + if err != nil { + fmt.Printf("Error parsing DataInterface: %v", err) + js.Call("appendMessages", v) + } + + switch chat.Type { + case common.DTEvent: + // on join or leave, update list of possible user names + fallthrough + case common.DTChat, common.DTError: + if !chat.Hidden { + js.Call("appendMessages", data.HTML()) + } case common.DTCommand: dc := data.(common.DataCommand) @@ -43,25 +55,52 @@ func recieve(v []js.Value) { case common.CmdPurgeChat: fmt.Println("//TODO: chat purge command received.") case common.CmdHelp: - js.Call("appendMesages", data.HTML()) + if !chat.Hidden { + js.Call("appendMessages", data.HTML()) + } // TODO: open window //js.Call("") } - return } } -func send(v []js.Value) { - if len(v) != 1 { - fmt.Printf("expected 1 parameter, got %d", len(v)) - return +func websocketSend(msg string, dataType common.ClientDataType) error { + data, err := json.Marshal(common.ClientData{ + Type: dataType, + Message: msg, + }) + if err != nil { + return fmt.Errorf("could not marshal data: %v", err) + } + + js.Call("websocketSend", string(data)) + return nil +} + +func send(this js.Value, v []js.Value) interface{} { + if len(v) != 1 { + showSendError(fmt.Errorf("expected 1 parameter, got %d", len(v))) + return false + } + + err := websocketSend(v[0].String(), common.CdMessage) + if err != nil { + showSendError(err) + return false + } + return true +} + +func showSendError(err error) { + if err != nil { + fmt.Printf("Could not send: %v\n", err) + js.Call("appendMessages", `
Could not send message
`) } - js.Call("websocketSend", v) } func main() { js.Set("recieveMessage", js.CallbackOf(recieve)) - js.Set("sendMessage", js.CallbackOf(send)) + js.Set("sendMessage", js.FuncOf(send)) // This is needed so the goroutine does not end for {