diff --git a/internal/api/room/handler.go b/internal/api/room/handler.go index ba33d915..378cb502 100644 --- a/internal/api/room/handler.go +++ b/internal/api/room/handler.go @@ -31,7 +31,7 @@ func New( } // generate fallback image for private mode when needed - sessions.OnSettingsChanged(func(new types.Settings, old types.Settings) { + sessions.OnSettingsChanged(func(session types.Session, new types.Settings, old types.Settings) { if old.PrivateMode && !new.PrivateMode { log.Debug().Msg("clearing private mode fallback image") h.privateModeImage = nil diff --git a/internal/api/room/settings.go b/internal/api/room/settings.go index 33e41552..c8e1816a 100644 --- a/internal/api/room/settings.go +++ b/internal/api/room/settings.go @@ -1,8 +1,12 @@ package room import ( + "encoding/json" + "io" "net/http" + "github.com/demodesk/neko/pkg/auth" + "github.com/demodesk/neko/pkg/types" "github.com/demodesk/neko/pkg/utils" ) @@ -12,13 +16,23 @@ func (h *RoomHandler) settingsGet(w http.ResponseWriter, r *http.Request) error } func (h *RoomHandler) settingsSet(w http.ResponseWriter, r *http.Request) error { - settings := h.sessions.Settings() + session, _ := auth.GetSession(r) - if err := utils.HttpJsonRequest(w, r, &settings); err != nil { - return err + // We read the request body first and unmashal it inside the UpdateSettingsFunc + // to ensure atomicity of the operation. + body, err := io.ReadAll(r.Body) + if err != nil { + return utils.HttpBadRequest("unable to read request body").WithInternalErr(err) } - h.sessions.UpdateSettings(settings) + h.sessions.UpdateSettingsFunc(session, func(settings *types.Settings) bool { + err = json.Unmarshal(body, settings) + return err == nil + }) + + if err != nil { + return utils.HttpBadRequest("unable to parse provided data").WithInternalErr(err) + } return utils.HttpSuccess(w) } diff --git a/internal/session/manager.go b/internal/session/manager.go index 7a324b50..c46d6452 100644 --- a/internal/session/manager.go +++ b/internal/session/manager.go @@ -367,9 +367,9 @@ func (manager *SessionManagerCtx) OnHostChanged(listener func(session types.Sess }) } -func (manager *SessionManagerCtx) OnSettingsChanged(listener func(new types.Settings, old types.Settings)) { +func (manager *SessionManagerCtx) OnSettingsChanged(listener func(session types.Session, new types.Settings, old types.Settings)) { manager.emmiter.On("settings_changed", func(payload ...any) { - listener(payload[0].(types.Settings), payload[1].(types.Settings)) + listener(payload[0].(types.Session), payload[1].(types.Settings), payload[2].(types.Settings)) }) } @@ -377,29 +377,20 @@ func (manager *SessionManagerCtx) OnSettingsChanged(listener func(new types.Sett // settings // --- -func (manager *SessionManagerCtx) UpdateSettings(new types.Settings) { - manager.settingsMu.Lock() - old := manager.settings - manager.settings = new - manager.settingsMu.Unlock() - - manager.updateSettings(new, old) -} - -func (manager *SessionManagerCtx) UpdateSettingsFunc(f func(settings *types.Settings) bool) { +func (manager *SessionManagerCtx) UpdateSettingsFunc(session types.Session, f func(settings *types.Settings) bool) { manager.settingsMu.Lock() new := manager.settings if f(&new) { old := manager.settings manager.settings = new manager.settingsMu.Unlock() - manager.updateSettings(new, old) + manager.updateSettings(session, new, old) return } manager.settingsMu.Unlock() } -func (manager *SessionManagerCtx) updateSettings(new, old types.Settings) { +func (manager *SessionManagerCtx) updateSettings(session types.Session, new, old types.Settings) { // if private mode changed if old.PrivateMode != new.PrivateMode { // update webrtc paused state for all sessions @@ -447,7 +438,7 @@ func (manager *SessionManagerCtx) updateSettings(new, old types.Settings) { } } - manager.emmiter.Emit("settings_changed", new, old) + manager.emmiter.Emit("settings_changed", session, new, old) } func (manager *SessionManagerCtx) Settings() types.Settings { diff --git a/internal/websocket/handler/session.go b/internal/websocket/handler/session.go index c81f181e..3b1c5bd5 100644 --- a/internal/websocket/handler/session.go +++ b/internal/websocket/handler/session.go @@ -39,7 +39,7 @@ func (h *MessageHandlerCtx) SessionConnected(session types.Session) error { } // update settings in atomic way - h.sessions.UpdateSettingsFunc(func(settings *types.Settings) bool { + h.sessions.UpdateSettingsFunc(session, func(settings *types.Settings) bool { // if control protection & locked controls: unlock controls if settings.LockedControls && settings.ControlProtection { settings.LockedControls = false @@ -70,7 +70,7 @@ func (h *MessageHandlerCtx) SessionDisconnected(session types.Session) error { }) // update settings in atomic way - h.sessions.UpdateSettingsFunc(func(settings *types.Settings) bool { + h.sessions.UpdateSettingsFunc(session, func(settings *types.Settings) bool { // if control protection & not locked controls & no admin: lock controls if !settings.LockedControls && settings.ControlProtection && !hasAdmin { settings.LockedControls = true diff --git a/internal/websocket/manager.go b/internal/websocket/manager.go index 9443aa6a..f04baf34 100644 --- a/internal/websocket/manager.go +++ b/internal/websocket/manager.go @@ -127,7 +127,7 @@ func (manager *WebSocketManagerCtx) Start() { Msg("session host changed") }) - manager.sessions.OnSettingsChanged(func(new types.Settings, old types.Settings) { + manager.sessions.OnSettingsChanged(func(session types.Session, new types.Settings, old types.Settings) { // start inactive cursors if new.InactiveCursors && !old.InactiveCursors { manager.startInactiveCursors() @@ -138,8 +138,13 @@ func (manager *WebSocketManagerCtx) Start() { manager.stopInactiveCursors() } - manager.sessions.Broadcast(event.SYSTEM_SETTINGS, new) + manager.sessions.Broadcast(event.SYSTEM_SETTINGS, message.SystemSettingsUpdate{ + ID: session.ID(), + Settings: new, + }) + manager.logger.Info(). + Str("session_id", session.ID()). Interface("new", new). Interface("old", old). Msg("settings changed") diff --git a/pkg/auth/auth_test.go b/pkg/auth/auth_test.go index fa9b89e3..d4b082df 100644 --- a/pkg/auth/auth_test.go +++ b/pkg/auth/auth_test.go @@ -224,9 +224,11 @@ func TestCanHostOnly(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - settings := sessionManager.Settings() - settings.PrivateMode = tt.privateMode - sessionManager.UpdateSettings(settings) + session, _ := GetSession(tt.r) + sessionManager.UpdateSettingsFunc(session, func(s *types.Settings) bool { + s.PrivateMode = tt.privateMode + return true + }) _, err := CanHostOnly(nil, tt.r) if (err != nil) != tt.wantErr { diff --git a/pkg/types/message/messages.go b/pkg/types/message/messages.go index 3ba1ab19..e127d563 100644 --- a/pkg/types/message/messages.go +++ b/pkg/types/message/messages.go @@ -42,6 +42,11 @@ type SystemDisconnect struct { Message string `json:"message"` } +type SystemSettingsUpdate struct { + ID string `json:"id"` + types.Settings +} + ///////////////////////////// // Signal ///////////////////////////// diff --git a/pkg/types/session.go b/pkg/types/session.go index 49d7c681..300a86e0 100644 --- a/pkg/types/session.go +++ b/pkg/types/session.go @@ -101,10 +101,9 @@ type SessionManager interface { OnProfileChanged(listener func(session Session)) OnStateChanged(listener func(session Session)) OnHostChanged(listener func(session Session)) - OnSettingsChanged(listener func(new Settings, old Settings)) + OnSettingsChanged(listener func(session Session, new Settings, old Settings)) - UpdateSettings(Settings) - UpdateSettingsFunc(f func(settings *Settings) bool) + UpdateSettingsFunc(session Session, f func(settings *Settings) bool) Settings() Settings CookieEnabled() bool