1
0
Fork 0
mirror of https://github.com/Luzifer/lounge-control.git synced 2024-12-22 22:41:16 +00:00
lounge-control/sioclient/sio.go

181 lines
3.6 KiB
Go

package sioclient
import (
"bytes"
"encoding/json"
"strconv"
"github.com/pkg/errors"
)
type MessageType int
const (
MessageTypeConnect MessageType = iota
MessageTypeDisconnect
MessageTypeEvent
MessageTypeAck
MessageTypeError
MessageTypeBinaryEvent
MessageTypeBinaryAck
)
type Config struct {
MessageHandler func(*Message) error
URL string
}
type Client struct {
EIO *EIOClient
cfg Config
}
func New(c Config) (*Client, error) {
var (
client = new(Client)
err error
)
client.cfg = c
if client.EIO, err = NewEIOClient(EIOClientConfig{
MessageHandlerText: client.handleTextMessage,
URL: c.URL,
}); err != nil {
return nil, errors.Wrap(err, "Unable to create EIO client")
}
return client, nil
}
func (c Client) Close() error {
return c.EIO.Close()
}
func (c Client) handleTextMessage(msg []byte) error {
m, err := c.parseProto(msg)
if err != nil {
return errors.Wrap(err, "Unable to parse message")
}
return c.cfg.MessageHandler(m)
}
type Message struct {
Type MessageType
Namespace string
ID int
Payload []json.RawMessage
}
func NewMessage(sType MessageType, id int, payloadType string, data interface{}) (*Message, error) {
out := &Message{
Type: sType,
Namespace: "/",
ID: id,
Payload: make([]json.RawMessage, 2),
}
var err error
if out.Payload[0], err = json.Marshal(payloadType); err != nil {
return nil, errors.Wrap(err, "Unable to marshal payloadType")
}
if out.Payload[1], err = json.Marshal(data); err != nil {
return nil, errors.Wrap(err, "Unable to marshal data")
}
return out, nil
}
func (m Message) Encode() (string, error) {
data, err := json.Marshal(m.Payload)
if err != nil {
return "", errors.Wrap(err, "Unable to marshal payload")
}
var msg = new(bytes.Buffer)
msg.WriteString(strconv.Itoa(int(m.Type)))
if m.Namespace != "" && m.Namespace != "/" {
msg.WriteString(m.Namespace + ",")
}
if m.ID > 0 {
msg.WriteString(strconv.Itoa(m.ID))
}
msg.Write(data)
return msg.String(), nil
}
func (m Message) PayloadType() (string, error) {
if len(m.Payload) == 0 {
return "", errors.New("No payload type available")
}
var t string
err := json.Unmarshal(m.Payload[0], &t)
return t, errors.Wrap(err, "Unable to unmarshal payload type")
}
func (m Message) Send(c *Client) error {
raw, err := m.Encode()
if err != nil {
return errors.Wrap(err, "Unable to encode message")
}
return c.EIO.SendTextMessage(EIOMessageTypeMessage, raw)
}
func (m Message) UnmarshalPayload(out interface{}) error { return json.Unmarshal(m.Payload[1], out) }
func (c Client) parseProto(msg []byte) (*Message, error) {
var (
err error
outMsg = new(Message)
ptr int
)
if len(msg) < 1 {
return nil, errors.New("Message was empty")
}
// Get message type
mType, err := strconv.Atoi(string(msg[ptr]))
if err != nil {
return nil, errors.Wrap(err, "Unable to parse message type")
}
outMsg.Type = MessageType(mType)
ptr++
// Message contains only message type
if len(msg[ptr:]) == 0 {
return outMsg, nil
}
// Binary
if outMsg.Type == MessageTypeBinaryEvent || outMsg.Type == MessageTypeBinaryAck {
return nil, errors.New("Binary is not supported")
}
// Check for namespace
if msg[ptr] == '/' {
return nil, errors.New("Namespaces is not supported")
}
// Read message ID if any
if outMsg.ID, err = strconv.Atoi(string(msg[ptr])); err == nil {
ptr++
}
// If there is no more data we have an empty message
if len(msg[ptr:]) == 0 || outMsg.Type == MessageTypeConnect {
return outMsg, nil
}
return outMsg, errors.Wrap(json.Unmarshal(msg[ptr:], &outMsg.Payload), "Unable to unmarshal message")
}