private mode implementation.

This commit is contained in:
Miroslav Šedivý 2022-03-26 23:20:38 +01:00
parent f549171ded
commit d004ddd68f
12 changed files with 160 additions and 11 deletions

View File

@ -4,6 +4,8 @@ import (
"context"
"net/http"
"github.com/rs/zerolog/log"
"gitlab.com/demodesk/neko/server/pkg/auth"
"gitlab.com/demodesk/neko/server/pkg/types"
"gitlab.com/demodesk/neko/server/pkg/utils"
@ -13,6 +15,8 @@ type RoomHandler struct {
sessions types.SessionManager
desktop types.DesktopManager
capture types.CaptureManager
privateModeImage []byte
}
func New(
@ -20,13 +24,32 @@ func New(
desktop types.DesktopManager,
capture types.CaptureManager,
) *RoomHandler {
// Init
return &RoomHandler{
h := &RoomHandler{
sessions: sessions,
desktop: desktop,
capture: capture,
}
// generate fallback image for private mode when needed
sessions.OnPrivateModeChanged(func(isPrivateMode bool) {
if !isPrivateMode {
log.Debug().Msg("clearing private mode fallback image")
h.privateModeImage = nil
return
}
img := h.desktop.GetScreenshotImage()
bytes, err := utils.CreateJPGImage(img, 90)
if err != nil {
log.Err(err).Msg("could not generate private mode fallback image")
return
}
log.Debug().Msg("using private mode fallback image")
h.privateModeImage = bytes
})
return h
}
func (h *RoomHandler) Route(r types.Router) {

View File

@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"gitlab.com/demodesk/neko/server/pkg/auth"
"gitlab.com/demodesk/neko/server/pkg/types"
"gitlab.com/demodesk/neko/server/pkg/types/event"
"gitlab.com/demodesk/neko/server/pkg/types/message"
@ -81,6 +82,19 @@ func (h *RoomHandler) screenShotGet(w http.ResponseWriter, r *http.Request) erro
}
func (h *RoomHandler) screenCastGet(w http.ResponseWriter, r *http.Request) error {
// display fallback image when private mode is enabled even if screencast is not
if session, ok := auth.GetSession(r); ok && session.PrivateModeEnabled() {
if h.privateModeImage != nil {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Content-Type", "image/jpeg")
_, err := w.Write(h.privateModeImage)
return err
}
return utils.HttpBadRequest("private mode is enabled but no fallback image available")
}
screencast := h.capture.Screencast()
if !screencast.Enabled() {
return utils.HttpBadRequest("screencast pipeline is not enabled")

View File

@ -56,6 +56,9 @@ type SessionManagerCtx struct {
host types.Session
hostMu sync.Mutex
privateMode bool
privateModeMu sync.Mutex
cursors map[types.Session][]types.Cursor
cursorsMu sync.Mutex
@ -197,6 +200,39 @@ func (manager *SessionManagerCtx) ClearHost() {
manager.SetHost(nil)
}
// ---
// private mode
// ---
func (manager *SessionManagerCtx) SetPrivateMode(isPrivateMode bool) {
manager.privateModeMu.Lock()
// only if value changed
if manager.privateMode == isPrivateMode {
manager.privateModeMu.Unlock()
return
}
// update webrtc paused state for all sessions
for _, session := range manager.List() {
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
webrtcPeer.SetPaused(isPrivateMode && !session.Profile().IsAdmin)
}
}
manager.privateMode = isPrivateMode
manager.privateModeMu.Unlock()
manager.emmiter.Emit("private_mode_changed", isPrivateMode)
}
func (manager *SessionManagerCtx) PrivateMode() bool {
manager.privateModeMu.Lock()
defer manager.privateModeMu.Unlock()
return manager.privateMode
}
// ---
// cursors
// ---
@ -326,6 +362,12 @@ func (manager *SessionManagerCtx) OnHostChanged(listener func(session types.Sess
})
}
func (manager *SessionManagerCtx) OnPrivateModeChanged(listener func(isPrivateMode bool)) {
manager.emmiter.On("private_mode_changed", func(payload ...interface{}) {
listener(payload[0].(bool))
})
}
// ---
// config
// ---

View File

@ -44,6 +44,11 @@ func (session *SessionCtx) profileChanged() {
if (!session.profile.CanConnect || !session.profile.CanLogin) && session.state.IsConnected {
session.GetWebSocketPeer().Destroy("profile changed")
}
// update webrtc paused state
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
webrtcPeer.SetPaused(session.PrivateModeEnabled())
}
}
func (session *SessionCtx) State() types.SessionState {
@ -54,6 +59,10 @@ func (session *SessionCtx) IsHost() bool {
return session.manager.GetHost() == session
}
func (session *SessionCtx) PrivateModeEnabled() bool {
return session.manager.PrivateMode() && !session.profile.IsAdmin
}
func (session *SessionCtx) SetCursor(cursor types.Cursor) {
if session.manager.InactiveCursors() && session.profile.SendsInactiveCursor {
session.manager.SetCursor(cursor, session)

View File

@ -200,6 +200,12 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
return videoTrack.SetStream(videoStream)
},
setPaused: func(isPaused bool) {
videoTrack.SetPaused(isPaused)
audioTrack.SetPaused(isPaused)
// TODO: Send fresh cursor position & image when unpausing.
},
iceTrickle: manager.config.ICETrickle,
}
@ -314,12 +320,22 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
})
cursorImage := func(entry *cursor.ImageEntry) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if err := peer.SendCursorImage(entry.Cursor, entry.Image); err != nil {
logger.Err(err).Msg("could not send cursor image")
}
}
cursorPosition := func(x, y int) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if session.IsHost() {
return
}
@ -352,6 +368,11 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
})
dataChannel.OnMessage(func(message webrtc.DataChannelMessage) {
// TODO: Refactor.
if videoTrack.paused {
return
}
if err := manager.handle(message.Data, session); err != nil {
logger.Err(err).Msg("data handle failed")
}

View File

@ -15,6 +15,7 @@ type WebRTCPeerCtx struct {
connection *webrtc.PeerConnection
dataChannel *webrtc.DataChannel
changeVideo func(videoID string) error
setPaused func(isPaused bool)
iceTrickle bool
}
@ -122,6 +123,19 @@ func (peer *WebRTCPeerCtx) SetVideoID(videoID string) error {
return peer.changeVideo(videoID)
}
func (peer *WebRTCPeerCtx) SetPaused(isPaused bool) error {
peer.mu.Lock()
defer peer.mu.Unlock()
if peer.connection == nil {
return types.ErrWebRTCConnectionNotFound
}
peer.logger.Info().Bool("is_paused", isPaused).Msg("set paused")
peer.setPaused(isPaused)
return nil
}
func (peer *WebRTCPeerCtx) Destroy() {
peer.mu.Lock()
defer peer.mu.Unlock()

View File

@ -26,12 +26,17 @@ func (manager *WebRTCManagerCtx) newPeerStreamTrack(stream types.StreamSinkManag
peer := &PeerStreamTrack{
logger: logger,
track: track,
listener: func(sample types.Sample) {
err := track.WriteSample(media.Sample(sample))
if err != nil && errors.Is(err, io.ErrClosedPipe) {
logger.Warn().Err(err).Msg("pipeline failed to write")
}
},
}
peer.listener = func(sample types.Sample) {
if peer.paused {
return
}
err := track.WriteSample(media.Sample(sample))
if err != nil && errors.Is(err, io.ErrClosedPipe) {
logger.Warn().Err(err).Msg("pipeline failed to write")
}
}
err = peer.SetStream(stream)
@ -41,6 +46,7 @@ func (manager *WebRTCManagerCtx) newPeerStreamTrack(stream types.StreamSinkManag
type PeerStreamTrack struct {
logger zerolog.Logger
track *webrtc.TrackLocalStaticSample
paused bool
listener func(sample types.Sample)
stream types.StreamSinkManager
@ -92,3 +98,7 @@ func (peer *PeerStreamTrack) AddToConnection(connection *webrtc.PeerConnection)
return nil
}
func (peer *PeerStreamTrack) SetPaused(paused bool) {
peer.paused = paused
}

View File

@ -17,7 +17,7 @@ var (
)
func (h *MessageHandlerCtx) controlRelease(session types.Session) error {
if !session.Profile().CanHost {
if !session.Profile().CanHost || session.PrivateModeEnabled() {
return ErrIsNotAllowedToHost
}
@ -32,7 +32,7 @@ func (h *MessageHandlerCtx) controlRelease(session types.Session) error {
}
func (h *MessageHandlerCtx) controlRequest(session types.Session) error {
if !session.Profile().CanHost {
if !session.Profile().CanHost || session.PrivateModeEnabled() {
return ErrIsNotAllowedToHost
}

View File

@ -24,6 +24,11 @@ func (h *MessageHandlerCtx) signalRequest(session types.Session, payload *messag
return err
}
// set webrtc as paused if session has private mode enabled
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil && session.PrivateModeEnabled() {
webrtcPeer.SetPaused(true)
}
session.Send(
event.SIGNAL_PROVIDE,
message.SignalProvide{

View File

@ -54,6 +54,10 @@ func CanHostOnly(w http.ResponseWriter, r *http.Request) (context.Context, error
return nil, utils.HttpForbidden("session cannot host")
}
if session.PrivateModeEnabled() {
return nil, utils.HttpUnprocessableEntity("private mode is enabled")
}
return nil, nil
}

View File

@ -27,6 +27,7 @@ type Session interface {
Profile() MemberProfile
State() SessionState
IsHost() bool
PrivateModeEnabled() bool
// cursor
SetCursor(cursor Cursor)
@ -55,6 +56,9 @@ type SessionManager interface {
GetHost() Session
ClearHost()
SetPrivateMode(isPrivateMode bool)
PrivateMode() bool
SetCursor(cursor Cursor, session Session)
PopCursors() map[Session][]Cursor
@ -69,6 +73,7 @@ type SessionManager interface {
OnProfileChanged(listener func(session Session))
OnStateChanged(listener func(session Session))
OnHostChanged(listener func(session Session))
OnPrivateModeChanged(listener func(isPrivateMode bool))
ImplicitHosting() bool
InactiveCursors() bool

View File

@ -26,6 +26,8 @@ type WebRTCPeer interface {
SetCandidate(candidate webrtc.ICECandidateInit) error
SetVideoID(videoID string) error
SetPaused(isPaused bool) error
SendCursorPosition(x, y int) error
SendCursorImage(cur *CursorImage, img []byte) error