package ws // Protocol defines the WebSocket message frame format and event type constants. // Reference: Chatwoot ActionCable protocol — subscribe/command/message pattern. // // Client → Server commands: // {"command":"subscribe","identifier":"{\"channel\":\"AccountChannel\",\"account_id\":1}"}} // {"command":"unsubscribe","identifier":"{\"channel\":\"AccountChannel\",\"account_id\":1}"}} // {"command":"ping"} // // Server → Client events: // {"type":"event","event":"message.created","payload":{...},"identifier":"{\"channel\":\"AccountChannel\",\"account_id\":1}"} // {"type":"confirm_subscribe","identifier":"..."} // {"type":"confirm_unsubscribe","identifier":"..."} // {"type":"ping","message":"2026-05-23T10:00:00Z"} // {"type":"reject_subscribe","identifier":"...","reason":"..."} // CommandType — client→server action types type CommandType string const ( CommandSubscribe CommandType = "subscribe" CommandUnsubscribe CommandType = "unsubscribe" CommandPing CommandType = "ping" ) // ServerMessageType — server→client message types type ServerMessageType string const ( // ServerEvent pushes a real-time event to subscribed clients ServerEvent ServerMessageType = "event" // ServerConfirmSubscribe acknowledges a successful subscription ServerConfirmSubscribe ServerMessageType = "confirm_subscribe" // ServerConfirmUnsubscribe acknowledges a successful unsubscribe ServerConfirmUnsubscribe ServerMessageType = "confirm_unsubscribe" // ServerRejectSubscribe rejects a subscription attempt ServerRejectSubscribe ServerMessageType = "reject_subscribe" // ServerPing is a heartbeat response ServerPing ServerMessageType = "ping" // ServerWelcome is sent immediately upon connection ServerWelcome ServerMessageType = "welcome" // ServerDisconnect is sent before closing the connection ServerDisconnect ServerMessageType = "disconnect" ) // --- Client → Server Frames --- // CommandFrame is the frame clients send to the server. // Mirrors Chatwoot ActionCable's command structure. type CommandFrame struct { Command CommandType `json:"command"` Identifier string `json:"identifier"` // JSON-encoded ChannelIdentifier Data string `json:"data,omitempty"` // optional action data } // ChannelIdentifier describes which "channel" (room) the client wants to subscribe to. // Serialized as JSON string in the `identifier` field, matching ActionCable convention. type ChannelIdentifier struct { Channel string `json:"channel"` // "AccountChannel" or "ConversationChannel" AccountID uint `json:"account_id"` // required for both channels ConversationID uint `json:"conversation_id,omitempty"` // required for ConversationChannel } // Channel name constants (ActionCable naming style) const ( ChannelAccount = "AccountChannel" ChannelConversation = "ConversationChannel" ) // --- Server → Client Frames --- // EventFrame pushes a real-time event payload to the client. type EventFrame struct { Type ServerMessageType `json:"type"` Event string `json:"event,omitempty"` // e.g. "message.created" Payload interface{} `json:"payload,omitempty"` // event data Identifier string `json:"identifier,omitempty"` // channel identifier } // ConfirmFrame acknowledges a subscribe/unsubscribe command. type ConfirmFrame struct { Type ServerMessageType `json:"type"` Identifier string `json:"identifier"` } // RejectFrame rejects a subscribe command with a reason. type RejectFrame struct { Type ServerMessageType `json:"type"` Identifier string `json:"identifier"` Reason string `json:"reason"` } // PingFrame is a heartbeat pong response. type PingFrame struct { Type ServerMessageType `json:"type"` Message string `json:"message"` // timestamp string } // WelcomeFrame is sent upon successful WebSocket connection. type WelcomeFrame struct { Type ServerMessageType `json:"type"` } // DisconnectFrame is sent before closing a connection. type DisconnectFrame struct { Type ServerMessageType `json:"type"` Reason string `json:"reason"` Reconnect bool `json:"reconnect"` } // --- Real-time Event Type Constants --- // These match the Watermill PubSub topic names and channel.EventType values. const ( // Message events EventMessageCreated = "message.created" EventMessageUpdated = "message.updated" EventMessageDeleted = "message.deleted" // Conversation events EventConversationCreated = "conversation.created" EventConversationUpdated = "conversation.updated" EventConversationResolved = "conversation.resolved" EventConversationOpened = "conversation.opened" EventConversationAssigned = "conversation.assigned" EventConversationUnassigned = "conversation.unassigned" // Contact events EventContactCreated = "contact.created" EventContactUpdated = "contact.updated" EventContactDeleted = "contact.deleted" // Agent/typing events EventAgentTypingOn = "agent.typing_on" EventAgentTypingOff = "agent.typing_off" EventAgentOnline = "agent.online" EventAgentOffline = "agent.offline" // Inbox events EventInboxCreated = "inbox.created" EventInboxUpdated = "inbox.updated" EventInboxDeleted = "inbox.deleted" // System notification event EventSystemNotification = "system.notification" // P4 M8 — Notification+Webhook event types EventNotificationCreated = "notification.created" ) // --- Ping/pong Configuration --- const ( // PingInterval is how often the server sends ping frames to detect dead connections. PingInterval = 30 // seconds )