From 260a1973f5a372d44aaedcfcc7a13a921cb82484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 20 Jul 2024 15:45:01 +0200 Subject: [PATCH] WIP legacy adapter. --- server/internal/http/legacy/handler.go | 66 ++- server/internal/http/legacy/session.go | 96 ++++ server/internal/http/legacy/wstobackend.go | 271 +++++++--- server/internal/http/legacy/wstoclient.go | 595 +++++++++++++++++---- 4 files changed, 823 insertions(+), 205 deletions(-) create mode 100644 server/internal/http/legacy/session.go diff --git a/server/internal/http/legacy/handler.go b/server/internal/http/legacy/handler.go index 31e08e8b..fae80e8b 100644 --- a/server/internal/http/legacy/handler.go +++ b/server/internal/http/legacy/handler.go @@ -38,14 +38,9 @@ func (h *LegacyHandler) Route(r types.Router) { log.Println("legacy handler route") r.Get("/ws", func(w http.ResponseWriter, r *http.Request) error { - connBackend, _, err := DefaultDialer.Dial("ws://127.0.0.1:8080/api/ws?token="+r.URL.Query().Get("token"), nil) - if err != nil { - return utils.HttpError(http.StatusServiceUnavailable). - WithInternalErr(err). - Msg("couldn't dial to remote backend url") - } - defer connBackend.Close() + s := newSession("http://127.0.0.1:8080") + // create a new websocket connection connClient, err := DefaultUpgrader.Upgrade(w, r, nil) if err != nil { return utils.HttpError(http.StatusInternalServerError). @@ -53,10 +48,36 @@ func (h *LegacyHandler) Route(r types.Router) { Msg("couldn't upgrade connection to websocket") } defer connClient.Close() + s.connClient = connClient + // create a new session + password := r.URL.Query().Get("password") + token, err := s.create(password) + if err != nil { + log.Printf("couldn't create a new session: %v", err) + return nil + } + defer s.destroy() + + // dial to the remote backend + connBackend, _, err := DefaultDialer.Dial("ws://127.0.0.1:8080/api/ws?token="+token, nil) + if err != nil { + log.Printf("couldn't dial to the remote backend: %v", err) + return nil + } + defer connBackend.Close() + s.connBackend = connBackend + + // request signal + if err = connBackend.WriteMessage(websocket.TextMessage, []byte(`{"event":"signal/request", "payload":{}}`)); err != nil { + log.Printf("couldn't request signal: %v", err) + return nil + } + + // copy messages between the client and the backend errClient := make(chan error, 1) errBackend := make(chan error, 1) - replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error, rewriteTextMessage func([]byte) ([]byte, error)) { + replicateWebsocketConn := func(dst, src *websocket.Conn, errc chan error, rewriteTextMessage func([]byte, func([]byte) error) error) { for { msgType, msg, err := src.ReadMessage() if err != nil { @@ -71,25 +92,33 @@ func (h *LegacyHandler) Route(r types.Router) { break } if msgType == websocket.TextMessage { - msg, err = rewriteTextMessage(msg) + doBreak := false + err = rewriteTextMessage(msg, func(msg []byte) error { + err := dst.WriteMessage(msgType, msg) + if err != nil { + doBreak = true + } + return err + }) + + if doBreak { + errc <- err + break + } + if err != nil { log.Printf("websocketproxy: Error when rewriting message: %v", err) continue } } - err = dst.WriteMessage(msgType, msg) - if err != nil { - errc <- err - break - } } } - // client -> backend - go replicateWebsocketConn(connClient, connBackend, errClient, h.wsToBackend) - // backend -> client - go replicateWebsocketConn(connBackend, connClient, errBackend, h.wsToClient) + go replicateWebsocketConn(connClient, connBackend, errClient, s.wsToClient) + + // client -> backend + go replicateWebsocketConn(connBackend, connClient, errBackend, s.wsToBackend) var message string select { @@ -97,7 +126,6 @@ func (h *LegacyHandler) Route(r types.Router) { message = "websocketproxy: Error when copying from backend to client: %v" case err = <-errBackend: message = "websocketproxy: Error when copying from client to backend: %v" - } if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure { diff --git a/server/internal/http/legacy/session.go b/server/internal/http/legacy/session.go new file mode 100644 index 00000000..8a542250 --- /dev/null +++ b/server/internal/http/legacy/session.go @@ -0,0 +1,96 @@ +package legacy + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/demodesk/neko/internal/api" + "github.com/demodesk/neko/pkg/types" + "github.com/gorilla/websocket" +) + +type session struct { + url string + id string + token string + profile types.MemberProfile + client *http.Client + + connClient *websocket.Conn + connBackend *websocket.Conn +} + +func newSession(url string) *session { + return &session{ + url: url, + client: http.DefaultClient, + } +} + +func (s *session) apiReq(method, path string, request, response any) error { + body, err := json.Marshal(request) + if err != nil { + return err + } + + req, err := http.NewRequest(method, s.url+path, bytes.NewReader(body)) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + + if s.token != "" { + req.Header.Set("Authorization", "Bearer "+s.token) + } + + res, err := s.client.Do(req) + if err != nil { + return err + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + body, _ := io.ReadAll(res.Body) + return fmt.Errorf("unexpected status code: %d, body: %s", res.StatusCode, body) + } + + return json.NewDecoder(res.Body).Decode(response) +} + +func (s *session) create(password string) (string, error) { + data := api.SessionDataPayload{} + + // pefrom login with arbitrary username that will be changed later + err := s.apiReq(http.MethodPost, "/api/login", api.SessionLoginPayload{ + Username: "admin", + Password: password, + }, &data) + if err != nil { + return "", err + } + + s.id = data.ID + s.token = data.Token + s.profile = data.Profile + + if s.token == "" { + return "", fmt.Errorf("token not found") + } + + return data.Token, nil +} + +func (s *session) destroy() { + defer s.client.CloseIdleConnections() + + // logout session + err := s.apiReq(http.MethodPost, "/api/logout", nil, nil) + if err != nil { + log.Println("failed to logout session:", err) + } +} diff --git a/server/internal/http/legacy/wstobackend.go b/server/internal/http/legacy/wstobackend.go index f50410b9..1923d3b5 100644 --- a/server/internal/http/legacy/wstobackend.go +++ b/server/internal/http/legacy/wstobackend.go @@ -4,158 +4,293 @@ import ( "encoding/json" "fmt" - "github.com/demodesk/neko/internal/http/legacy/event" - "github.com/demodesk/neko/internal/http/legacy/message" + oldEvent "github.com/demodesk/neko/internal/http/legacy/event" + oldMessage "github.com/demodesk/neko/internal/http/legacy/message" + "github.com/pion/webrtc/v3" + + "github.com/demodesk/neko/pkg/types" + "github.com/demodesk/neko/pkg/types/event" + "github.com/demodesk/neko/pkg/types/message" + + chat "github.com/demodesk/neko/internal/plugins/chat" + filetransfer "github.com/demodesk/neko/internal/plugins/filetransfer" ) -func (h *LegacyHandler) wsToBackend(msg []byte) ([]byte, error) { - header := message.Message{} +func (s *session) wsToBackend(msg []byte, sendMsg func([]byte) error) error { + header := oldMessage.Message{} err := json.Unmarshal(msg, &header) if err != nil { - return nil, err + return err + } + + send := func(event string, payload any) error { + rawPayload, err := json.Marshal(payload) + if err != nil { + return err + } + + msg, err := json.Marshal(&types.WebSocketMessage{ + Event: event, + Payload: rawPayload, + }) + if err != nil { + return err + } + + return sendMsg(msg) } - var response any switch header.Event { // Signal Events - case event.SIGNAL_OFFER: - request := &message.SignalOffer{} + case oldEvent.SIGNAL_OFFER: + request := &oldMessage.SignalOffer{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.SIGNAL_ANSWER: - request := &message.SignalAnswer{} + return send(event.SIGNAL_OFFER, &message.SignalDescription{ + SDP: request.SDP, + }) + + case oldEvent.SIGNAL_ANSWER: + request := &oldMessage.SignalAnswer{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.SIGNAL_CANDIDATE: - request := &message.SignalCandidate{} + // TODO: Set Display Name here. + + return send(event.SIGNAL_ANSWER, &message.SignalDescription{ + SDP: request.SDP, + }) + + case oldEvent.SIGNAL_CANDIDATE: + request := &oldMessage.SignalCandidate{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } + var candidate webrtc.ICECandidateInit + err = json.Unmarshal([]byte(request.Data), &candidate) + if err != nil { + return err + } + + return send(event.SIGNAL_CANDIDATE, &message.SignalCandidate{ + ICECandidateInit: candidate, + }) + // Control Events - case event.CONTROL_RELEASE: - case event.CONTROL_REQUEST: - case event.CONTROL_GIVE: - request := &message.Control{} + case oldEvent.CONTROL_RELEASE: + return send(event.CONTROL_RELEASE, nil) + + case oldEvent.CONTROL_REQUEST: + return send(event.CONTROL_REQUEST, nil) + + case oldEvent.CONTROL_GIVE: + request := &oldMessage.Control{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.CONTROL_CLIPBOARD: - request := &message.Clipboard{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.CONTROL_CLIPBOARD: + request := &oldMessage.Clipboard{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.CONTROL_KEYBOARD: - request := &message.Keyboard{} + return send(event.CLIPBOARD_SET, &message.ClipboardData{ + Text: request.Text, + }) + + case oldEvent.CONTROL_KEYBOARD: + request := &oldMessage.Keyboard{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } + if request.Layout != nil { + err = send(event.KEYBOARD_MAP, &message.KeyboardMap{ + KeyboardMap: types.KeyboardMap{ + Layout: *request.Layout, + }, + }) + + if err != nil { + return err + } + } + + if request.CapsLock != nil || request.NumLock != nil || request.ScrollLock != nil { + err = send(event.KEYBOARD_MODIFIERS, &message.KeyboardModifiers{ + KeyboardModifiers: types.KeyboardModifiers{ + CapsLock: request.CapsLock, + NumLock: request.NumLock, + // ScrollLock: request.ScrollLock, // ScrollLock is deprecated. + }, + }) + + if err != nil { + return err + } + } + + return nil + // Chat Events - case event.CHAT_MESSAGE: - request := &message.ChatReceive{} + case oldEvent.CHAT_MESSAGE: + request := &oldMessage.ChatReceive{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.CHAT_EMOTE: - request := &message.EmoteReceive{} + return send(chat.CHAT_MESSAGE, &chat.Content{ + Text: request.Content, + }) + + case oldEvent.CHAT_EMOTE: + request := &oldMessage.EmoteReceive{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } + // TODO: Emote plugin not implemented. + return fmt.Errorf("event not implemented: %s", header.Event) + // File Transfer Events - case event.FILETRANSFER_REFRESH: + case oldEvent.FILETRANSFER_REFRESH: + return send(filetransfer.FILETRANSFER_UPDATE, nil) // Screen Events - case event.SCREEN_RESOLUTION: - case event.SCREEN_CONFIGURATIONS: - case event.SCREEN_SET: - request := &message.ScreenResolution{} + case oldEvent.SCREEN_RESOLUTION: + // No WS equivalent, call HTTP API and return screen resolution. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.SCREEN_CONFIGURATIONS: + // No WS equivalent, call HTTP API and return screen configurations. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.SCREEN_SET: + request := &oldMessage.ScreenResolution{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } + return send(event.SCREEN_SET, &message.ScreenSize{ + ScreenSize: types.ScreenSize{ + Width: request.Width, + Height: request.Height, + Rate: request.Rate, + }, + }) + // Broadcast Events - case event.BROADCAST_CREATE: - request := &message.BroadcastCreate{} + case oldEvent.BROADCAST_CREATE: + request := &oldMessage.BroadcastCreate{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.BROADCAST_DESTROY: + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.BROADCAST_DESTROY: + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) // Admin Events - case event.ADMIN_LOCK: - request := &message.AdminLock{} + case oldEvent.ADMIN_LOCK: + request := &oldMessage.AdminLock{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_UNLOCK: - request := &message.AdminLock{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_UNLOCK: + request := &oldMessage.AdminLock{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_CONTROL: - case event.ADMIN_RELEASE: - case event.ADMIN_GIVE: - request := &message.Admin{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_CONTROL: + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_RELEASE: + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_GIVE: + request := &oldMessage.Admin{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_BAN: - request := &message.Admin{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_BAN: + request := &oldMessage.Admin{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_KICK: - request := &message.Admin{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_KICK: + request := &oldMessage.Admin{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_MUTE: - request := &message.Admin{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_MUTE: + request := &oldMessage.Admin{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } - case event.ADMIN_UNMUTE: - request := &message.Admin{} + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + + case oldEvent.ADMIN_UNMUTE: + request := &oldMessage.Admin{} err := json.Unmarshal(msg, request) if err != nil { - return nil, err + return err } + // TODO: No WS equivalent, call HTTP API. + return fmt.Errorf("event not implemented: %s", header.Event) + default: - return nil, fmt.Errorf("unknown event type: %s", header.Event) + return fmt.Errorf("unknown event type: %s", header.Event) } - - return json.Marshal(request) } diff --git a/server/internal/http/legacy/wstoclient.go b/server/internal/http/legacy/wstoclient.go index 002651b8..32085236 100644 --- a/server/internal/http/legacy/wstoclient.go +++ b/server/internal/http/legacy/wstoclient.go @@ -2,161 +2,520 @@ package legacy import ( "encoding/json" + "errors" "fmt" - "github.com/demodesk/neko/internal/http/legacy/event" - "github.com/demodesk/neko/internal/http/legacy/message" + oldEvent "github.com/demodesk/neko/internal/http/legacy/event" + oldMessage "github.com/demodesk/neko/internal/http/legacy/message" + oldTypes "github.com/demodesk/neko/internal/http/legacy/types" + "github.com/pion/webrtc/v3" + + chat "github.com/demodesk/neko/internal/plugins/chat" + filetransfer "github.com/demodesk/neko/internal/plugins/filetransfer" + "github.com/demodesk/neko/pkg/types" + "github.com/demodesk/neko/pkg/types/event" + "github.com/demodesk/neko/pkg/types/message" ) -func (h *LegacyHandler) wsToClient(msg []byte) ([]byte, error) { - header := message.Message{} - err := json.Unmarshal(msg, &header) - if err != nil { - return nil, err +func sessionDataToMember(id string, session message.SessionData) (*oldTypes.Member, error) { + settings := chat.Settings{ + CanSend: true, // defaults to true + CanReceive: true, // defaults to true } - var payload any - switch header.Event { + err := session.Profile.Plugins.Unmarshal(chat.PluginName, &settings) + if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) { + return nil, fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", chat.PluginName, err) + } + + return &oldTypes.Member{ + ID: id, + Name: session.Profile.Name, + Admin: session.Profile.IsAdmin, + Muted: !settings.CanSend, + }, nil +} + +func sendControlHost(request message.ControlHost, send func(payload any) error) error { + if request.HasHost { + if request.ID == request.HostID { + return send(&oldMessage.Control{ + Event: oldEvent.CONTROL_LOCKED, + ID: request.ID, + }) + } + + return send(&oldMessage.ControlTarget{ + Event: oldEvent.CONTROL_GIVE, + ID: request.ID, + Target: request.HostID, + }) + } + + if request.ID != "" { + return send(&oldMessage.Control{ + Event: oldEvent.CONTROL_RELEASE, + ID: request.ID, + }) + } + + return nil +} + +func (s *session) wsToClient(msg []byte, sendMsg func([]byte) error) error { + data := types.WebSocketMessage{} + err := json.Unmarshal(msg, &data) + if err != nil { + return err + } + + send := func(payload any) error { + msg, err := json.Marshal(payload) + if err != nil { + return err + } + + return sendMsg(msg) + } + + switch data.Event { // System Events - case: - payload = &message.SystemMessage{ - Event: event.SYSTEM_DISCONNECT, + case event.SYSTEM_DISCONNECT: + request := &message.SystemDisconnect{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.SystemMessage{ - Event: event.SYSTEM_ERROR, + + return send(&oldMessage.SystemMessage{ + Event: oldEvent.SYSTEM_DISCONNECT, + Message: request.Message, + }) + + // case: + // send(&oldMessage.SystemMessage{ + // Event: oldEvent.SYSTEM_ERROR, + // }) + case event.SYSTEM_INIT: + request := &message.SystemInit{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.SystemInit{ - Event: event.SYSTEM_INIT, + + // + // MembersList + // + + membersList := []*oldTypes.Member{} + for id, session := range request.Sessions { + member, err := sessionDataToMember(id, session) + if err != nil { + return err + } + membersList = append(membersList, member) } + err = send(&oldMessage.MembersList{ + Event: oldEvent.MEMBER_LIST, + Members: membersList, + }) + if err != nil { + return err + } + + // + // ScreenSize + // + + err = send(&oldMessage.ScreenResolution{ + Event: oldEvent.SCREEN_RESOLUTION, + Width: request.ScreenSize.Width, + Height: request.ScreenSize.Height, + Rate: request.ScreenSize.Rate, + }) + if err != nil { + return err + } + + // actually its already set when we create the session + s.id = request.SessionId + + // + // ControlHost + // + + err = sendControlHost(request.ControlHost, send) + if err != nil { + return err + } + + // + // FileTransfer + // + + filetransferSettings := filetransfer.Settings{ + Enabled: true, // defaults to true + } + + err = request.Settings.Plugins.Unmarshal(filetransfer.PluginName, &filetransferSettings) + if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) { + return fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", filetransfer.PluginName, err) + } + + // + // Locks + // + locks := map[string]string{} + if request.Settings.LockedLogins { + locks["login"] = "" // TODO: We don't know who locked the login. + } + if request.Settings.LockedControls { + locks["control"] = "" // TODO: We don't know who locked the control. + } + if !filetransferSettings.Enabled { + locks["filetransfer"] = "" // TODO: We don't know who locked the file transfer. + } + + return send(&oldMessage.SystemInit{ + Event: oldEvent.SYSTEM_INIT, + ImplicitHosting: request.Settings.ImplicitHosting, + Locks: locks, + FileTransfer: true, // TODO: We don't know if file transfer is enabled, we would need to check the global config somehow. + }) + + case event.SYSTEM_ADMIN: + request := &message.SystemAdmin{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err + } + + // + // ScreenSizesList + // + + rates := map[string][]int16{} + for _, size := range request.ScreenSizesList { + key := fmt.Sprintf("%dx%d", size.Width, size.Height) + rates[key] = append(rates[key], size.Rate) + } + + usedScreenSizes := map[string]struct{}{} + screenSizesList := map[int]oldTypes.ScreenConfiguration{} + for i, size := range request.ScreenSizesList { + key := fmt.Sprintf("%dx%d", size.Width, size.Height) + if _, ok := usedScreenSizes[key]; ok { + continue + } + + ratesMap := map[int]int16{} + for i, rate := range rates[key] { + ratesMap[i] = rate + } + + screenSizesList[i] = oldTypes.ScreenConfiguration{ + Width: size.Width, + Height: size.Height, + Rates: ratesMap, + } + } + + err = send(&oldMessage.ScreenConfigurations{ + Event: oldEvent.SCREEN_CONFIGURATIONS, + Configurations: screenSizesList, + }) + if err != nil { + return err + } + + // + // BroadcastStatus + // + + return send(&oldMessage.BroadcastStatus{ + Event: oldEvent.BROADCAST_STATUS, + URL: request.BroadcastStatus.URL, + IsActive: request.BroadcastStatus.IsActive, + }) + // Member Events - case: - payload = &message.MembersList{ - Event: event.MEMBER_LIST, + + // TODO: This is on Created but old API wants OnConnected. + case event.SESSION_CREATED: + request := &message.SessionData{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.Member{ - Event: event.MEMBER_CONNECTED, + + member, err := sessionDataToMember(request.ID, *request) + if err != nil { + return err } - case: - payload = &message.MemberDisconnected{ - Event: event.MEMBER_DISCONNECTED, + + return send(&oldMessage.Member{ + Event: oldEvent.MEMBER_CONNECTED, + Member: member, + }) + + // TODO: This is on Deleted but old API wants OnDisconnected. + case event.SESSION_DELETED: + request := &message.SessionID{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } + return send(&oldMessage.MemberDisconnected{ + Event: oldEvent.MEMBER_DISCONNECTED, + ID: request.ID, + }) + + // TODO: This would need some context to know it message has been sent already/to get session data on connect. + //case event.SESSION_STATE: + // request := &message.SessionState{} + // err := json.Unmarshal(data.Payload, request) + // if err != nil { + // return err + // } + // if request.IsConnected { + // // oldEvent.MEMBER_CONNECTED if not sent already + // } else { + // // oldEvent.MEMBER_DISCONNECTED if nor sent already + // } + // Signal Events - case: - payload = &message.SignalOffer{ - Event: event.SIGNAL_OFFER, - } - case: - payload = &message.SignalAnswer{ - Event: event.SIGNAL_ANSWER, - } - case: - payload = &message.SignalCandidate{ - Event: event.SIGNAL_CANDIDATE, - } - case: - payload = &message.SignalProvide{ - Event: event.SIGNAL_PROVIDE, + case event.SIGNAL_OFFER: + request := &message.SignalDescription{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } + return send(&oldMessage.SignalOffer{ + Event: oldEvent.SIGNAL_OFFER, + SDP: request.SDP, + }) + + case event.SIGNAL_ANSWER: + request := &message.SignalDescription{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err + } + + return send(&oldMessage.SignalAnswer{ + Event: oldEvent.SIGNAL_ANSWER, + DisplayName: s.profile.Name, // DisplayName + SDP: request.SDP, + }) + + case event.SIGNAL_CANDIDATE: + request := &message.SignalCandidate{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err + } + + json, err := json.Marshal(request.ICECandidateInit) + if err != nil { + return err + } + + return send(&oldMessage.SignalCandidate{ + Event: oldEvent.SIGNAL_CANDIDATE, + Data: string(json), + }) + + case event.SIGNAL_PROVIDE: + request := &message.SignalProvide{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err + } + + iceServers := []webrtc.ICEServer{} + for _, ice := range request.ICEServers { + iceServers = append(iceServers, webrtc.ICEServer{ + URLs: ice.URLs, + Username: ice.Username, + Credential: ice.Credential, + CredentialType: webrtc.ICECredentialTypePassword, + }) + } + + return send(&oldMessage.SignalProvide{ + Event: oldEvent.SIGNAL_PROVIDE, + ID: s.id, // SessionId + SDP: request.SDP, + Lite: len(iceServers) == 0, // if no ICE servers are provided, it's a lite offer + ICE: iceServers, + }) + // Control Events - case: - payload = &message.Clipboard{ - Event: event.CONTROL_CLIPBOARD, + case event.CLIPBOARD_UPDATED: + request := &message.ClipboardData{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.Control{ - Event: event.CONTROL_REQUEST, + + return send(&oldMessage.Clipboard{ + Event: oldEvent.CONTROL_CLIPBOARD, + Text: request.Text, + }) + + case event.CONTROL_HOST: + request := &message.ControlHost{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.Control{ - Event: event.CONTROL_REQUESTING, + + return sendControlHost(*request, send) + + case event.CONTROL_REQUEST: + request := &message.SessionID{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - case: - payload = &message.ControlTarget{ - Event: event.CONTROL_GIVE, - } // message.AdminTarget - case: - payload = &message.Control{ - Event: event.CONTROL_RELEASE, - } - case: - payload = &message.Control{ - Event: event.CONTROL_LOCKED, + + if s.id == request.ID { + // if i am the one that is requesting, send CONTROL_REQUEST to me + return send(&oldMessage.Control{ + Event: oldEvent.CONTROL_REQUEST, + ID: request.ID, + }) + } else { + // if not, let me know someone else is requesting + return send(&oldMessage.Control{ + Event: oldEvent.CONTROL_REQUESTING, + ID: request.ID, + }) } // Chat Events - case: - payload = &message.ChatSend{ - Event: event.CHAT_MESSAGE, - } - case: - payload = &message.EmoteSend{ - Event: event.CHAT_EMOTE, + case chat.CHAT_MESSAGE: + request := &chat.Message{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } + return send(&oldMessage.ChatSend{ + Event: oldEvent.CHAT_MESSAGE, + ID: request.ID, + Content: request.Content.Text, + }) + + // TODO: emotes. + //case: + // send(&oldMessage.EmoteSend{ + // Event: oldEvent.CHAT_EMOTE, + // }) + // File Transfer Events - case: - payload = &message.FileTransferList{ - Event: event.FILETRANSFER_LIST, + case filetransfer.FILETRANSFER_UPDATE: + request := &filetransfer.Message{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } + files := []oldTypes.FileListItem{} + for _, file := range request.Files { + var itemType string + switch file.Type { + case filetransfer.ItemTypeFile: + itemType = "file" + case filetransfer.ItemTypeDir: + itemType = "dir" + } + files = append(files, oldTypes.FileListItem{ + Filename: file.Name, + Type: itemType, + Size: file.Size, + }) + } + + return send(&oldMessage.FileTransferList{ + Event: oldEvent.FILETRANSFER_LIST, + Cwd: request.RootDir, + Files: files, + }) + // Screen Events - case: - payload = &message.ScreenResolution{ - Event: event.SCREEN_RESOLUTION, - } - case: - payload = &message.ScreenConfigurations{ - Event: event.SCREEN_CONFIGURATIONS, + case event.SCREEN_UPDATED: + request := &message.ScreenSizeUpdate{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } + return send(&oldMessage.ScreenResolution{ + Event: oldEvent.SCREEN_RESOLUTION, + ID: request.ID, + Width: request.ScreenSize.Width, + Height: request.ScreenSize.Height, + Rate: request.ScreenSize.Rate, + }) + // Broadcast Events - case: - payload = &message.BroadcastStatus{ - Event: event.BROADCAST_STATUS, + case event.BROADCAST_STATUS: + request := &message.BroadcastStatus{} + err := json.Unmarshal(data.Payload, request) + if err != nil { + return err } - // Admin Events - case: - payload = &message.AdminLock{ - Event: event.ADMIN_UNLOCK, - } - case: - payload = &message.AdminLock{ - Event: event.ADMIN_LOCK, - } - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_CONTROL, - } // message.Admin - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_RELEASE, - } // message.Admin - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_MUTE, - } - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_UNMUTE, - } - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_KICK, - } - case: - payload = &message.AdminTarget{ - Event: event.ADMIN_BAN, - } + return send(&oldMessage.BroadcastStatus{ + Event: oldEvent.BROADCAST_STATUS, + URL: request.URL, + IsActive: request.IsActive, + }) + + /* + // Admin Events + case: + send(&oldMessage.AdminLock{ + Event: oldEvent.ADMIN_UNLOCK, + }) + case: + send(&oldMessage.AdminLock{ + Event: oldEvent.ADMIN_LOCK, + }) + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_CONTROL, + } // )oldMessage.Admin + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_RELEASE, + } // )oldMessage.Admin + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_MUTE, + }) + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_UNMUTE, + }) + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_KICK, + }) + case: + send(&oldMessage.AdminTarget{ + Event: oldEvent.ADMIN_BAN, + }) + */ + + case event.SYSTEM_HEARTBEAT: + return nil + default: - return nil, fmt.Errorf("unknown event type: %s", header.Event) + return fmt.Errorf("unknown event type: %s", data.Event) } - - return json.Marshal(payload) }