diff --git a/socketserver/internal/server/backend.go b/socketserver/internal/server/backend.go index d02baaeb..70c826b2 100644 --- a/socketserver/internal/server/backend.go +++ b/socketserver/internal/server/backend.go @@ -74,14 +74,16 @@ func HBackendPublishRequest(w http.ResponseWriter, r *http.Request) { cmd := formData.Get("cmd") json := formData.Get("args") - chat := formData.Get("chat") - watchChannel := formData.Get("channel") + channel := formData.Get("channel") + scope := formData.Get("scope") cm := ClientMessage{MessageID: -1, Command: Command(cmd), origArguments: json} var count int - if chat != "" { - count = PublishToChat(chat, cm) - } else if watchChannel != "" { - count = PublishToWatchers(watchChannel, cm) + if scope == "chat" { + count = PublishToChat(channel, cm) + } else if scope == "channel" { + count = PublishToWatchers(channel, cm) + } else if scope == "global" { + count = PublishToAll(cm) } else { w.WriteHeader(400) fmt.Fprint(w, "Need to specify either chat or channel") diff --git a/socketserver/internal/server/backlog.go b/socketserver/internal/server/backlog.go index abb4e431..82fd500c 100644 --- a/socketserver/internal/server/backlog.go +++ b/socketserver/internal/server/backlog.go @@ -1 +1,89 @@ package server + +import ( + "errors" + "fmt" + "net/http" +) + +// this value is just docs right now +var ServerInitiatedCommands = []string{ + /// Global updates & notices + "update_news", // timecache:global + "message", // timecache:global + "reload_ff", // timecache:global + + /// Emote updates + "reload_badges", // timecache:global + "set_badge", // timecache:multichat + "reload_set", // timecache:multichat + "load_set", // TODO what are the semantics of this? + + /// User auth + "do_authorize", // nocache:single + + /// Channel data + // extra emote sets included in the chat + "follow_sets", // mustcache:chat + // extra follow buttons below the stream + "follow_buttons", // mustcache:watching + // SRL race data + "srl_race", // cachelast:watching + + /// Chatter/viewer counts + "chatters", // cachelast:watching + "viewers", // cachelast:watching +} +var _ = ServerInitiatedCommands + +type BacklogCacheType int + +const ( + // This is not a cache type. + CacheTypeInvalid BacklogCacheType = iota + // This message cannot be cached. + CacheTypeNever + // Save the last 24 hours of this message. + // If a client indicates that it has reconnected, replay the messages sent after the disconnect. + CacheTypeTimestamps + // Save only the last copy of this message, and always send it when the backlog is requested. + CacheTypeLastOnly + // Save this backlog data to disk with its timestamp. + // Send it when the backlog is requested, or after a reconnect if it was updated. + CacheTypePersistent +) + +type MessageTargetType int + +const ( + // This is not a message target. + MsgTargetTypeInvalid MessageTargetType = iota + // This message is targeted to a single TODO(user or connection) + MsgTargetTypeSingle + // This message is targeted to all users in a chat + MsgTargetTypeChat + // This message is targeted to all users in multiple chats + MsgTargetTypeMultichat + // This message is targeted to all users watching a stream + MsgTargetTypeWatching + // This message is sent to all FFZ users. + MsgTargetTypeGlobal +) + +// Returned by BacklogCacheType.UnmarshalJSON() +var ErrorUnrecognizedCacheType = errors.New("Invalid value for cachetype") + +// Returned by MessageTargetType.UnmarshalJSON() +var ErrorUnrecognizedTargetType = errors.New("Invalid value for message target") + +// note: see types.go for methods on these + +func HBackendSaveBacklog(w http.ResponseWriter, r *http.Request) { + formData, err := UnsealRequest(r.Form) + if err != nil { + w.WriteHeader(403) + fmt.Fprintf(w, "Error: %v", err) + return + } + +} diff --git a/socketserver/internal/server/commands.go b/socketserver/internal/server/commands.go index ac5cae42..abbf01ff 100644 --- a/socketserver/internal/server/commands.go +++ b/socketserver/internal/server/commands.go @@ -54,6 +54,8 @@ func HandleHello(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (r client.ClientID = uuid.NewV4() } + SubscribeGlobal(client) + return ClientMessage{ Arguments: client.ClientID.String(), }, nil diff --git a/socketserver/internal/server/types.go b/socketserver/internal/server/types.go index 1c052ca1..5ce92c81 100644 --- a/socketserver/internal/server/types.go +++ b/socketserver/internal/server/types.go @@ -84,5 +84,120 @@ type ClientInfo struct { // Server-initiated messages should be sent here // Never nil. - MessageChannel chan <- ClientMessage + MessageChannel chan<- ClientMessage +} + +func (bct BacklogCacheType) Name() string { + switch bct { + case CacheTypeInvalid: + return "" + case CacheTypeNever: + return "never" + case CacheTypeTimestamps: + return "timed" + case CacheTypeLastOnly: + return "last" + case CacheTypePersistent: + return "persist" + } + panic("Invalid BacklogCacheType value") +} + +var CacheTypesByName = map[string]BacklogCacheType{ + "never": CacheTypeNever, + "timed": CacheTypeTimestamps, + "last": CacheTypeLastOnly, + "persist": CacheTypePersistent, +} + +func BacklogCacheTypeByName(name string) (bct BacklogCacheType) { + // CacheTypeInvalid is the zero value so it doesn't matter + bct, _ = CacheTypesByName[name] + return +} + +// Implements Stringer +func (bct BacklogCacheType) String() string { return bct.Name() } + +// Implements json.Marshaler +func (bct BacklogCacheType) MarshalJSON() ([]byte, error) { + return json.Marshal(bct.Name()) +} + +// Implements json.Unmarshaler +func (pbct *BacklogCacheType) UnmarshalJSON(data []byte) error { + var str string + err := json.Unmarshal(data, &str) + if err != nil { + return err + } + if str == "" { + *pbct = CacheTypeInvalid + return nil + } + val := BacklogCacheTypeByName(str) + if val != CacheTypeInvalid { + *pbct = val + return nil + } + return ErrorUnrecognizedCacheType +} + +func (mtt MessageTargetType) Name() string { + switch mtt { + case MsgTargetTypeInvalid: + return "" + case MsgTargetTypeSingle: + return "single" + case MsgTargetTypeChat: + return "chat" + case MsgTargetTypeMultichat: + return "multichat" + case MsgTargetTypeWatching: + return "channel" + case MsgTargetTypeGlobal: + return "global" + } + panic("Invalid MessageTargetType value") +} + +var TargetTypesByName = map[string]MessageTargetType{ + "single": MsgTargetTypeSingle, + "chat": MsgTargetTypeChat, + "multichat": MsgTargetTypeMultichat, + "channel": MsgTargetTypeWatching, + "global": MsgTargetTypeGlobal, +} + +func MessageTargetTypeByName(name string) (mtt MessageTargetType) { + // MsgTargetTypeInvalid is the zero value so it doesn't matter + mtt, _ = TargetTypesByName[name] + return +} + +// Implements Stringer +func (mtt MessageTargetType) String() string { return mtt.Name() } + +// Implements json.Marshaler +func (mtt MessageTargetType) MarshalJSON() ([]byte, error) { + return json.Marshal(mtt.Name()) +} + +// Implements json.Unmarshaler +func (pmtt *MessageTargetType) UnmarshalJSON(data []byte) error { + var str string + err := json.Unmarshal(data, &str) + if err != nil { + return err + } + if str == "" { + *pmtt = MsgTargetTypeInvalid + return nil + } + mtt := MessageTargetTypeByName(str) + if mtt != MsgTargetTypeInvalid { + *pmtt = mtt + return nil + } + return ErrorUnrecognizedTargetType }