diff --git a/api4/channel_test.go b/api4/channel_test.go index c59b76d684..2e4a8beeef 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -1367,7 +1367,7 @@ func TestConvertChannelToPrivate(t *testing.T) { for { select { case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.Data["channel_id"].(string) == publicChannel2.Id { + if resp.EventType() == model.WEBSOCKET_EVENT_CHANNEL_CONVERTED && resp.GetData()["channel_id"].(string) == publicChannel2.Id { eventHit = true } case <-stop: @@ -2426,7 +2426,7 @@ func TestRemoveChannelMember(t *testing.T) { }) wsr := <-wsClient.EventChannel - require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.Event) + require.Equal(t, model.WEBSOCKET_EVENT_HELLO, wsr.EventType()) // requirePost listens for websocket events and tries to find the post matching // the expected post's channel and message. @@ -2435,7 +2435,7 @@ func TestRemoveChannelMember(t *testing.T) { for { select { case event := <-wsClient.EventChannel: - postData, ok := event.Data["post"] + postData, ok := event.GetData()["post"] if !ok { continue } diff --git a/api4/plugin_test.go b/api4/plugin_test.go index 777d918dc8..b9b9baa718 100644 --- a/api4/plugin_test.go +++ b/api4/plugin_test.go @@ -315,7 +315,7 @@ func TestNotifyClusterPluginEvent(t *testing.T) { for { select { case resp := <-webSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.Data["plugin_statuses"].([]interface{})) == 0 { + if resp.EventType() == model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED && len(resp.GetData()["plugin_statuses"].([]interface{})) == 0 { done <- true return } diff --git a/api4/post_test.go b/api4/post_test.go index b5df1d0028..a7c213a5e5 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -487,7 +487,7 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) { for waiting { select { case event := <-WebSocketClient.EventChannel: - require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.Event, "should not have ephemeral message event") + require.NotEqual(t, model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE, event.EventType(), "should not have ephemeral message event") case <-timeout: waiting = false } @@ -506,12 +506,12 @@ func TestCreatePostSendOutOfChannelMentions(t *testing.T) { for waiting { select { case event := <-WebSocketClient.EventChannel: - if event.Event != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE { + if event.EventType() != model.WEBSOCKET_EVENT_EPHEMERAL_MESSAGE { // Ignore any other events continue } - wpost := model.PostFromJson(strings.NewReader(event.Data["post"].(string))) + wpost := model.PostFromJson(strings.NewReader(event.GetData()["post"].(string))) acm, ok := wpost.Props[model.PROPS_ADD_CHANNEL_MEMBER].(map[string]interface{}) require.True(t, ok, "should have received ephemeral post with 'add_channel_member' in props") diff --git a/api4/preference_test.go b/api4/preference_test.go index 61d2538253..62935d8ec6 100644 --- a/api4/preference_test.go +++ b/api4/preference_test.go @@ -259,6 +259,7 @@ func TestUpdatePreferencesWebsocket(t *testing.T) { Name: model.NewId(), }, } + _, resp := th.Client.UpdatePreferences(userId, preferences) CheckNoError(t, resp) @@ -268,12 +269,12 @@ func TestUpdatePreferencesWebsocket(t *testing.T) { for waiting { select { case event := <-WebSocketClient.EventChannel: - if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED { + if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_CHANGED { // Ignore any other events continue } - received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string))) + received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string))) require.NoError(t, err) for i, p := range *preferences { @@ -376,12 +377,12 @@ func TestDeletePreferencesWebsocket(t *testing.T) { for waiting { select { case event := <-WebSocketClient.EventChannel: - if event.Event != model.WEBSOCKET_EVENT_PREFERENCES_DELETED { + if event.EventType() != model.WEBSOCKET_EVENT_PREFERENCES_DELETED { // Ignore any other events continue } - received, err := model.PreferencesFromJson(strings.NewReader(event.Data["preferences"].(string))) + received, err := model.PreferencesFromJson(strings.NewReader(event.GetData()["preferences"].(string))) if err != nil { t.Fatal(err) } diff --git a/api4/user_test.go b/api4/user_test.go index d83ccb2794..4a41afaded 100644 --- a/api4/user_test.go +++ b/api4/user_test.go @@ -323,11 +323,11 @@ func TestCreateUserWebSocketEvent(t *testing.T) { for { select { case ev := <-userWSClient.EventChannel: - if ev.Event == model.WEBSOCKET_EVENT_NEW_USER { + if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER { userHasReceived = true } case ev := <-guestWSClient.EventChannel: - if ev.Event == model.WEBSOCKET_EVENT_NEW_USER { + if ev.EventType() == model.WEBSOCKET_EVENT_NEW_USER { guestHasReceived = true } case <-time.After(2 * time.Second): @@ -1675,7 +1675,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e select { case resp, ok := <-client.EventChannel: require.Truef(t, ok, "channel closed before receiving expected event %s", model.WEBSOCKET_EVENT_USER_UPDATED) - if resp.Event == model.WEBSOCKET_EVENT_USER_UPDATED { + if resp.EventType() == model.WEBSOCKET_EVENT_USER_UPDATED { test(resp) return } @@ -1687,7 +1687,7 @@ func assertExpectedWebsocketEvent(t *testing.T, client *model.WebSocketClient, e func assertWebsocketEventUserUpdatedWithEmail(t *testing.T, client *model.WebSocketClient, email string) { assertExpectedWebsocketEvent(t, client, model.WEBSOCKET_EVENT_USER_UPDATED, func(event *model.WebSocketEvent) { - eventUser, ok := event.Data["user"].(map[string]interface{}) + eventUser, ok := event.GetData()["user"].(map[string]interface{}) require.True(t, ok, "expected user") userEmail, ok := eventUser["email"].(string) require.Truef(t, ok, "expected email %s, but got nil", email) diff --git a/api4/websocket_test.go b/api4/websocket_test.go index c4cf14570f..c9eb1a5688 100644 --- a/api4/websocket_test.go +++ b/api4/websocket_test.go @@ -115,7 +115,7 @@ func TestWebSocketEvent(t *testing.T) { for { select { case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING && resp.Data["user_id"].(string) == "somerandomid" { + if resp.EventType() == model.WEBSOCKET_EVENT_TYPING && resp.GetData()["user_id"].(string) == "somerandomid" { eventHit = true } case <-stop: @@ -140,7 +140,7 @@ func TestWebSocketEvent(t *testing.T) { for { select { case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_TYPING { + if resp.EventType() == model.WEBSOCKET_EVENT_TYPING { eventHit = true } case <-stop: @@ -180,7 +180,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) { require.Equal(t, resp.Status, model.STATUS_OK, "should have responded OK to authentication challenge") wsr := <-WebSocketClient.EventChannel - require.Equal(t, wsr.Event, model.WEBSOCKET_EVENT_HELLO, "missing hello") + require.Equal(t, wsr.EventType(), model.WEBSOCKET_EVENT_HELLO, "missing hello") stop := make(chan bool) count := 0 @@ -189,7 +189,7 @@ func TestCreateDirectChannelWithSocket(t *testing.T) { for { select { case wsr := <-WebSocketClient.EventChannel: - if wsr != nil && wsr.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED { + if wsr != nil && wsr.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED { count = count + 1 } @@ -378,8 +378,8 @@ func TestWebSocketStatuses(t *testing.T) { for { select { case resp := <-WebSocketClient.EventChannel: - if resp.Event == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.Data["user_id"].(string) == th.BasicUser.Id { - status := resp.Data["status"].(string) + if resp.EventType() == model.WEBSOCKET_EVENT_STATUS_CHANGE && resp.GetData()["user_id"].(string) == th.BasicUser.Id { + status := resp.GetData()["status"].(string) if status == model.STATUS_ONLINE { onlineHit = true } else if status == model.STATUS_AWAY { diff --git a/app/plugin_api.go b/app/plugin_api.go index 4850572fc8..d2219686d6 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -734,11 +734,9 @@ func (api *PluginAPI) KVList(page, perPage int) ([]string, *model.AppError) { } func (api *PluginAPI) PublishWebSocketEvent(event string, payload map[string]interface{}, broadcast *model.WebsocketBroadcast) { - api.app.Publish(&model.WebSocketEvent{ - Event: fmt.Sprintf("custom_%v_%v", api.id, event), - Data: payload, - Broadcast: broadcast, - }) + ev := model.NewWebSocketEvent(fmt.Sprintf("custom_%v_%v", api.id, event), "", "", "", nil) + ev = ev.SetBroadcast(broadcast).SetData(payload) + api.app.Publish(ev) } func (api *PluginAPI) HasPermissionTo(userId string, permission *model.Permission) bool { diff --git a/app/plugin_statuses.go b/app/plugin_statuses.go index 4efb413e2e..05b37303b2 100644 --- a/app/plugin_statuses.go +++ b/app/plugin_statuses.go @@ -79,7 +79,7 @@ func (a *App) notifyPluginStatusesChanged() error { // Notify any system admins. message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_PLUGIN_STATUSES_CHANGED, "", "", "", nil) message.Add("plugin_statuses", pluginStatuses) - message.Broadcast.ContainsSensitiveData = true + message.GetBroadcast().ContainsSensitiveData = true a.Publish(message) return nil diff --git a/app/user.go b/app/user.go index 3cbc9a3e36..d3c7f46caa 100644 --- a/app/user.go +++ b/app/user.go @@ -1112,13 +1112,13 @@ func (a *App) sendUpdatedUserEvent(user model.User) { a.SanitizeProfile(adminCopyOfUser, true) adminMessage := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) adminMessage.Add("user", &adminCopyOfUser) - adminMessage.Broadcast.ContainsSensitiveData = true + adminMessage.GetBroadcast().ContainsSensitiveData = true a.Publish(adminMessage) a.SanitizeProfile(&user, false) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_UPDATED, "", "", "", nil) message.Add("user", &user) - message.Broadcast.ContainsSanitizedData = true + message.GetBroadcast().ContainsSanitizedData = true a.Publish(message) } diff --git a/app/web_conn.go b/app/web_conn.go index c66d9f4f84..b19905aac2 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -185,7 +185,7 @@ func (c *WebConn) writePump() { "websocket.slow: dropping message", mlog.String("user_id", c.UserId), mlog.String("type", msg.EventType()), - mlog.String("channel_id", evt.Broadcast.ChannelId), + mlog.String("channel_id", evt.GetBroadcast().ChannelId), ) skipSend = true } @@ -194,9 +194,7 @@ func (c *WebConn) writePump() { if !skipSend { var msgBytes []byte if evtOk { - cpyEvt := &model.WebSocketEvent{} - *cpyEvt = *evt - cpyEvt.Sequence = c.Sequence + cpyEvt := evt.SetSequence(c.Sequence) msgBytes = []byte(cpyEvt.ToJson()) c.Sequence++ } else { @@ -209,7 +207,7 @@ func (c *WebConn) writePump() { "websocket.full", mlog.String("user_id", c.UserId), mlog.String("type", msg.EventType()), - mlog.String("channel_id", evt.Broadcast.ChannelId), + mlog.String("channel_id", evt.GetBroadcast().ChannelId), mlog.Int("size", len(msg.ToJson())), ) } else { @@ -305,11 +303,11 @@ func (webCon *WebConn) shouldSendEventToGuest(msg *model.WebSocketEvent) bool { var userId string var canSee bool - switch msg.Event { + switch msg.EventType() { case model.WEBSOCKET_EVENT_USER_UPDATED: - userId = msg.Data["user"].(*model.User).Id + userId = msg.GetData()["user"].(*model.User).Id case model.WEBSOCKET_EVENT_NEW_USER: - userId = msg.Data["user_id"].(string) + userId = msg.GetData()["user_id"].(string) default: return true } @@ -332,7 +330,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { // If the event contains sanitized data, only send to users that don't have permission to // see sensitive data. Prevents admin clients from receiving events with bad data var hasReadPrivateDataPermission *bool - if msg.Broadcast.ContainsSanitizedData { + if msg.GetBroadcast().ContainsSanitizedData { hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id)) if *hasReadPrivateDataPermission { @@ -341,7 +339,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { } // If the event contains sensitive data, only send to users with permission to see it - if msg.Broadcast.ContainsSensitiveData { + if msg.GetBroadcast().ContainsSensitiveData { if hasReadPrivateDataPermission == nil { hasReadPrivateDataPermission = model.NewBool(webCon.App.RolesGrantPermission(webCon.GetSession().GetUserRoles(), model.PERMISSION_MANAGE_SYSTEM.Id)) } @@ -352,19 +350,19 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { } // If the event is destined to a specific user - if len(msg.Broadcast.UserId) > 0 { - return webCon.UserId == msg.Broadcast.UserId + if len(msg.GetBroadcast().UserId) > 0 { + return webCon.UserId == msg.GetBroadcast().UserId } // if the user is omitted don't send the message - if len(msg.Broadcast.OmitUsers) > 0 { - if _, ok := msg.Broadcast.OmitUsers[webCon.UserId]; ok { + if len(msg.GetBroadcast().OmitUsers) > 0 { + if _, ok := msg.GetBroadcast().OmitUsers[webCon.UserId]; ok { return false } } // Only report events to users who are in the channel for the event - if len(msg.Broadcast.ChannelId) > 0 { + if len(msg.GetBroadcast().ChannelId) > 0 { if model.GetMillis()-webCon.LastAllChannelMembersTime > WEBCONN_MEMBER_CACHE_TIME { webCon.AllChannelMembers = nil webCon.LastAllChannelMembersTime = 0 @@ -380,15 +378,15 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { webCon.LastAllChannelMembersTime = model.GetMillis() } - if _, ok := webCon.AllChannelMembers[msg.Broadcast.ChannelId]; ok { + if _, ok := webCon.AllChannelMembers[msg.GetBroadcast().ChannelId]; ok { return true } return false } // Only report events to users who are in the team for the event - if len(msg.Broadcast.TeamId) > 0 { - return webCon.IsMemberOfTeam(msg.Broadcast.TeamId) + if len(msg.GetBroadcast().TeamId) > 0 { + return webCon.IsMemberOfTeam(msg.GetBroadcast().TeamId) } if webCon.GetSession().Props[model.SESSION_PROP_IS_GUEST] == "true" { diff --git a/app/web_conn_test.go b/app/web_conn_test.go index 0ce996941b..fcb036e24f 100644 --- a/app/web_conn_test.go +++ b/app/web_conn_test.go @@ -72,9 +72,9 @@ func TestWebConnShouldSendEvent(t *testing.T) { // needs more cases to get full coverage } - event := &model.WebSocketEvent{Event: "some_event"} + event := model.NewWebSocketEvent("some_event", "", "", "", nil) for _, c := range cases { - event.Broadcast = c.Broadcast + event = event.SetBroadcast(c.Broadcast) assert.Equal(t, c.User1Expected, basicUserWc.ShouldSendEvent(event), c.Description) assert.Equal(t, c.User2Expected, basicUser2Wc.ShouldSendEvent(event), c.Description) assert.Equal(t, c.AdminExpected, adminUserWc.ShouldSendEvent(event), c.Description) diff --git a/app/web_hub.go b/app/web_hub.go index 9dbc2b18c1..204d3ffbc9 100644 --- a/app/web_hub.go +++ b/app/web_hub.go @@ -164,7 +164,7 @@ func (a *App) HubUnregister(webConn *WebConn) { func (a *App) Publish(message *model.WebSocketEvent) { if metrics := a.Metrics; metrics != nil { - metrics.IncrementWebsocketEvent(message.Event) + metrics.IncrementWebsocketEvent(message.EventType()) } a.PublishSkipClusterSend(message) @@ -176,11 +176,11 @@ func (a *App) Publish(message *model.WebSocketEvent) { Data: message.ToJson(), } - if message.Event == model.WEBSOCKET_EVENT_POSTED || - message.Event == model.WEBSOCKET_EVENT_POST_EDITED || - message.Event == model.WEBSOCKET_EVENT_DIRECT_ADDED || - message.Event == model.WEBSOCKET_EVENT_GROUP_ADDED || - message.Event == model.WEBSOCKET_EVENT_ADDED_TO_TEAM { + if message.EventType() == model.WEBSOCKET_EVENT_POSTED || + message.EventType() == model.WEBSOCKET_EVENT_POST_EDITED || + message.EventType() == model.WEBSOCKET_EVENT_DIRECT_ADDED || + message.EventType() == model.WEBSOCKET_EVENT_GROUP_ADDED || + message.EventType() == model.WEBSOCKET_EVENT_ADDED_TO_TEAM { cm.SendType = model.CLUSTER_SEND_RELIABLE } @@ -189,8 +189,8 @@ func (a *App) Publish(message *model.WebSocketEvent) { } func (a *App) PublishSkipClusterSend(message *model.WebSocketEvent) { - if message.Broadcast.UserId != "" { - hub := a.GetHubForUserId(message.Broadcast.UserId) + if message.GetBroadcast().UserId != "" { + hub := a.GetHubForUserId(message.GetBroadcast().UserId) if hub != nil { hub.Broadcast(message) } @@ -485,10 +485,10 @@ func (h *Hub) Start() { } case msg := <-h.broadcast: candidates := connections.All() - if msg.Broadcast.UserId != "" { - candidates = connections.ForUser(msg.Broadcast.UserId) + if msg.GetBroadcast().UserId != "" { + candidates = connections.ForUser(msg.GetBroadcast().UserId) } - msg.PrecomputeJSON() + msg = msg.PrecomputeJSON() for _, webCon := range candidates { if webCon.ShouldSendEvent(msg) { select { diff --git a/app/web_hub_test.go b/app/web_hub_test.go index c145cca228..aa5faff1b0 100644 --- a/app/web_hub_test.go +++ b/app/web_hub_test.go @@ -91,7 +91,7 @@ func TestHubStopRaceCondition(t *testing.T) { hub.UpdateActivity("userId", "sessionToken", 0) for i := 0; i <= BROADCAST_QUEUE_SIZE; i++ { - hub.Broadcast(&model.WebSocketEvent{}) + hub.Broadcast(model.NewWebSocketEvent("", "", "", "", nil)) } hub.InvalidateUser("userId") diff --git a/model/websocket_client.go b/model/websocket_client.go index f31431ec1f..8d9a8330e8 100644 --- a/model/websocket_client.go +++ b/model/websocket_client.go @@ -4,6 +4,7 @@ package model import ( + "bytes" "encoding/json" "net/http" "time" @@ -121,9 +122,9 @@ func (wsc *WebSocketClient) Listen() { return } - var event WebSocketEvent - if err := json.Unmarshal(rawMsg, &event); err == nil && event.IsValid() { - wsc.EventChannel <- &event + event := WebSocketEventFromJson(bytes.NewReader(rawMsg)) + if event.IsValid() { + wsc.EventChannel <- event continue } diff --git a/model/websocket_message.go b/model/websocket_message.go index 585280c888..bcf0f62775 100644 --- a/model/websocket_message.go +++ b/model/websocket_message.go @@ -76,30 +76,41 @@ type precomputedWebSocketEventJSON struct { Broadcast json.RawMessage } -type WebSocketEvent struct { +// webSocketEventJSON mirrors WebSocketEvent to make some of its unexported fields serializable +type webSocketEventJSON struct { Event string `json:"event"` Data map[string]interface{} `json:"data"` Broadcast *WebsocketBroadcast `json:"broadcast"` Sequence int64 `json:"seq"` +} +// **NOTE**: Direct access to WebSocketEvent fields is deprecated. They will be +// made unexported in next major version release. Provided getter functions should be used instead. +type WebSocketEvent struct { + Event string // Deprecated: use EventType() + Data map[string]interface{} // Deprecated: use GetData() + Broadcast *WebsocketBroadcast // Deprecated: use GetBroadcast() + Sequence int64 // Deprecated: use GetSequence() precomputedJSON *precomputedWebSocketEventJSON } // PrecomputeJSON precomputes and stores the serialized JSON for all fields other than Sequence. // This makes ToJson much more efficient when sending the same event to multiple connections. -func (m *WebSocketEvent) PrecomputeJSON() { - event, _ := json.Marshal(m.Event) - data, _ := json.Marshal(m.Data) - broadcast, _ := json.Marshal(m.Broadcast) - m.precomputedJSON = &precomputedWebSocketEventJSON{ +func (ev *WebSocketEvent) PrecomputeJSON() *WebSocketEvent { + copy := ev.Copy() + event, _ := json.Marshal(copy.Event) + data, _ := json.Marshal(copy.Data) + broadcast, _ := json.Marshal(copy.Broadcast) + copy.precomputedJSON = &precomputedWebSocketEventJSON{ Event: json.RawMessage(event), Data: json.RawMessage(data), Broadcast: json.RawMessage(broadcast), } + return copy } -func (m *WebSocketEvent) Add(key string, value interface{}) { - m.Data[key] = value +func (ev *WebSocketEvent) Add(key string, value interface{}) { + ev.Data[key] = value } func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { @@ -107,26 +118,85 @@ func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[st Broadcast: &WebsocketBroadcast{TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}} } -func (o *WebSocketEvent) IsValid() bool { - return o.Event != "" -} - -func (o *WebSocketEvent) EventType() string { - return o.Event -} - -func (o *WebSocketEvent) ToJson() string { - if o.precomputedJSON != nil { - return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, o.precomputedJSON.Event, o.precomputedJSON.Data, o.precomputedJSON.Broadcast, o.Sequence) +func (ev *WebSocketEvent) Copy() *WebSocketEvent { + copy := &WebSocketEvent{ + Event: ev.Event, + Data: ev.Data, + Broadcast: ev.Broadcast, + Sequence: ev.Sequence, + precomputedJSON: ev.precomputedJSON, } - b, _ := json.Marshal(o) + return copy +} + +func (ev *WebSocketEvent) GetData() map[string]interface{} { + return ev.Data +} + +func (ev *WebSocketEvent) GetBroadcast() *WebsocketBroadcast { + return ev.Broadcast +} + +func (ev *WebSocketEvent) GetSequence() int64 { + return ev.Sequence +} + +func (ev *WebSocketEvent) SetEvent(event string) *WebSocketEvent { + copy := ev.Copy() + copy.Event = event + return copy +} + +func (ev *WebSocketEvent) SetData(data map[string]interface{}) *WebSocketEvent { + copy := ev.Copy() + copy.Data = data + return copy +} + +func (ev *WebSocketEvent) SetBroadcast(broadcast *WebsocketBroadcast) *WebSocketEvent { + copy := ev.Copy() + copy.Broadcast = broadcast + return copy +} + +func (ev *WebSocketEvent) SetSequence(seq int64) *WebSocketEvent { + copy := ev.Copy() + copy.Sequence = seq + return copy +} + +func (ev *WebSocketEvent) IsValid() bool { + return ev.Event != "" +} + +func (ev *WebSocketEvent) EventType() string { + return ev.Event +} + +func (ev *WebSocketEvent) ToJson() string { + if ev.precomputedJSON != nil { + return fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.Sequence) + } + b, _ := json.Marshal(webSocketEventJSON{ + ev.Event, + ev.Data, + ev.Broadcast, + ev.Sequence, + }) return string(b) } func WebSocketEventFromJson(data io.Reader) *WebSocketEvent { - var o *WebSocketEvent - json.NewDecoder(data).Decode(&o) - return o + var ev WebSocketEvent + var o webSocketEventJSON + if err := json.NewDecoder(data).Decode(&o); err != nil { + return nil + } + ev.Event = o.Event + ev.Data = o.Data + ev.Broadcast = o.Broadcast + ev.Sequence = o.Sequence + return &ev } type WebSocketResponse struct { diff --git a/model/websocket_message_test.go b/model/websocket_message_test.go index 76d5811a9e..da17736f0f 100644 --- a/model/websocket_message_test.go +++ b/model/websocket_message_test.go @@ -17,14 +17,68 @@ func TestWebSocketEvent(t *testing.T) { json := m.ToJson() result := WebSocketEventFromJson(strings.NewReader(json)) - badresult := WebSocketEventFromJson(strings.NewReader("junk")) - require.Nil(t, badresult, "should not have parsed") - require.True(t, m.IsValid(), "should be valid") + require.Equal(t, m.GetBroadcast().TeamId, result.GetBroadcast().TeamId, "Ids do not match") + require.Equal(t, m.GetData()["RootId"], result.GetData()["RootId"], "Ids do not match") +} - require.Equal(t, m.Broadcast.TeamId, result.Broadcast.TeamId, "Ids do not match") +func TestWebSocketEventImmutable(t *testing.T) { + m := NewWebSocketEvent("some_event", NewId(), NewId(), NewId(), nil) - require.Equal(t, m.Data["RootId"], result.Data["RootId"], "Ids do not match") + new := m.SetEvent("new_event") + if new == m { + require.Fail(t, "pointers should not be the same") + } + require.NotEqual(t, m.Event, new.Event) + require.Equal(t, new.Event, "new_event") + require.Equal(t, new.Event, new.EventType()) + + new = m.SetSequence(45) + if new == m { + require.Fail(t, "pointers should not be the same") + } + require.NotEqual(t, m.Sequence, new.Sequence) + require.Equal(t, new.Sequence, int64(45)) + require.Equal(t, new.Sequence, new.GetSequence()) + + broadcast := &WebsocketBroadcast{} + new = m.SetBroadcast(broadcast) + if new == m { + require.Fail(t, "pointers should not be the same") + } + require.NotEqual(t, m.Broadcast, new.Broadcast) + require.Equal(t, new.Broadcast, broadcast) + require.Equal(t, new.Broadcast, new.GetBroadcast()) + + data := map[string]interface{}{ + "key": "val", + "key2": "val2", + } + new = m.SetData(data) + if new == m { + require.Fail(t, "pointers should not be the same") + } + require.NotEqual(t, m, new) + require.Equal(t, new.Data, data) + require.Equal(t, new.Data, new.GetData()) + + copy := m.Copy() + if copy == m { + require.Fail(t, "pointers should not be the same") + } + require.Equal(t, m, copy) +} + +func TestWebSocketEventFromJson(t *testing.T) { + ev := WebSocketEventFromJson(strings.NewReader("junk")) + require.Nil(t, ev, "should not have parsed") + data := `{"event": "test", "data": {"key": "val"}, "seq": 45, "broadcast": {"user_id": "userid"}}` + ev = WebSocketEventFromJson(strings.NewReader(data)) + require.NotNil(t, ev, "should have parsed") + require.Equal(t, ev.Event, "test") + require.Equal(t, ev.Sequence, int64(45)) + require.Equal(t, ev.Data, map[string]interface{}{"key": "val"}) + require.Equal(t, ev.Broadcast, &WebsocketBroadcast{UserId: "userid"}) } func TestWebSocketResponse(t *testing.T) { @@ -46,7 +100,7 @@ func TestWebSocketResponse(t *testing.T) { func TestWebSocketEvent_PrecomputeJSON(t *testing.T) { event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil) - event.Sequence = 7 + event = event.SetSequence(7) before := event.ToJson() event.PrecomputeJSON() @@ -60,7 +114,7 @@ var stringSink string func BenchmarkWebSocketEvent_ToJson(b *testing.B) { event := NewWebSocketEvent(WEBSOCKET_EVENT_POSTED, "foo", "bar", "baz", nil) for i := 0; i < 100; i++ { - event.Data[NewId()] = NewId() + event.GetData()[NewId()] = NewId() } b.Run("SerializedNTimes", func(b *testing.B) {