diff --git a/server/action/client.go b/server/action/client.go deleted file mode 100644 index 22445fb..0000000 --- a/server/action/client.go +++ /dev/null @@ -1,34 +0,0 @@ -package action - -import ( - "go.uber.org/zap/zapcore" -) - -// ClientHello is the action sent by the client when it first establishes the connection. -type ClientHello struct { - // Version is the protocol version the client is running. - Version int `json:"version"` -} - -// ClientRefresh is the action sent by the client when it needs the full state re-sent. -type ClientRefresh struct { -} - -// IDed contains a pair of ID and Action, as sent by the server. -type IDed struct { - // ID contains the arbitrary ID that was sent by the client, for identifying the action in future messages. - ID int `json:"id"` - // Action contains the action that was actually being sent. - Action Syncable `json:"action"` -} - -func (i IDed) MarshalLogObject(encoder zapcore.ObjectEncoder) error { - encoder.AddInt("id", i.ID) - return encoder.AddObject("action", i.Action) -} - -// ClientSent is an action sent in order to deliver one or more Syncable actions to the server. -type ClientSent struct { - // Actions contains the actions the client wants to apply, in the order they should be applied. - Actions []IDed `json:"nested"` -} diff --git a/server/room/clientmessage.go b/server/room/clientmessage.go index 8238d64..917f60b 100644 --- a/server/room/clientmessage.go +++ b/server/room/clientmessage.go @@ -3,7 +3,7 @@ package room import ( "github.com/rs/xid" "go.uber.org/zap/zapcore" - "hexmap-server/action" + "hexmap-server/websocket" ) // ClientMessage marks messages coming from clients to the room. @@ -62,7 +62,7 @@ func (r RefreshRequest) ClientID() xid.ID { // websocket. type ApplyRequest struct { id xid.ID - action action.IDed + action websocket.IDed } func (f ApplyRequest) MarshalLogObject(encoder zapcore.ObjectEncoder) error { diff --git a/server/websocket/client.go b/server/websocket/client.go new file mode 100644 index 0000000..df5d2cc --- /dev/null +++ b/server/websocket/client.go @@ -0,0 +1,93 @@ +package websocket + +import ( + "go.uber.org/zap/zapcore" + "hexmap-server/action" +) + +// ClientMessageType is an enum type for the client's protocol messages. +type ClientMessageType string + +const ( + ClientHelloType ClientMessageType = "HELLO" + ClientRefreshType ClientMessageType = "REFRESH" + ClientActType ClientMessageType = "ACT" + ClientGoodbyeType ClientMessageType = GoodbyeType +) + +// ClientMessage s are those sent by the client. +type ClientMessage interface { + zapcore.ObjectMarshaler + // ClientType gives the type constant that will be sent on or read from the wire. + ClientType() ClientMessageType +} + +// ClientHello is the action sent by the client when it first establishes the connection. +type ClientHello struct { + // Version is the protocol version the client is running. + Version int `json:"version"` +} + +func (c ClientHello) ClientType() ClientMessageType { + return ClientHelloType +} + +func (c ClientHello) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("type", string(ClientHelloType)) + encoder.AddInt("version", c.Version) + return nil +} + +// ClientRefresh is the action sent by the client when it needs the full state re-sent. +type ClientRefresh struct { +} + +func (c ClientRefresh) ClientType() ClientMessageType { + return ClientRefreshType +} + +func (c ClientRefresh) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("type", string(ClientRefreshType)) + return nil +} + +// IDed contains a pair of ID and Action, as sent by the client. +type IDed struct { + // ID contains the arbitrary ID that was sent by the client, for identifying the action in future messages. + ID int `json:"id"` + // Action contains the action that was actually being sent. + Action action.Syncable `json:"action"` +} + +func (i IDed) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddInt("id", i.ID) + return encoder.AddObject("action", i.Action) +} + +type IDPairs []IDed + +func (a IDPairs) MarshalLogArray(encoder zapcore.ArrayEncoder) error { + var finalErr error = nil + for _, v := range a { + err := encoder.AppendObject(v) + if err != nil && finalErr == nil { + finalErr = err + } + } + return finalErr +} + +// ClientAct is an action sent in order to deliver one or more Syncable actions to the server. +type ClientAct struct { + // Actions contains the actions the client wants to apply, in the order they should be applied. + Actions IDPairs `json:"actions"` +} + +func (c ClientAct) ClientType() ClientMessageType { + return ClientActType +} + +func (c ClientAct) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("type", string(ClientActType)) + return encoder.AddArray("actions", c.Actions) +} diff --git a/server/action/server.go b/server/websocket/server.go similarity index 62% rename from server/action/server.go rename to server/websocket/server.go index 454c207..320efa6 100644 --- a/server/action/server.go +++ b/server/websocket/server.go @@ -1,6 +1,31 @@ -package action +package websocket -import "hexmap-server/state" +import ( + "go.uber.org/zap/zapcore" + "hexmap-server/action" + "hexmap-server/state" +) + +// TODO: Make all the ServerMessages implement ServerMessage. + +// ServerMessageType is an enum type for the server's messages. +type ServerMessageType string + +const ( + ServerHelloType ServerMessageType = "HELLO" + ServerRefreshType ServerMessageType = "REFRESH" + ServerOKType ServerMessageType = "OK" + ServerFailedType ServerMessageType = "FAILED" + ServerActType ServerMessageType = "ACT" + ServerGoodbyeType ServerMessageType = GoodbyeType +) + +// ServerMessage s are sent by the server to the client. +type ServerMessage interface { + zapcore.ObjectMarshaler + // ServerType returns the type constant that will be sent on the wire. + ServerType() ServerMessageType +} // ServerHello is the action sent to establish the current state of the server when a new client connects. type ServerHello struct { @@ -34,9 +59,9 @@ type ServerFailed struct { Error string `json:"error"` } -// ServerSent is the action sent when one or more client actions from other clients have been accepted and applied. +// ServerAct is the action sent when one or more client actions from other clients have been accepted and applied. // The client's own actions will never be included in this action. -type ServerSent struct { +type ServerAct struct { // Actions contains the actions that are now being applied. - Actions []Syncable `json:"actions"` + Actions []action.Syncable `json:"actions"` } diff --git a/server/websocket/shared.go b/server/websocket/shared.go new file mode 100644 index 0000000..e9e0335 --- /dev/null +++ b/server/websocket/shared.go @@ -0,0 +1,53 @@ +package websocket + +import "go.uber.org/zap/zapcore" + +// StatusCode is the code used by the WebSocket protocol to signal the other side on close. +type StatusCode int16 + +const ( + StatusNormal StatusCode = 1000 + StatusGoingAway StatusCode = 1001 + StatusProtocolError StatusCode = 1002 + StatusTooBig StatusCode = 1009 + StatusProtocolVersionOutOfDate StatusCode = 4000 + + GoodbyeType = "GOODBYE" +) + +// TODO: Noting that there should be three channels in play: +// 1) Reader to client: to receive messages from the connection +// 2) Client to writer: to send messages on the connection +// 3) Writer to reader: indicating that it is about to send a close message, and the reader should wait for one and +// time out the connection if it takes too long. + +// SocketClosed is synthesized when a client closes the WebSocket connection, or sent to the write process to write a +// WebSocket close message. +// Sending a SocketClosed on a channel causes that channel to be closed right after. +type SocketClosed struct { + // Code is the StatusCode given (or which should be given) in the close message. + Code StatusCode + // Text is the reason text given (or which should be given) in the close message. Max 123 characters. + Text string + // Error may be an error that resulted in the closure of the socket. + // Will not be written by the writer; only useful when it's returned from the reader. + Error error +} + +func (c SocketClosed) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("type", GoodbyeType) + encoder.AddInt16("code", int16(c.Code)) + encoder.AddString("text", c.Text) + if c.Error != nil { + encoder.AddString("error", c.Error.Error()) + } + return nil +} + +func (c SocketClosed) ClientType() ClientMessageType { + return ClientGoodbyeType +} + +func (c SocketClosed) ServerType() ServerMessageType { + return ServerGoodbyeType +}