mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
move server to server directory.
This commit is contained in:
80
server/internal/session/auth.go
Normal file
80
server/internal/session/auth.go
Normal file
@ -0,0 +1,80 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/demodesk/neko/pkg/types"
|
||||
)
|
||||
|
||||
func (manager *SessionManagerCtx) CookieSetToken(w http.ResponseWriter, token string) {
|
||||
sameSite := http.SameSiteDefaultMode
|
||||
if manager.config.CookieSecure {
|
||||
sameSite = http.SameSiteNoneMode
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: manager.config.CookieName,
|
||||
Value: token,
|
||||
Expires: time.Now().Add(manager.config.CookieExpiration),
|
||||
Secure: manager.config.CookieSecure,
|
||||
SameSite: sameSite,
|
||||
HttpOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) CookieClearToken(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie(manager.config.CookieName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cookie.Value = ""
|
||||
cookie.Expires = time.Unix(0, 0)
|
||||
http.SetCookie(w, cookie)
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Authenticate(r *http.Request) (types.Session, error) {
|
||||
token, ok := manager.getToken(r)
|
||||
if !ok {
|
||||
return nil, errors.New("no authentication provided")
|
||||
}
|
||||
|
||||
session, ok := manager.GetByToken(token)
|
||||
if !ok {
|
||||
return nil, types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
if !session.Profile().CanLogin {
|
||||
return nil, types.ErrSessionLoginDisabled
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) getToken(r *http.Request) (string, bool) {
|
||||
if manager.CookieEnabled() {
|
||||
// get from Cookie
|
||||
cookie, err := r.Cookie(manager.config.CookieName)
|
||||
if err == nil {
|
||||
return cookie.Value, true
|
||||
}
|
||||
}
|
||||
|
||||
// get from Header
|
||||
reqToken := r.Header.Get("Authorization")
|
||||
splitToken := strings.Split(reqToken, "Bearer ")
|
||||
if len(splitToken) == 2 {
|
||||
return strings.TrimSpace(splitToken[1]), true
|
||||
}
|
||||
|
||||
// get from URL
|
||||
token := r.URL.Query().Get("token")
|
||||
if token != "" {
|
||||
return token, true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
470
server/internal/session/manager.go
Normal file
470
server/internal/session/manager.go
Normal file
@ -0,0 +1,470 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/demodesk/neko/internal/config"
|
||||
"github.com/demodesk/neko/pkg/types"
|
||||
"github.com/demodesk/neko/pkg/utils"
|
||||
)
|
||||
|
||||
func New(config *config.Session) *SessionManagerCtx {
|
||||
manager := &SessionManagerCtx{
|
||||
logger: log.With().Str("module", "session").Logger(),
|
||||
config: config,
|
||||
settings: types.Settings{
|
||||
PrivateMode: config.PrivateMode,
|
||||
LockedLogins: config.LockedLogins,
|
||||
LockedControls: config.LockedControls || config.ControlProtection,
|
||||
ControlProtection: config.ControlProtection,
|
||||
ImplicitHosting: config.ImplicitHosting,
|
||||
InactiveCursors: config.InactiveCursors,
|
||||
MercifulReconnect: config.MercifulReconnect,
|
||||
},
|
||||
tokens: make(map[string]string),
|
||||
sessions: make(map[string]*SessionCtx),
|
||||
cursors: make(map[types.Session][]types.Cursor),
|
||||
emmiter: events.New(),
|
||||
}
|
||||
|
||||
// create API session
|
||||
if config.APIToken != "" {
|
||||
manager.apiSession = &SessionCtx{
|
||||
id: "API",
|
||||
token: config.APIToken,
|
||||
manager: manager,
|
||||
logger: manager.logger.With().Str("session_id", "API").Logger(),
|
||||
profile: types.MemberProfile{
|
||||
Name: "API Session",
|
||||
IsAdmin: true,
|
||||
CanLogin: true,
|
||||
CanConnect: false,
|
||||
CanWatch: true,
|
||||
CanHost: true,
|
||||
CanAccessClipboard: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// try to load sessions from file
|
||||
manager.load()
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
type SessionManagerCtx struct {
|
||||
logger zerolog.Logger
|
||||
config *config.Session
|
||||
|
||||
settings types.Settings
|
||||
settingsMu sync.Mutex
|
||||
|
||||
tokens map[string]string
|
||||
sessions map[string]*SessionCtx
|
||||
sessionsMu sync.Mutex
|
||||
|
||||
hostId atomic.Value
|
||||
|
||||
cursors map[types.Session][]types.Cursor
|
||||
cursorsMu sync.Mutex
|
||||
|
||||
emmiter events.EventEmmiter
|
||||
apiSession *SessionCtx
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile) (types.Session, string, error) {
|
||||
token, err := utils.NewUID(64)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
manager.sessionsMu.Lock()
|
||||
if _, ok := manager.sessions[id]; ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return nil, "", types.ErrSessionAlreadyExists
|
||||
}
|
||||
|
||||
if _, ok := manager.tokens[token]; ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return nil, "", errors.New("session token already exists")
|
||||
}
|
||||
|
||||
session := &SessionCtx{
|
||||
id: id,
|
||||
token: token,
|
||||
manager: manager,
|
||||
logger: manager.logger.With().Str("session_id", id).Logger(),
|
||||
profile: profile,
|
||||
}
|
||||
|
||||
manager.tokens[token] = id
|
||||
manager.sessions[id] = session
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("created", session)
|
||||
manager.save()
|
||||
|
||||
return session, token, nil
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile) error {
|
||||
manager.sessionsMu.Lock()
|
||||
|
||||
session, ok := manager.sessions[id]
|
||||
if !ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
old := session.profile
|
||||
session.profile = profile
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
manager.emmiter.Emit("profile_changed", session, profile, old)
|
||||
manager.save()
|
||||
|
||||
session.profileChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Delete(id string) error {
|
||||
manager.sessionsMu.Lock()
|
||||
session, ok := manager.sessions[id]
|
||||
if !ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
delete(manager.tokens, session.token)
|
||||
delete(manager.sessions, id)
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
if session.State().IsConnected {
|
||||
session.DestroyWebSocketPeer("session deleted")
|
||||
}
|
||||
|
||||
if session.State().IsWatching {
|
||||
session.GetWebRTCPeer().Destroy()
|
||||
}
|
||||
|
||||
manager.emmiter.Emit("deleted", session)
|
||||
manager.save()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Disconnect(id string) error {
|
||||
manager.sessionsMu.Lock()
|
||||
session, ok := manager.sessions[id]
|
||||
if !ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return types.ErrSessionNotFound
|
||||
}
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
if session.State().IsConnected {
|
||||
session.DestroyWebSocketPeer("session disconnected")
|
||||
}
|
||||
|
||||
if session.State().IsWatching {
|
||||
session.GetWebRTCPeer().Destroy()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Get(id string) (types.Session, bool) {
|
||||
manager.sessionsMu.Lock()
|
||||
defer manager.sessionsMu.Unlock()
|
||||
|
||||
session, ok := manager.sessions[id]
|
||||
return session, ok
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) GetByToken(token string) (types.Session, bool) {
|
||||
manager.sessionsMu.Lock()
|
||||
id, ok := manager.tokens[token]
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
if ok {
|
||||
return manager.Get(id)
|
||||
}
|
||||
|
||||
// is API session
|
||||
if manager.apiSession != nil && manager.apiSession.token == token {
|
||||
return manager.apiSession, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) List() []types.Session {
|
||||
manager.sessionsMu.Lock()
|
||||
defer manager.sessionsMu.Unlock()
|
||||
|
||||
var sessions []types.Session
|
||||
for _, session := range manager.sessions {
|
||||
sessions = append(sessions, session)
|
||||
}
|
||||
|
||||
return sessions
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Range(f func(session types.Session) bool) {
|
||||
manager.sessionsMu.Lock()
|
||||
defer manager.sessionsMu.Unlock()
|
||||
|
||||
for _, session := range manager.sessions {
|
||||
if !f(session) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// host
|
||||
// ---
|
||||
|
||||
func (manager *SessionManagerCtx) setHost(session, host types.Session) {
|
||||
var hostId string
|
||||
if host != nil {
|
||||
hostId = host.ID()
|
||||
}
|
||||
|
||||
manager.hostId.Store(hostId)
|
||||
manager.emmiter.Emit("host_changed", session, host)
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) GetHost() (types.Session, bool) {
|
||||
hostId, ok := manager.hostId.Load().(string)
|
||||
if !ok || hostId == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return manager.Get(hostId)
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) isHost(host types.Session) bool {
|
||||
hostId, ok := manager.hostId.Load().(string)
|
||||
return ok && hostId == host.ID()
|
||||
}
|
||||
|
||||
// ---
|
||||
// cursors
|
||||
// ---
|
||||
|
||||
func (manager *SessionManagerCtx) SetCursor(cursor types.Cursor, session types.Session) {
|
||||
manager.cursorsMu.Lock()
|
||||
defer manager.cursorsMu.Unlock()
|
||||
|
||||
list, ok := manager.cursors[session]
|
||||
if !ok {
|
||||
list = []types.Cursor{}
|
||||
}
|
||||
|
||||
list = append(list, cursor)
|
||||
manager.cursors[session] = list
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) PopCursors() map[types.Session][]types.Cursor {
|
||||
manager.cursorsMu.Lock()
|
||||
defer manager.cursorsMu.Unlock()
|
||||
|
||||
cursors := manager.cursors
|
||||
manager.cursors = make(map[types.Session][]types.Cursor)
|
||||
|
||||
return cursors
|
||||
}
|
||||
|
||||
// ---
|
||||
// broadcasts
|
||||
// ---
|
||||
|
||||
func (manager *SessionManagerCtx) Broadcast(event string, payload any, exclude ...string) {
|
||||
for _, session := range manager.List() {
|
||||
if !session.State().IsConnected {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(exclude) > 0 {
|
||||
if in, _ := utils.ArrayIn(session.ID(), exclude); in {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
session.Send(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) AdminBroadcast(event string, payload any, exclude ...string) {
|
||||
for _, session := range manager.List() {
|
||||
if !session.State().IsConnected || !session.Profile().IsAdmin {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(exclude) > 0 {
|
||||
if in, _ := utils.ArrayIn(session.ID(), exclude); in {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
session.Send(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) InactiveCursorsBroadcast(event string, payload any, exclude ...string) {
|
||||
for _, session := range manager.List() {
|
||||
if !session.State().IsConnected || !session.Profile().CanSeeInactiveCursors {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(exclude) > 0 {
|
||||
if in, _ := utils.ArrayIn(session.ID(), exclude); in {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
session.Send(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// events
|
||||
// ---
|
||||
|
||||
func (manager *SessionManagerCtx) OnCreated(listener func(session types.Session)) {
|
||||
manager.emmiter.On("created", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnDeleted(listener func(session types.Session)) {
|
||||
manager.emmiter.On("deleted", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnConnected(listener func(session types.Session)) {
|
||||
manager.emmiter.On("connected", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnDisconnected(listener func(session types.Session)) {
|
||||
manager.emmiter.On("disconnected", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnProfileChanged(listener func(session types.Session, new, old types.MemberProfile)) {
|
||||
manager.emmiter.On("profile_changed", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx), payload[1].(types.MemberProfile), payload[2].(types.MemberProfile))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnStateChanged(listener func(session types.Session)) {
|
||||
manager.emmiter.On("state_changed", func(payload ...any) {
|
||||
listener(payload[0].(*SessionCtx))
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnHostChanged(listener func(session, host types.Session)) {
|
||||
manager.emmiter.On("host_changed", func(payload ...any) {
|
||||
if payload[1] == nil {
|
||||
listener(payload[0].(*SessionCtx), nil)
|
||||
} else {
|
||||
listener(payload[0].(*SessionCtx), payload[1].(*SessionCtx))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) OnSettingsChanged(listener func(session types.Session, new, old types.Settings)) {
|
||||
manager.emmiter.On("settings_changed", func(payload ...any) {
|
||||
listener(payload[0].(types.Session), payload[1].(types.Settings), payload[2].(types.Settings))
|
||||
})
|
||||
}
|
||||
|
||||
// ---
|
||||
// settings
|
||||
// ---
|
||||
|
||||
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(session, new, old)
|
||||
return
|
||||
}
|
||||
manager.settingsMu.Unlock()
|
||||
}
|
||||
|
||||
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
|
||||
for _, s := range manager.List() {
|
||||
enabled := s.PrivateModeEnabled()
|
||||
|
||||
// if session had control, it must release it
|
||||
if enabled && s.IsHost() {
|
||||
session.ClearHost()
|
||||
}
|
||||
|
||||
// its webrtc connection will be paused or unpaused
|
||||
if webrtcPeer := s.GetWebRTCPeer(); webrtcPeer != nil {
|
||||
webrtcPeer.SetPaused(enabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if control protection changed and controls are not locked
|
||||
if old.ControlProtection != new.ControlProtection && new.ControlProtection && !new.LockedControls {
|
||||
// if there is no admin, lock controls
|
||||
hasAdmin := false
|
||||
manager.Range(func(session types.Session) bool {
|
||||
if session.Profile().IsAdmin && session.State().IsConnected {
|
||||
hasAdmin = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if !hasAdmin {
|
||||
manager.settingsMu.Lock()
|
||||
manager.settings.LockedControls = true
|
||||
new.LockedControls = true
|
||||
manager.settingsMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// if contols have been locked
|
||||
if old.LockedControls != new.LockedControls && new.LockedControls {
|
||||
// if the host is not admin, it must release controls
|
||||
host, hasHost := manager.GetHost()
|
||||
if hasHost && !host.Profile().IsAdmin {
|
||||
session.ClearHost()
|
||||
}
|
||||
}
|
||||
|
||||
manager.emmiter.Emit("settings_changed", session, new, old)
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) Settings() types.Settings {
|
||||
manager.settingsMu.Lock()
|
||||
defer manager.settingsMu.Unlock()
|
||||
|
||||
return manager.settings
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) CookieEnabled() bool {
|
||||
return manager.config.CookieEnabled
|
||||
}
|
97
server/internal/session/serialize.go
Normal file
97
server/internal/session/serialize.go
Normal file
@ -0,0 +1,97 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/demodesk/neko/pkg/types"
|
||||
)
|
||||
|
||||
func (manager *SessionManagerCtx) save() {
|
||||
if manager.config.File == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// serialize sessions
|
||||
sessions := make([]types.SessionProfile, 0, len(manager.sessions))
|
||||
for _, session := range manager.sessions {
|
||||
sessions = append(sessions, types.SessionProfile{
|
||||
Id: session.id,
|
||||
Token: session.token,
|
||||
Profile: session.profile,
|
||||
})
|
||||
}
|
||||
|
||||
// convert to json
|
||||
data, err := json.Marshal(sessions)
|
||||
if err != nil {
|
||||
manager.logger.Error().Err(err).Msg("failed to marshal sessions")
|
||||
return
|
||||
}
|
||||
|
||||
// write to file
|
||||
err = os.WriteFile(manager.config.File, data, 0644)
|
||||
if err != nil {
|
||||
manager.logger.Error().Err(err).
|
||||
Str("file", manager.config.File).
|
||||
Msg("failed to write sessions to a file")
|
||||
}
|
||||
}
|
||||
|
||||
func (manager *SessionManagerCtx) load() {
|
||||
if manager.config.File == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// read file
|
||||
data, err := os.ReadFile(manager.config.File)
|
||||
if err != nil {
|
||||
// if file does not exist
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
manager.logger.Info().
|
||||
Str("file", manager.config.File).
|
||||
Msg("sessions file does not exist")
|
||||
return
|
||||
}
|
||||
manager.logger.Error().Err(err).
|
||||
Str("file", manager.config.File).
|
||||
Msg("failed to read sessions from a file")
|
||||
return
|
||||
}
|
||||
|
||||
// if file is empty
|
||||
if len(data) == 0 {
|
||||
manager.logger.Info().
|
||||
Str("file", manager.config.File).
|
||||
Msg("sessions file is empty")
|
||||
return
|
||||
}
|
||||
|
||||
// deserialize sessions
|
||||
sessions := make([]types.SessionProfile, 0)
|
||||
err = json.Unmarshal(data, &sessions)
|
||||
if err != nil {
|
||||
manager.logger.Error().Err(err).Msg("failed to unmarshal sessions")
|
||||
return
|
||||
}
|
||||
|
||||
// create sessions
|
||||
manager.sessionsMu.Lock()
|
||||
for _, session := range sessions {
|
||||
manager.tokens[session.Token] = session.Id
|
||||
manager.sessions[session.Id] = &SessionCtx{
|
||||
id: session.Id,
|
||||
token: session.Token,
|
||||
manager: manager,
|
||||
logger: manager.logger.With().Str("session_id", session.Id).Logger(),
|
||||
profile: session.Profile,
|
||||
}
|
||||
}
|
||||
manager.sessionsMu.Unlock()
|
||||
|
||||
manager.logger.Info().
|
||||
Int("sessions", len(sessions)).
|
||||
Str("file", manager.config.File).
|
||||
Msg("loaded sessions from a file")
|
||||
}
|
283
server/internal/session/session.go
Normal file
283
server/internal/session/session.go
Normal file
@ -0,0 +1,283 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/demodesk/neko/pkg/types"
|
||||
"github.com/demodesk/neko/pkg/types/event"
|
||||
)
|
||||
|
||||
// client is expected to reconnect within 5 second
|
||||
// if some unexpected websocket disconnect happens
|
||||
const WS_DELAYED_DURATION = 5 * time.Second
|
||||
|
||||
type SessionCtx struct {
|
||||
id string
|
||||
token string
|
||||
logger zerolog.Logger
|
||||
manager *SessionManagerCtx
|
||||
profile types.MemberProfile
|
||||
state types.SessionState
|
||||
|
||||
websocketPeer types.WebSocketPeer
|
||||
websocketMu sync.Mutex
|
||||
|
||||
// websocket delayed set connected events
|
||||
wsDelayedMu sync.Mutex
|
||||
wsDelayedTimer *time.Timer
|
||||
|
||||
webrtcPeer types.WebRTCPeer
|
||||
webrtcMu sync.Mutex
|
||||
}
|
||||
|
||||
func (session *SessionCtx) ID() string {
|
||||
return session.id
|
||||
}
|
||||
|
||||
func (session *SessionCtx) Profile() types.MemberProfile {
|
||||
return session.profile
|
||||
}
|
||||
|
||||
func (session *SessionCtx) profileChanged() {
|
||||
if !session.profile.CanHost && session.IsHost() {
|
||||
session.ClearHost()
|
||||
}
|
||||
|
||||
if (!session.profile.CanConnect || !session.profile.CanLogin || !session.profile.CanWatch) && session.state.IsWatching {
|
||||
session.GetWebRTCPeer().Destroy()
|
||||
}
|
||||
|
||||
if (!session.profile.CanConnect || !session.profile.CanLogin) && session.state.IsConnected {
|
||||
session.DestroyWebSocketPeer("profile changed")
|
||||
}
|
||||
|
||||
// update webrtc paused state
|
||||
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
|
||||
webrtcPeer.SetPaused(session.PrivateModeEnabled())
|
||||
}
|
||||
}
|
||||
|
||||
func (session *SessionCtx) State() types.SessionState {
|
||||
return session.state
|
||||
}
|
||||
|
||||
func (session *SessionCtx) IsHost() bool {
|
||||
return session.manager.isHost(session)
|
||||
}
|
||||
|
||||
func (session *SessionCtx) SetAsHost() {
|
||||
session.manager.setHost(session, session)
|
||||
}
|
||||
|
||||
func (session *SessionCtx) SetAsHostBy(host types.Session) {
|
||||
session.manager.setHost(session, host)
|
||||
}
|
||||
|
||||
func (session *SessionCtx) ClearHost() {
|
||||
session.manager.setHost(session, nil)
|
||||
}
|
||||
|
||||
func (session *SessionCtx) PrivateModeEnabled() bool {
|
||||
return session.manager.Settings().PrivateMode && !session.profile.IsAdmin
|
||||
}
|
||||
|
||||
func (session *SessionCtx) SetCursor(cursor types.Cursor) {
|
||||
if session.manager.Settings().InactiveCursors && session.profile.SendsInactiveCursor {
|
||||
session.manager.SetCursor(cursor, session)
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// websocket
|
||||
// ---
|
||||
|
||||
// Connect WebSocket peer sets current peer and emits connected event. It also destroys the
|
||||
// previous peer, if there was one. If the peer is already set, it will be ignored.
|
||||
func (session *SessionCtx) ConnectWebSocketPeer(websocketPeer types.WebSocketPeer) {
|
||||
session.websocketMu.Lock()
|
||||
isCurrentPeer := websocketPeer == session.websocketPeer
|
||||
session.websocketPeer, websocketPeer = websocketPeer, session.websocketPeer
|
||||
session.websocketMu.Unlock()
|
||||
|
||||
// ignore if already set
|
||||
if isCurrentPeer {
|
||||
return
|
||||
}
|
||||
|
||||
session.logger.Info().Msg("set websocket connected")
|
||||
|
||||
// update state
|
||||
now := time.Now()
|
||||
session.state.IsConnected = true
|
||||
session.state.ConnectedSince = &now
|
||||
session.state.NotConnectedSince = nil
|
||||
|
||||
session.manager.emmiter.Emit("connected", session)
|
||||
|
||||
// if there is a previous peer, destroy it
|
||||
if websocketPeer != nil {
|
||||
websocketPeer.Destroy("connection replaced")
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect WebSocket peer sets current peer to nil and emits disconnected event. It also
|
||||
// allows for a delayed disconnect. That means, the peer will not be disconnected immediately,
|
||||
// but after a delay. If the peer is connected again before the delay, the disconnect will be
|
||||
// cancelled.
|
||||
//
|
||||
// If the peer is not the current peer or the peer is nil, it will be ignored.
|
||||
func (session *SessionCtx) DisconnectWebSocketPeer(websocketPeer types.WebSocketPeer, delayed bool) {
|
||||
session.websocketMu.Lock()
|
||||
isCurrentPeer := websocketPeer == session.websocketPeer && websocketPeer != nil
|
||||
session.websocketMu.Unlock()
|
||||
|
||||
// ignore if not current peer
|
||||
if !isCurrentPeer {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// ws delayed
|
||||
//
|
||||
|
||||
var wsDelayedTimer *time.Timer
|
||||
|
||||
if delayed {
|
||||
wsDelayedTimer = time.AfterFunc(WS_DELAYED_DURATION, func() {
|
||||
session.DisconnectWebSocketPeer(websocketPeer, false)
|
||||
})
|
||||
}
|
||||
|
||||
session.wsDelayedMu.Lock()
|
||||
if session.wsDelayedTimer != nil {
|
||||
session.wsDelayedTimer.Stop()
|
||||
}
|
||||
session.wsDelayedTimer = wsDelayedTimer
|
||||
session.wsDelayedMu.Unlock()
|
||||
|
||||
if delayed {
|
||||
session.logger.Info().Msg("delayed websocket disconnected")
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
// not delayed
|
||||
//
|
||||
|
||||
session.logger.Info().Msg("set websocket disconnected")
|
||||
|
||||
now := time.Now()
|
||||
session.state.IsConnected = false
|
||||
session.state.ConnectedSince = nil
|
||||
session.state.NotConnectedSince = &now
|
||||
|
||||
session.manager.emmiter.Emit("disconnected", session)
|
||||
|
||||
session.websocketMu.Lock()
|
||||
if websocketPeer == session.websocketPeer {
|
||||
session.websocketPeer = nil
|
||||
}
|
||||
session.websocketMu.Unlock()
|
||||
}
|
||||
|
||||
// Destroy WebSocket peer disconnects the peer and destroys it. It ensures that the peer is
|
||||
// disconnected immediately even though normal flow would be to disconnect it delayed.
|
||||
func (session *SessionCtx) DestroyWebSocketPeer(reason string) {
|
||||
session.websocketMu.Lock()
|
||||
peer := session.websocketPeer
|
||||
session.websocketMu.Unlock()
|
||||
|
||||
if peer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// disconnect peer first, so that it is not used anymore
|
||||
session.DisconnectWebSocketPeer(peer, false)
|
||||
|
||||
// destroy it afterwards
|
||||
peer.Destroy(reason)
|
||||
}
|
||||
|
||||
// Send event to websocket peer.
|
||||
func (session *SessionCtx) Send(event string, payload any) {
|
||||
session.websocketMu.Lock()
|
||||
peer := session.websocketPeer
|
||||
session.websocketMu.Unlock()
|
||||
|
||||
if peer != nil {
|
||||
peer.Send(event, payload)
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// webrtc
|
||||
// ---
|
||||
|
||||
// Set webrtc peer and destroy the old one, if there is old one.
|
||||
func (session *SessionCtx) SetWebRTCPeer(webrtcPeer types.WebRTCPeer) {
|
||||
session.webrtcMu.Lock()
|
||||
session.webrtcPeer, webrtcPeer = webrtcPeer, session.webrtcPeer
|
||||
session.webrtcMu.Unlock()
|
||||
|
||||
if webrtcPeer != nil && webrtcPeer != session.webrtcPeer {
|
||||
webrtcPeer.Destroy()
|
||||
}
|
||||
}
|
||||
|
||||
// Set if current webrtc peer is connected or not. Since there might be lefover calls from
|
||||
// webrtc peer, that are not used anymore, we need to check if the webrtc peer is still the
|
||||
// same as the one we are setting the connected state for.
|
||||
//
|
||||
// If webrtc peer is disconnected, we don't expect it to be reconnected, so we set it to nil
|
||||
// and send a signal close to the client. New connection is expected to use a new webrtc peer.
|
||||
func (session *SessionCtx) SetWebRTCConnected(webrtcPeer types.WebRTCPeer, connected bool) {
|
||||
session.webrtcMu.Lock()
|
||||
isCurrentPeer := webrtcPeer == session.webrtcPeer
|
||||
session.webrtcMu.Unlock()
|
||||
|
||||
if !isCurrentPeer {
|
||||
return
|
||||
}
|
||||
|
||||
session.logger.Info().
|
||||
Bool("connected", connected).
|
||||
Msg("set webrtc connected")
|
||||
|
||||
// update state
|
||||
session.state.IsWatching = connected
|
||||
if now := time.Now(); connected {
|
||||
session.state.WatchingSince = &now
|
||||
session.state.NotWatchingSince = nil
|
||||
} else {
|
||||
session.state.WatchingSince = nil
|
||||
session.state.NotWatchingSince = &now
|
||||
}
|
||||
|
||||
session.manager.emmiter.Emit("state_changed", session)
|
||||
|
||||
if connected {
|
||||
return
|
||||
}
|
||||
|
||||
session.webrtcMu.Lock()
|
||||
isCurrentPeer = webrtcPeer == session.webrtcPeer
|
||||
if isCurrentPeer {
|
||||
session.webrtcPeer = nil
|
||||
}
|
||||
session.webrtcMu.Unlock()
|
||||
|
||||
if isCurrentPeer {
|
||||
session.Send(event.SIGNAL_CLOSE, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Get current WebRTC peer. Nil if not connected.
|
||||
func (session *SessionCtx) GetWebRTCPeer() types.WebRTCPeer {
|
||||
session.webrtcMu.Lock()
|
||||
defer session.webrtcMu.Unlock()
|
||||
|
||||
return session.webrtcPeer
|
||||
}
|
Reference in New Issue
Block a user