neko/server/internal/http/legacy/wstoclient.go
2024-07-22 23:30:45 +02:00

642 lines
14 KiB
Go

package legacy
import (
"encoding/json"
"errors"
"fmt"
"github.com/pion/webrtc/v3"
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/demodesk/neko/internal/plugins/chat"
"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 sessionDataToMember(id string, session message.SessionData) (*oldTypes.Member, error) {
settings := chat.Settings{
CanSend: true, // defaults to true
CanReceive: true, // defaults to true
}
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 (s *session) sendControlHost(request message.ControlHost) error {
lastHostID := s.lastHostID
if request.HasHost {
s.lastHostID = request.ID
if request.ID == request.HostID {
if request.ID == lastHostID || lastHostID == "" {
return s.toClient(&oldMessage.Control{
Event: oldEvent.CONTROL_LOCKED,
ID: request.HostID,
})
} else {
return s.toClient(&oldMessage.AdminTarget{
Event: oldEvent.ADMIN_CONTROL,
ID: request.ID,
Target: lastHostID,
})
}
} else {
return s.toClient(&oldMessage.ControlTarget{
Event: oldEvent.CONTROL_GIVE,
ID: request.HostID,
Target: request.ID,
})
}
}
if request.ID != "" {
s.lastHostID = ""
if request.ID == lastHostID {
return s.toClient(&oldMessage.Control{
Event: oldEvent.CONTROL_RELEASE,
ID: request.ID,
})
} else {
return s.toClient(&oldMessage.Control{
Event: oldEvent.ADMIN_RELEASE,
ID: request.ID,
})
}
}
return nil
}
func (s *session) wsToClient(msg []byte) error {
data := types.WebSocketMessage{}
err := json.Unmarshal(msg, &data)
if err != nil {
return err
}
switch data.Event {
// System Events
case event.SYSTEM_DISCONNECT:
request := &message.SystemDisconnect{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&oldMessage.SystemMessage{
Event: oldEvent.SYSTEM_DISCONNECT,
Message: request.Message,
})
case event.SYSTEM_INIT:
request := &message.SystemInit{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
//
// MembersList
//
membersList := []*oldTypes.Member{}
s.sessions = map[string]*oldTypes.Member{}
for id, session := range request.Sessions {
if !session.State.IsConnected {
continue
}
member, err := sessionDataToMember(id, session)
if err != nil {
return err
}
membersList = append(membersList, member)
s.sessions[id] = member
}
err = s.toClient(&oldMessage.MembersList{
Event: oldEvent.MEMBER_LIST,
Members: membersList,
})
if err != nil {
return err
}
//
// ScreenSize
//
err = s.toClient(&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 = s.sendControlHost(request.ControlHost)
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.
s.lockedLogins = true
}
if request.Settings.LockedControls {
locks["control"] = "" // TODO: We don't know who locked the control.
s.lockedControls = true
}
if !filetransferSettings.Enabled {
locks["file_transfer"] = "" // TODO: We don't know who locked the file transfer.
s.lockedFileTransfer = true
}
return s.toClient(&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 = s.toClient(&oldMessage.ScreenConfigurations{
Event: oldEvent.SCREEN_CONFIGURATIONS,
Configurations: screenSizesList,
})
if err != nil {
return err
}
//
// BroadcastStatus
//
return s.toClient(&oldMessage.BroadcastStatus{
Event: oldEvent.BROADCAST_STATUS,
URL: request.BroadcastStatus.URL,
IsActive: request.BroadcastStatus.IsActive,
})
// Member Events
case event.SESSION_CREATED:
request := &message.SessionData{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
member, err := sessionDataToMember(request.ID, *request)
if err != nil {
return err
}
// only save session - will be notified on connect
s.sessions[request.ID] = member
return nil
case event.SESSION_DELETED:
request := &message.SessionID{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
// only continue if session is in the list - should have been already removed
if _, ok := s.sessions[request.ID]; !ok {
return nil
}
delete(s.sessions, request.ID)
return s.toClient(&oldMessage.MemberDisconnected{
Event: oldEvent.MEMBER_DISCONNECTED,
ID: request.ID,
})
case event.SESSION_STATE:
request := &message.SessionState{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
member, ok := s.sessions[request.ID]
if !ok {
return nil
}
if request.IsConnected && member != nil {
s.sessions[request.ID] = nil
// oldEvent.MEMBER_CONNECTED if not sent already
return s.toClient(&oldMessage.Member{
Event: oldEvent.MEMBER_CONNECTED,
Member: member,
})
}
if !request.IsConnected {
delete(s.sessions, request.ID)
// oldEvent.MEMBER_DISCONNECTED if nor sent already
return s.toClient(&oldMessage.MemberDisconnected{
Event: oldEvent.MEMBER_DISCONNECTED,
ID: request.ID,
})
}
return nil
// Signal Events
case event.SIGNAL_OFFER:
request := &message.SignalDescription{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&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 s.toClient(&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 s.toClient(&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 s.toClient(&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 event.CLIPBOARD_UPDATED:
request := &message.ClipboardData{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&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
}
return s.sendControlHost(*request)
case event.CONTROL_REQUEST:
request := &message.SessionID{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
if s.id == request.ID {
// if i am the one that is requesting, send CONTROL_REQUEST to me
return s.toClient(&oldMessage.Control{
Event: oldEvent.CONTROL_REQUEST,
ID: request.ID,
})
} else {
// if not, let me know someone else is requesting
return s.toClient(&oldMessage.Control{
Event: oldEvent.CONTROL_REQUESTING,
ID: request.ID,
})
}
// Chat Events
case chat.CHAT_MESSAGE:
request := &chat.Message{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&oldMessage.ChatSend{
Event: oldEvent.CHAT_MESSAGE,
ID: request.ID,
Content: request.Content.Text,
})
case event.SEND_BROADCAST:
request := &message.SendBroadcast{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
if request.Subject == "emote" {
return s.toClient(&oldMessage.EmoteSend{
Event: oldEvent.CHAT_EMOTE,
ID: request.Sender,
Emote: request.Body.(string),
})
}
return nil
// File Transfer Events
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 s.toClient(&oldMessage.FileTransferList{
Event: oldEvent.FILETRANSFER_LIST,
Cwd: request.RootDir,
Files: files,
})
// Screen Events
case event.SCREEN_UPDATED:
request := &message.ScreenSizeUpdate{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&oldMessage.ScreenResolution{
Event: oldEvent.SCREEN_RESOLUTION,
ID: request.ID,
Width: request.ScreenSize.Width,
Height: request.ScreenSize.Height,
Rate: request.ScreenSize.Rate,
})
// Broadcast Events
case event.BROADCAST_STATUS:
request := &message.BroadcastStatus{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
return s.toClient(&oldMessage.BroadcastStatus{
Event: oldEvent.BROADCAST_STATUS,
URL: request.URL,
IsActive: request.IsActive,
})
// Admin Events
case event.SYSTEM_SETTINGS:
request := &message.SystemSettingsUpdate{}
err := json.Unmarshal(data.Payload, request)
if err != nil {
return err
}
if s.lockedControls != request.LockedControls {
s.lockedControls = request.LockedControls
if request.LockedControls {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_LOCK,
Resource: "control",
})
} else {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_UNLOCK,
Resource: "control",
})
}
if err != nil {
return err
}
}
if s.lockedLogins != request.LockedLogins {
s.lockedLogins = request.LockedLogins
if request.LockedLogins {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_LOCK,
Resource: "login",
})
} else {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_UNLOCK,
Resource: "login",
})
}
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)
}
if s.lockedFileTransfer != !filetransferSettings.Enabled {
s.lockedFileTransfer = !filetransferSettings.Enabled
if !filetransferSettings.Enabled {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_LOCK,
Resource: "file_transfer",
})
} else {
err = s.toClient(&oldMessage.AdminLock{
Event: oldEvent.ADMIN_UNLOCK,
Resource: "file_transfer",
})
}
if err != nil {
return err
}
}
return nil
/*
case:
s.toClient(&oldMessage.AdminTarget{
Event: oldEvent.ADMIN_BAN,
})
case:
s.toClient(&oldMessage.AdminTarget{
Event: oldEvent.ADMIN_KICK,
})
case:
s.toClient(&oldMessage.AdminTarget{
Event: oldEvent.ADMIN_MUTE,
})
case:
s.toClient(&oldMessage.AdminTarget{
Event: oldEvent.ADMIN_UNMUTE,
})
*/
case event.SYSTEM_HEARTBEAT:
return nil
default:
return fmt.Errorf("unknown event type: %s", data.Event)
}
}