// Package api defines the general API for both coordinator and worker applications. // // Each API call (request and response) is a JSON-encoded "packet" of the following structure: // // id - (optional) a globally unique packet id; // t - (required) one of the predefined unique packet types; // p - (optional) packet payload with arbitrary data. // // The basic idea behind this API is that the packets differentiate by their predefined types // with which it is possible to unwrap the payload into distinct request/response data structures. // And the id field is used for tracking packets through a chain of different network points (apps, devices), // for example, passing a packet from a browser forward to a worker and back through a coordinator. // // Example: // // {"t":4,"p":{"ice":[{"urls":"stun:stun.l.google.com:19302"}],"games":["Sushi The Cat"],"wid":"cfv68irdrc3ifu3jn6bg"}} package api import ( "encoding/json" "fmt" "strings" ) type ( Id interface { String() string } Stateful struct { Id string `json:"id"` } Room struct { Rid string `json:"room_id"` } StatefulRoom struct { Id string `json:"id"` Rid string `json:"room_id"` } PT uint8 ) type In[I Id] struct { Id I `json:"id,omitempty"` T PT `json:"t"` Payload json.RawMessage `json:"p,omitempty"` // should be json.RawMessage for 2-pass unmarshal } func (i In[I]) GetId() I { return i.Id } func (i In[I]) GetPayload() []byte { return i.Payload } func (i In[I]) GetType() PT { return i.T } type Out struct { Id string `json:"id,omitempty"` // string because omitempty won't work as intended with arrays T uint8 `json:"t"` Payload any `json:"p,omitempty"` } func (o *Out) SetId(s string) { o.Id = s } func (o *Out) SetType(u uint8) { o.T = u } func (o *Out) SetPayload(a any) { o.Payload = a } func (o *Out) SetGetId(s fmt.Stringer) { o.Id = s.String() } func (o *Out) GetPayload() any { return o.Payload } // Packet codes: // // x, 1xx - user codes // 15x - webrtc data exchange codes // 2xx - worker codes const ( CheckLatency PT = 3 InitSession PT = 4 WebrtcInit PT = 100 WebrtcOffer PT = 101 WebrtcAnswer PT = 102 WebrtcIce PT = 103 StartGame PT = 104 QuitGame PT = 105 SaveGame PT = 106 LoadGame PT = 107 ChangePlayer PT = 108 RecordGame PT = 110 GetWorkerList PT = 111 ErrNoFreeSlots PT = 112 ResetGame PT = 113 RegisterRoom PT = 201 CloseRoom PT = 202 IceCandidate = WebrtcIce TerminateSession PT = 204 AppVideoChange PT = 150 LibNewGameList PT = 205 PrevSessions PT = 206 ) func (p PT) String() string { switch p { case CheckLatency: return "CheckLatency" case InitSession: return "InitSession" case WebrtcInit: return "WebrtcInit" case WebrtcOffer: return "WebrtcOffer" case WebrtcAnswer: return "WebrtcAnswer" case WebrtcIce: return "WebrtcIce" case StartGame: return "StartGame" case ChangePlayer: return "ChangePlayer" case QuitGame: return "QuitGame" case SaveGame: return "SaveGame" case LoadGame: return "LoadGame" case RecordGame: return "RecordGame" case GetWorkerList: return "GetWorkerList" case ErrNoFreeSlots: return "NoFreeSlots" case ResetGame: return "ResetGame" case RegisterRoom: return "RegisterRoom" case CloseRoom: return "CloseRoom" case TerminateSession: return "TerminateSession" case AppVideoChange: return "AppVideoChange" case LibNewGameList: return "LibNewGameList" case PrevSessions: return "PrevSessions" default: return "Unknown" } } // Various codes const ( EMPTY = "" OK = "ok" ) var ( ErrForbidden = fmt.Errorf("forbidden") ErrMalformed = fmt.Errorf("malformed") ) var ( EmptyPacket = Out{Payload: ""} ErrPacket = Out{Payload: "err"} OkPacket = Out{Payload: "ok"} ) func Do[I Id, T any](in In[I], fn func(T)) error { if dat := Unwrap[T](in.Payload); dat != nil { fn(*dat) return nil } return ErrMalformed } func DoE[I Id, T any](in In[I], fn func(T) error) error { if dat := Unwrap[T](in.Payload); dat != nil { return fn(*dat) } return ErrMalformed } func Unwrap[T any](data []byte) *T { out := new(T) if err := json.Unmarshal(data, out); err != nil { return nil } return out } func UnwrapChecked[T any](bytes []byte, err error) (*T, error) { if err != nil { return nil, err } return Unwrap[T](bytes), nil } func Wrap(t any) ([]byte, error) { return json.Marshal(t) } const separator = "___" func ExplodeDeepLink(link string) (string, string) { p := strings.SplitN(link, separator, 2) if len(p) == 1 { return p[0], "" } return p[0], p[1] }