snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
525
server/public/model/websocket_message.go
Normal file
525
server/public/model/websocket_message.go
Normal file
@@ -0,0 +1,525 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type WebsocketEventType string
|
||||
|
||||
const (
|
||||
WebsocketEventTyping WebsocketEventType = "typing"
|
||||
WebsocketEventPosted WebsocketEventType = "posted"
|
||||
WebsocketEventPostEdited WebsocketEventType = "post_edited"
|
||||
WebsocketEventPostDeleted WebsocketEventType = "post_deleted"
|
||||
WebsocketEventPostUnread WebsocketEventType = "post_unread"
|
||||
WebsocketEventChannelConverted WebsocketEventType = "channel_converted"
|
||||
WebsocketEventChannelCreated WebsocketEventType = "channel_created"
|
||||
WebsocketEventChannelDeleted WebsocketEventType = "channel_deleted"
|
||||
WebsocketEventChannelRestored WebsocketEventType = "channel_restored"
|
||||
WebsocketEventChannelUpdated WebsocketEventType = "channel_updated"
|
||||
WebsocketEventChannelMemberUpdated WebsocketEventType = "channel_member_updated"
|
||||
WebsocketEventChannelSchemeUpdated WebsocketEventType = "channel_scheme_updated"
|
||||
WebsocketEventDirectAdded WebsocketEventType = "direct_added"
|
||||
WebsocketEventGroupAdded WebsocketEventType = "group_added"
|
||||
WebsocketEventNewUser WebsocketEventType = "new_user"
|
||||
WebsocketEventAddedToTeam WebsocketEventType = "added_to_team"
|
||||
WebsocketEventLeaveTeam WebsocketEventType = "leave_team"
|
||||
WebsocketEventUpdateTeam WebsocketEventType = "update_team"
|
||||
WebsocketEventDeleteTeam WebsocketEventType = "delete_team"
|
||||
WebsocketEventRestoreTeam WebsocketEventType = "restore_team"
|
||||
WebsocketEventUpdateTeamScheme WebsocketEventType = "update_team_scheme"
|
||||
WebsocketEventUserAdded WebsocketEventType = "user_added"
|
||||
WebsocketEventUserUpdated WebsocketEventType = "user_updated"
|
||||
WebsocketEventUserRoleUpdated WebsocketEventType = "user_role_updated"
|
||||
WebsocketEventMemberroleUpdated WebsocketEventType = "memberrole_updated"
|
||||
WebsocketEventUserRemoved WebsocketEventType = "user_removed"
|
||||
WebsocketEventPreferenceChanged WebsocketEventType = "preference_changed"
|
||||
WebsocketEventPreferencesChanged WebsocketEventType = "preferences_changed"
|
||||
WebsocketEventPreferencesDeleted WebsocketEventType = "preferences_deleted"
|
||||
WebsocketEventEphemeralMessage WebsocketEventType = "ephemeral_message"
|
||||
WebsocketEventStatusChange WebsocketEventType = "status_change"
|
||||
WebsocketEventHello WebsocketEventType = "hello"
|
||||
WebsocketAuthenticationChallenge WebsocketEventType = "authentication_challenge"
|
||||
WebsocketEventReactionAdded WebsocketEventType = "reaction_added"
|
||||
WebsocketEventReactionRemoved WebsocketEventType = "reaction_removed"
|
||||
WebsocketEventResponse WebsocketEventType = "response"
|
||||
WebsocketEventEmojiAdded WebsocketEventType = "emoji_added"
|
||||
WebsocketEventMultipleChannelsViewed WebsocketEventType = "multiple_channels_viewed"
|
||||
WebsocketEventPluginStatusesChanged WebsocketEventType = "plugin_statuses_changed"
|
||||
WebsocketEventPluginEnabled WebsocketEventType = "plugin_enabled"
|
||||
WebsocketEventPluginDisabled WebsocketEventType = "plugin_disabled"
|
||||
WebsocketEventRoleUpdated WebsocketEventType = "role_updated"
|
||||
WebsocketEventLicenseChanged WebsocketEventType = "license_changed"
|
||||
WebsocketEventConfigChanged WebsocketEventType = "config_changed"
|
||||
WebsocketEventOpenDialog WebsocketEventType = "open_dialog"
|
||||
WebsocketEventGuestsDeactivated WebsocketEventType = "guests_deactivated"
|
||||
WebsocketEventUserActivationStatusChange WebsocketEventType = "user_activation_status_change"
|
||||
WebsocketEventReceivedGroup WebsocketEventType = "received_group"
|
||||
WebsocketEventReceivedGroupAssociatedToTeam WebsocketEventType = "received_group_associated_to_team"
|
||||
WebsocketEventReceivedGroupNotAssociatedToTeam WebsocketEventType = "received_group_not_associated_to_team"
|
||||
WebsocketEventReceivedGroupAssociatedToChannel WebsocketEventType = "received_group_associated_to_channel"
|
||||
WebsocketEventReceivedGroupNotAssociatedToChannel WebsocketEventType = "received_group_not_associated_to_channel"
|
||||
WebsocketEventGroupMemberDelete WebsocketEventType = "group_member_deleted"
|
||||
WebsocketEventGroupMemberAdd WebsocketEventType = "group_member_add"
|
||||
WebsocketEventSidebarCategoryCreated WebsocketEventType = "sidebar_category_created"
|
||||
WebsocketEventSidebarCategoryUpdated WebsocketEventType = "sidebar_category_updated"
|
||||
WebsocketEventSidebarCategoryDeleted WebsocketEventType = "sidebar_category_deleted"
|
||||
WebsocketEventSidebarCategoryOrderUpdated WebsocketEventType = "sidebar_category_order_updated"
|
||||
WebsocketEventCloudSubscriptionChanged WebsocketEventType = "cloud_subscription_changed"
|
||||
WebsocketEventThreadUpdated WebsocketEventType = "thread_updated"
|
||||
WebsocketEventThreadFollowChanged WebsocketEventType = "thread_follow_changed"
|
||||
WebsocketEventThreadReadChanged WebsocketEventType = "thread_read_changed"
|
||||
WebsocketFirstAdminVisitMarketplaceStatusReceived WebsocketEventType = "first_admin_visit_marketplace_status_received"
|
||||
WebsocketEventDraftCreated WebsocketEventType = "draft_created"
|
||||
WebsocketEventDraftUpdated WebsocketEventType = "draft_updated"
|
||||
WebsocketEventDraftDeleted WebsocketEventType = "draft_deleted"
|
||||
WebsocketEventAcknowledgementAdded WebsocketEventType = "post_acknowledgement_added"
|
||||
WebsocketEventAcknowledgementRemoved WebsocketEventType = "post_acknowledgement_removed"
|
||||
WebsocketEventPersistentNotificationTriggered WebsocketEventType = "persistent_notification_triggered"
|
||||
WebsocketEventHostedCustomerSignupProgressUpdated WebsocketEventType = "hosted_customer_signup_progress_updated"
|
||||
WebsocketEventChannelBookmarkCreated WebsocketEventType = "channel_bookmark_created"
|
||||
WebsocketEventChannelBookmarkUpdated WebsocketEventType = "channel_bookmark_updated"
|
||||
WebsocketEventChannelBookmarkDeleted WebsocketEventType = "channel_bookmark_deleted"
|
||||
WebsocketEventChannelBookmarkSorted WebsocketEventType = "channel_bookmark_sorted"
|
||||
WebsocketEventChannelAccessControlUpdated WebsocketEventType = "channel_access_control_updated"
|
||||
WebsocketEventPermissionPolicyUpdated WebsocketEventType = "permission_policy_updated"
|
||||
WebsocketEventTeamAccessControlUpdated WebsocketEventType = "team_access_control_updated"
|
||||
WebsocketPresenceIndicator WebsocketEventType = "presence"
|
||||
WebsocketPostedNotifyAck WebsocketEventType = "posted_notify_ack"
|
||||
WebsocketScheduledPostCreated WebsocketEventType = "scheduled_post_created"
|
||||
WebsocketScheduledPostUpdated WebsocketEventType = "scheduled_post_updated"
|
||||
WebsocketScheduledPostDeleted WebsocketEventType = "scheduled_post_deleted"
|
||||
WebsocketEventCPAFieldCreated WebsocketEventType = "custom_profile_attributes_field_created"
|
||||
WebsocketEventCPAFieldUpdated WebsocketEventType = "custom_profile_attributes_field_updated"
|
||||
WebsocketEventCPAFieldDeleted WebsocketEventType = "custom_profile_attributes_field_deleted"
|
||||
WebsocketEventCPAValuesUpdated WebsocketEventType = "custom_profile_attributes_values_updated"
|
||||
WebsocketContentFlaggingReportValueUpdated WebsocketEventType = "content_flagging_report_value_updated"
|
||||
WebsocketEventJobUpdated WebsocketEventType = "job_updated"
|
||||
WebsocketEventRecapUpdated WebsocketEventType = "recap_updated"
|
||||
WebsocketEventPostTranslationUpdated WebsocketEventType = "post_translation_updated"
|
||||
WebsocketEventPostRevealed WebsocketEventType = "post_revealed"
|
||||
WebsocketEventPostBurned WebsocketEventType = "post_burned"
|
||||
WebsocketEventBurnOnReadAllRevealed WebsocketEventType = "burn_on_read_all_revealed"
|
||||
|
||||
WebsocketEventBoardCreated WebsocketEventType = "board_created"
|
||||
|
||||
WebsocketEventViewCreated WebsocketEventType = "view_created"
|
||||
WebsocketEventViewUpdated WebsocketEventType = "view_updated"
|
||||
WebsocketEventViewDeleted WebsocketEventType = "view_deleted"
|
||||
WebsocketEventViewSorted WebsocketEventType = "view_sorted"
|
||||
WebsocketEventPropertyFieldCreated WebsocketEventType = "property_field_created"
|
||||
WebsocketEventPropertyFieldUpdated WebsocketEventType = "property_field_updated"
|
||||
WebsocketEventPropertyFieldDeleted WebsocketEventType = "property_field_deleted"
|
||||
WebsocketEventPropertyValuesUpdated WebsocketEventType = "property_values_updated"
|
||||
WebsocketEventFileDownloadRejected WebsocketEventType = "file_download_rejected"
|
||||
WebsocketEventFileUploadRejected WebsocketEventType = "file_upload_rejected"
|
||||
WebsocketEventShowToast WebsocketEventType = "show_toast"
|
||||
WebsocketEventSharedChannelRemoteUpdated WebsocketEventType = "shared_channel_remote_updated"
|
||||
WebsocketEventChannelJoinRequestCreated WebsocketEventType = "channel_join_request_created"
|
||||
WebsocketEventChannelJoinRequestUpdated WebsocketEventType = "channel_join_request_updated"
|
||||
|
||||
WebSocketMsgTypeResponse = "response"
|
||||
WebSocketMsgTypeEvent = "event"
|
||||
)
|
||||
|
||||
type ActiveQueueItem struct {
|
||||
Type string `json:"type"` // websocket event or websocket response
|
||||
Buf json.RawMessage `json:"buf"`
|
||||
}
|
||||
|
||||
type WSQueues struct {
|
||||
ActiveQ []ActiveQueueItem `json:"active_queue"` // websocketEvent|websocketResponse
|
||||
DeadQ []json.RawMessage `json:"dead_queue"` // websocketEvent
|
||||
ReuseCount int `json:"reuse_count"`
|
||||
}
|
||||
|
||||
type WebSocketMessage interface {
|
||||
ToJSON() ([]byte, error)
|
||||
IsValid() bool
|
||||
EventType() WebsocketEventType
|
||||
}
|
||||
|
||||
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
|
||||
ConnectionId string `json:"connection_id"` // broadcast only occurs for this connection
|
||||
OmitConnectionId string `json:"omit_connection_id"` // broadcast is omitted for this connection
|
||||
ContainsSanitizedData bool `json:"contains_sanitized_data,omitempty"` // broadcast only occurs for non-sysadmins
|
||||
ContainsSensitiveData bool `json:"contains_sensitive_data,omitempty"` // broadcast only occurs for sysadmins
|
||||
RequiredPermissions []string `json:"required_permissions,omitempty"` // broadcast only occurs for sessions with all permissions
|
||||
// ReliableClusterSend indicates whether or not the message should
|
||||
// be sent through the cluster using the reliable, TCP backed channel.
|
||||
ReliableClusterSend bool `json:"-"`
|
||||
|
||||
// RecordPostDelivery marks a post whose websocket delivery should be audit logged. It
|
||||
// is server-internal but intentionally NOT json:"-": it must cross the cluster so a
|
||||
// peer node's hub can record deliveries for its own connections. It is stripped before
|
||||
// client serialization by WithoutRecordPostDelivery.
|
||||
RecordPostDelivery *PostDeliveryMarker `json:"record_post_delivery,omitempty"`
|
||||
|
||||
// BroadcastHooks is a slice of hooks IDs used to process events before sending them on individual connections. The
|
||||
// IDs should be understood by the WebSocket code.
|
||||
//
|
||||
// This field should never be sent to the client.
|
||||
BroadcastHooks []string `json:"broadcast_hooks,omitempty"`
|
||||
// BroadcastHookArgs is a slice of named arguments for each hook invocation. The index of each entry corresponds to
|
||||
// the index of a hook ID in BroadcastHooks
|
||||
//
|
||||
// This field should never be sent to the client.
|
||||
BroadcastHookArgs []map[string]any `json:"broadcast_hook_args,omitempty"`
|
||||
}
|
||||
|
||||
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))
|
||||
maps.Copy(c.OmitUsers, wb.OmitUsers)
|
||||
}
|
||||
c.UserId = wb.UserId
|
||||
c.ChannelId = wb.ChannelId
|
||||
c.TeamId = wb.TeamId
|
||||
c.OmitConnectionId = wb.OmitConnectionId
|
||||
c.ContainsSanitizedData = wb.ContainsSanitizedData
|
||||
c.ContainsSensitiveData = wb.ContainsSensitiveData
|
||||
c.RecordPostDelivery = wb.RecordPostDelivery
|
||||
c.RequiredPermissions = slices.Clone(wb.RequiredPermissions)
|
||||
c.BroadcastHooks = wb.BroadcastHooks
|
||||
c.BroadcastHookArgs = wb.BroadcastHookArgs
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func (wb *WebsocketBroadcast) AddHook(hookID string, hookArgs map[string]any) {
|
||||
wb.BroadcastHooks = append(wb.BroadcastHooks, hookID)
|
||||
wb.BroadcastHookArgs = append(wb.BroadcastHookArgs, hookArgs)
|
||||
}
|
||||
|
||||
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 WebsocketEventType `json:"event"`
|
||||
Data map[string]any `json:"data"`
|
||||
Broadcast *WebsocketBroadcast `json:"broadcast"`
|
||||
Sequence int64 `json:"seq"`
|
||||
}
|
||||
|
||||
type WebSocketEvent struct {
|
||||
event WebsocketEventType
|
||||
data map[string]any
|
||||
broadcast *WebsocketBroadcast
|
||||
sequence int64
|
||||
precomputedJSON *precomputedWebSocketEventJSON
|
||||
rejected bool
|
||||
}
|
||||
|
||||
// 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 {
|
||||
evCopy := ev.Copy()
|
||||
event, _ := json.Marshal(evCopy.event)
|
||||
data, _ := json.Marshal(evCopy.data)
|
||||
broadcast, _ := json.Marshal(evCopy.broadcast)
|
||||
evCopy.precomputedJSON = &precomputedWebSocketEventJSON{
|
||||
Event: json.RawMessage(event),
|
||||
Data: json.RawMessage(data),
|
||||
Broadcast: json.RawMessage(broadcast),
|
||||
}
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) RemovePrecomputedJSON() *WebSocketEvent {
|
||||
evCopy := ev.DeepCopy()
|
||||
evCopy.precomputedJSON = nil
|
||||
return evCopy
|
||||
}
|
||||
|
||||
// WithoutBroadcastHooks gets the broadcast hook information from a WebSocketEvent and returns the event without that.
|
||||
// If the event has broadcast hooks, a copy of the event is returned. Otherwise, the original event is returned. This
|
||||
// is intended to be called before the event is sent to the client.
|
||||
func (ev *WebSocketEvent) WithoutBroadcastHooks() (*WebSocketEvent, []string, []map[string]any) {
|
||||
hooks := ev.broadcast.BroadcastHooks
|
||||
hookArgs := ev.broadcast.BroadcastHookArgs
|
||||
|
||||
if len(hooks) == 0 && len(hookArgs) == 0 {
|
||||
return ev, hooks, hookArgs
|
||||
}
|
||||
|
||||
evCopy := ev.Copy()
|
||||
evCopy.broadcast = ev.broadcast.copy()
|
||||
|
||||
evCopy.broadcast.BroadcastHooks = nil
|
||||
evCopy.broadcast.BroadcastHookArgs = nil
|
||||
|
||||
return evCopy, hooks, hookArgs
|
||||
}
|
||||
|
||||
// WithoutRecordPostDelivery returns the event's post-delivery tracking marker and an event
|
||||
// whose broadcast no longer carries it. Callers invoke it right before precomputing the
|
||||
// client-bound JSON and use the returned marker out-of-band to record the delivery.
|
||||
//
|
||||
// Why not simply tag the field json:"-"? The marker is server-internal, but it must still
|
||||
// cross the cluster. Delivery is recorded per node, for that node's own connections: a post
|
||||
// created on node A reaches node B via PlatformService.Publish -> message.ToJSON() -> (on B)
|
||||
// WebSocketEventFromJSON -> B's hub, which records its recipients using this field. With
|
||||
// json:"-" the field would be dropped on the wire, so any recipient not on the origin node
|
||||
// would be silently unrecorded — a correctness bug in clustered deployments. And the client
|
||||
// frame is built from the SAME broadcast JSON (PrecomputeJSON), so a struct tag cannot keep
|
||||
// it for the cluster yet hide it from clients; the strip has to happen in code, here.
|
||||
//
|
||||
// This is copy-on-write (a copy is made only when the marker is set, mirroring
|
||||
// WithoutBroadcastHooks) and must stay so: Publish serializes the ORIGINAL event for the
|
||||
// cluster immediately after the local broadcast, so clearing the marker in place would race
|
||||
// that send and could drop the field before it leaves the node.
|
||||
func (ev *WebSocketEvent) WithoutRecordPostDelivery() (*WebSocketEvent, *PostDeliveryMarker) {
|
||||
marker := ev.broadcast.RecordPostDelivery
|
||||
if marker == nil {
|
||||
return ev, nil
|
||||
}
|
||||
|
||||
evCopy := ev.Copy()
|
||||
evCopy.broadcast = ev.broadcast.copy()
|
||||
evCopy.broadcast.RecordPostDelivery = nil
|
||||
|
||||
return evCopy, marker
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) Add(key string, value any) {
|
||||
ev.data[key] = value
|
||||
}
|
||||
|
||||
func NewWebSocketEvent(event WebsocketEventType, teamId, channelId, userId string, omitUsers map[string]bool, omitConnectionId string) *WebSocketEvent {
|
||||
return &WebSocketEvent{
|
||||
event: event,
|
||||
data: make(map[string]any),
|
||||
broadcast: &WebsocketBroadcast{
|
||||
TeamId: teamId,
|
||||
ChannelId: channelId,
|
||||
UserId: userId,
|
||||
OmitUsers: omitUsers,
|
||||
OmitConnectionId: omitConnectionId},
|
||||
}
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) Copy() *WebSocketEvent {
|
||||
evCopy := &WebSocketEvent{
|
||||
event: ev.event,
|
||||
data: ev.data,
|
||||
broadcast: ev.broadcast,
|
||||
sequence: ev.sequence,
|
||||
precomputedJSON: ev.precomputedJSON,
|
||||
}
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) DeepCopy() *WebSocketEvent {
|
||||
evCopy := &WebSocketEvent{
|
||||
event: ev.event,
|
||||
data: maps.Clone(ev.data),
|
||||
broadcast: ev.broadcast.copy(),
|
||||
sequence: ev.sequence,
|
||||
precomputedJSON: ev.precomputedJSON.copy(),
|
||||
}
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetData() map[string]any {
|
||||
return ev.data
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetBroadcast() *WebsocketBroadcast {
|
||||
return ev.broadcast
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) GetSequence() int64 {
|
||||
return ev.sequence
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetEvent(event WebsocketEventType) *WebSocketEvent {
|
||||
evCopy := ev.Copy()
|
||||
evCopy.event = event
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetData(data map[string]any) *WebSocketEvent {
|
||||
evCopy := ev.Copy()
|
||||
evCopy.data = data
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetBroadcast(broadcast *WebsocketBroadcast) *WebSocketEvent {
|
||||
evCopy := ev.Copy()
|
||||
evCopy.broadcast = broadcast
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) SetSequence(seq int64) *WebSocketEvent {
|
||||
evCopy := ev.Copy()
|
||||
evCopy.sequence = seq
|
||||
return evCopy
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) IsValid() bool {
|
||||
return ev.event != ""
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) EventType() WebsocketEventType {
|
||||
return ev.event
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) ToJSON() ([]byte, error) {
|
||||
if ev.precomputedJSON != nil {
|
||||
return ev.precomputedJSONBuf(), 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, buf io.Writer) error {
|
||||
if ev.precomputedJSON != nil {
|
||||
_, err := buf.Write(ev.precomputedJSONBuf())
|
||||
return err
|
||||
}
|
||||
|
||||
return enc.Encode(webSocketEventJSON{
|
||||
ev.event,
|
||||
ev.data,
|
||||
ev.broadcast,
|
||||
ev.sequence,
|
||||
})
|
||||
}
|
||||
|
||||
// We write optimal code here sacrificing readability for
|
||||
// performance.
|
||||
func (ev *WebSocketEvent) precomputedJSONBuf() []byte {
|
||||
return []byte(`{"event": ` +
|
||||
string(ev.precomputedJSON.Event) +
|
||||
`, "data": ` +
|
||||
string(ev.precomputedJSON.Data) +
|
||||
`, "broadcast": ` +
|
||||
string(ev.precomputedJSON.Broadcast) +
|
||||
`, "seq": ` +
|
||||
strconv.Itoa(int(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]any.
|
||||
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]any `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 any) {
|
||||
m.Data[key] = value
|
||||
}
|
||||
|
||||
func NewWebSocketResponse(status string, seqReply int64, data map[string]any) *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() WebsocketEventType {
|
||||
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)
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) Reject() {
|
||||
ev.rejected = true
|
||||
}
|
||||
|
||||
func (ev *WebSocketEvent) IsRejected() bool {
|
||||
return ev.rejected
|
||||
}
|
||||
Reference in New Issue
Block a user