// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. package model import ( "encoding/json" "fmt" "io" ) const ( WebsocketEventTyping = "typing" WebsocketEventPosted = "posted" WebsocketEventPostEdited = "post_edited" WebsocketEventPostDeleted = "post_deleted" WebsocketEventPostUnread = "post_unread" WebsocketEventChannelConverted = "channel_converted" WebsocketEventChannelCreated = "channel_created" WebsocketEventChannelDeleted = "channel_deleted" WebsocketEventChannelRestored = "channel_restored" WebsocketEventChannelUpdated = "channel_updated" WebsocketEventChannelMemberUpdated = "channel_member_updated" WebsocketEventChannelSchemeUpdated = "channel_scheme_updated" WebsocketEventDirectAdded = "direct_added" WebsocketEventGroupAdded = "group_added" WebsocketEventNewUser = "new_user" WebsocketEventAddedToTeam = "added_to_team" WebsocketEventLeaveTeam = "leave_team" WebsocketEventUpdateTeam = "update_team" WebsocketEventDeleteTeam = "delete_team" WebsocketEventRestoreTeam = "restore_team" WebsocketEventUpdateTeamScheme = "update_team_scheme" WebsocketEventUserAdded = "user_added" WebsocketEventUserUpdated = "user_updated" WebsocketEventUserRoleUpdated = "user_role_updated" WebsocketEventMemberroleUpdated = "memberrole_updated" WebsocketEventUserRemoved = "user_removed" WebsocketEventPreferenceChanged = "preference_changed" WebsocketEventPreferencesChanged = "preferences_changed" WebsocketEventPreferencesDeleted = "preferences_deleted" WebsocketEventEphemeralMessage = "ephemeral_message" WebsocketEventStatusChange = "status_change" WebsocketEventHello = "hello" WebsocketAuthenticationChallenge = "authentication_challenge" WebsocketEventReactionAdded = "reaction_added" WebsocketEventReactionRemoved = "reaction_removed" WebsocketEventResponse = "response" WebsocketEventEmojiAdded = "emoji_added" WebsocketEventChannelViewed = "channel_viewed" WebsocketEventPluginStatusesChanged = "plugin_statuses_changed" WebsocketEventPluginEnabled = "plugin_enabled" WebsocketEventPluginDisabled = "plugin_disabled" WebsocketEventRoleUpdated = "role_updated" WebsocketEventLicenseChanged = "license_changed" WebsocketEventConfigChanged = "config_changed" WebsocketEventOpenDialog = "open_dialog" WebsocketEventGuestsDeactivated = "guests_deactivated" WebsocketEventUserActivationStatusChange = "user_activation_status_change" WebsocketEventReceivedGroup = "received_group" WebsocketEventReceivedGroupAssociatedToTeam = "received_group_associated_to_team" WebsocketEventReceivedGroupNotAssociatedToTeam = "received_group_not_associated_to_team" WebsocketEventReceivedGroupAssociatedToChannel = "received_group_associated_to_channel" WebsocketEventReceivedGroupNotAssociatedToChannel = "received_group_not_associated_to_channel" WebsocketEventSidebarCategoryCreated = "sidebar_category_created" WebsocketEventSidebarCategoryUpdated = "sidebar_category_updated" WebsocketEventSidebarCategoryDeleted = "sidebar_category_deleted" WebsocketEventSidebarCategoryOrderUpdated = "sidebar_category_order_updated" WebsocketWarnMetricStatusReceived = "warn_metric_status_received" WebsocketWarnMetricStatusRemoved = "warn_metric_status_removed" WebsocketEventCloudPaymentStatusUpdated = "cloud_payment_status_updated" WebsocketEventThreadUpdated = "thread_updated" WebsocketEventThreadFollowChanged = "thread_follow_changed" WebsocketEventThreadReadChanged = "thread_read_changed" WebsocketFirstAdminVisitMarketplaceStatusReceived = "first_admin_visit_marketplace_status_received" ) type WebSocketMessage interface { ToJSON() ([]byte, error) IsValid() bool EventType() string } type WebsocketBroadcast struct { OmitUsers map[string]bool `json:"omit_users"` // broadcast is omitted for users listed here UserId string `json:"user_id"` // broadcast only occurs for this user ChannelId string `json:"channel_id"` // broadcast only occurs for users in this channel TeamId string `json:"team_id"` // broadcast only occurs for users in this team ContainsSanitizedData bool `json:"-"` ContainsSensitiveData bool `json:"-"` } func (wb *WebsocketBroadcast) copy() *WebsocketBroadcast { if wb == nil { return nil } var c WebsocketBroadcast if wb.OmitUsers != nil { c.OmitUsers = make(map[string]bool, len(wb.OmitUsers)) for k, v := range wb.OmitUsers { c.OmitUsers[k] = v } } c.UserId = wb.UserId c.ChannelId = wb.ChannelId c.TeamId = wb.TeamId c.ContainsSanitizedData = wb.ContainsSanitizedData c.ContainsSensitiveData = wb.ContainsSensitiveData return &c } type precomputedWebSocketEventJSON struct { Event json.RawMessage Data json.RawMessage Broadcast json.RawMessage } func (p *precomputedWebSocketEventJSON) copy() *precomputedWebSocketEventJSON { if p == nil { return nil } var c precomputedWebSocketEventJSON if p.Event != nil { c.Event = make([]byte, len(p.Event)) copy(c.Event, p.Event) } if p.Data != nil { c.Data = make([]byte, len(p.Data)) copy(c.Data, p.Data) } if p.Broadcast != nil { c.Broadcast = make([]byte, len(p.Broadcast)) copy(c.Broadcast, p.Broadcast) } return &c } // 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"` } type WebSocketEvent struct { event string data map[string]interface{} broadcast *WebsocketBroadcast sequence int64 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 (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 (ev *WebSocketEvent) Add(key string, value interface{}) { ev.data[key] = value } func NewWebSocketEvent(event, teamId, channelId, userId string, omitUsers map[string]bool) *WebSocketEvent { return &WebSocketEvent{ event: event, data: make(map[string]interface{}), broadcast: &WebsocketBroadcast{ TeamId: teamId, ChannelId: channelId, UserId: userId, OmitUsers: omitUsers}, } } func (ev *WebSocketEvent) Copy() *WebSocketEvent { copy := &WebSocketEvent{ event: ev.event, data: ev.data, broadcast: ev.broadcast, sequence: ev.sequence, precomputedJSON: ev.precomputedJSON, } return copy } func (ev *WebSocketEvent) DeepCopy() *WebSocketEvent { var dataCopy map[string]interface{} if ev.data != nil { dataCopy = make(map[string]interface{}, len(ev.data)) for k, v := range ev.data { dataCopy[k] = v } } copy := &WebSocketEvent{ event: ev.event, data: dataCopy, broadcast: ev.broadcast.copy(), sequence: ev.sequence, precomputedJSON: ev.precomputedJSON.copy(), } 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() ([]byte, error) { if ev.precomputedJSON != nil { return []byte(fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.GetSequence())), nil } return json.Marshal(webSocketEventJSON{ ev.event, ev.data, ev.broadcast, ev.sequence, }) } // Encode encodes the event to the given encoder. func (ev *WebSocketEvent) Encode(enc *json.Encoder) error { if ev.precomputedJSON != nil { return enc.Encode(json.RawMessage( fmt.Sprintf(`{"event": %s, "data": %s, "broadcast": %s, "seq": %d}`, ev.precomputedJSON.Event, ev.precomputedJSON.Data, ev.precomputedJSON.Broadcast, ev.sequence), )) } return enc.Encode(webSocketEventJSON{ ev.event, ev.data, ev.broadcast, ev.sequence, }) } func WebSocketEventFromJSON(data io.Reader) (*WebSocketEvent, error) { var ev WebSocketEvent var o webSocketEventJSON if err := json.NewDecoder(data).Decode(&o); err != nil { return nil, err } ev.event = o.Event if u, ok := o.Data["user"]; ok { // We need to convert to and from JSON again // because the user is in the form of a map[string]interface{}. buf, err := json.Marshal(u) if err != nil { return nil, err } var user User if err = json.Unmarshal(buf, &user); err != nil { return nil, err } o.Data["user"] = &user } ev.data = o.Data ev.broadcast = o.Broadcast ev.sequence = o.Sequence return &ev, nil } // WebSocketResponse represents a response received through the WebSocket // for a request made to the server. This is available through the ResponseChannel // channel in WebSocketClient. type WebSocketResponse struct { Status string `json:"status"` // The status of the response. For example: OK, FAIL. SeqReply int64 `json:"seq_reply,omitempty"` // A counter which is incremented for every response sent. Data map[string]interface{} `json:"data,omitempty"` // The data contained in the response. Error *AppError `json:"error,omitempty"` // A field that is set if any error has occurred. } func (m *WebSocketResponse) Add(key string, value interface{}) { m.Data[key] = value } func NewWebSocketResponse(status string, seqReply int64, data map[string]interface{}) *WebSocketResponse { return &WebSocketResponse{Status: status, SeqReply: seqReply, Data: data} } func NewWebSocketError(seqReply int64, err *AppError) *WebSocketResponse { return &WebSocketResponse{Status: StatusFail, SeqReply: seqReply, Error: err} } func (m *WebSocketResponse) IsValid() bool { return m.Status != "" } func (m *WebSocketResponse) EventType() string { return WebsocketEventResponse } func (m *WebSocketResponse) ToJSON() ([]byte, error) { return json.Marshal(m) } func WebSocketResponseFromJSON(data io.Reader) (*WebSocketResponse, error) { var o *WebSocketResponse return o, json.NewDecoder(data).Decode(&o) }