mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
Remove legacy mode.
- Deleted the legacy WebSocket client implementation (`wstoclient.go`) and its test file (`wstoclient_test.go`). - Removed legacy handling from the HTTP manager, including legacy proxy setup and routing. - Cleaned up session management by removing legacy host checks and related functions. - Eliminated the legacy WebRTC handler and its associated logic. - Updated session interface to remove legacy methods. - Removed legacy API documentation and configuration options from migration guide and help files.
This commit is contained in:
+3
-9
@@ -81,10 +81,7 @@ func init() {
|
||||
// set root config values
|
||||
rootConfig.Set()
|
||||
|
||||
// legacy if explicitly enabled or if unspecified and legacy config is found
|
||||
if viper.GetBool("legacy") || !viper.IsSet("legacy") {
|
||||
rootConfig.SetV2()
|
||||
}
|
||||
rootConfig.SetV2()
|
||||
|
||||
//////
|
||||
// logs
|
||||
@@ -166,11 +163,8 @@ func init() {
|
||||
log.Panic().Err(err).Msg("unable to run root command")
|
||||
}
|
||||
|
||||
// legacy if explicitly enabled or if unspecified and legacy config is found
|
||||
if viper.GetBool("legacy") || !viper.IsSet("legacy") {
|
||||
if err := rootConfig.InitV2(root); err != nil {
|
||||
log.Panic().Err(err).Msg("unable to run root command")
|
||||
}
|
||||
if err := rootConfig.InitV2(root); err != nil {
|
||||
log.Panic().Err(err).Msg("unable to run root command")
|
||||
}
|
||||
|
||||
root.SetVersionTemplate(neko.Version.Details())
|
||||
|
||||
+23
-30
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/api"
|
||||
"github.com/m1k1o/neko/server/internal/capture"
|
||||
@@ -88,26 +87,23 @@ func (c *serve) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// legacy if explicitly enabled or if unspecified and legacy config is found
|
||||
if viper.GetBool("legacy") || !viper.IsSet("legacy") {
|
||||
if err := c.configs.Desktop.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Capture.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.WebRTC.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Member.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Session.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Server.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Desktop.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Capture.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.WebRTC.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Member.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Session.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.configs.Server.InitV2(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -124,15 +120,12 @@ func (c *serve) PreRun(cmd *cobra.Command, args []string) {
|
||||
c.configs.Plugins.Set()
|
||||
c.configs.Server.Set()
|
||||
|
||||
// legacy if explicitly enabled or if unspecified and legacy config is found
|
||||
if viper.GetBool("legacy") || !viper.IsSet("legacy") {
|
||||
c.configs.Desktop.SetV2()
|
||||
c.configs.Capture.SetV2()
|
||||
c.configs.WebRTC.SetV2()
|
||||
c.configs.Member.SetV2()
|
||||
c.configs.Session.SetV2()
|
||||
c.configs.Server.SetV2()
|
||||
}
|
||||
c.configs.Desktop.SetV2()
|
||||
c.configs.Capture.SetV2()
|
||||
c.configs.WebRTC.SetV2()
|
||||
c.configs.Member.SetV2()
|
||||
c.configs.Session.SetV2()
|
||||
c.configs.Server.SetV2()
|
||||
}
|
||||
|
||||
func (c *serve) Start(cmd *cobra.Command) {
|
||||
|
||||
@@ -86,7 +86,6 @@ func (api *ApiManagerCtx) Whoami(w http.ResponseWriter, r *http.Request) error {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Remove when legacy mode is removed as all sessions must be synced with their providers.
|
||||
func (api *ApiManagerCtx) UpdateProfile(w http.ResponseWriter, r *http.Request) error {
|
||||
session, _ := auth.GetSession(r)
|
||||
|
||||
|
||||
@@ -378,14 +378,6 @@ func (s *Capture) Set() {
|
||||
},
|
||||
}
|
||||
s.VideoIDs = []string{"main"}
|
||||
|
||||
if viper.GetBool("legacy") {
|
||||
legacyPipeline := s.VideoPipelines["main"]
|
||||
// Hacky way to enable pointer for legacy pipeline.
|
||||
legacyPipeline.GstPipeline = strings.Replace(legacyPipeline.GstPipeline, "show-pointer=false", "show-pointer=true", 1)
|
||||
s.VideoPipelines["legacy"] = legacyPipeline
|
||||
// we do not add legacy to VideoIDs so that its ignored by bandwidth estimator
|
||||
}
|
||||
} else {
|
||||
log.Warn().Msgf("no video pipelines specified, using default")
|
||||
|
||||
@@ -414,13 +406,6 @@ func (s *Capture) Set() {
|
||||
},
|
||||
}
|
||||
s.VideoIDs = []string{"main"}
|
||||
|
||||
if viper.GetBool("legacy") {
|
||||
legacyPipeline := s.VideoPipelines["main"]
|
||||
legacyPipeline.ShowPointer = true
|
||||
s.VideoPipelines["legacy"] = legacyPipeline
|
||||
// we do not add legacy to VideoIDs so that its ignored by bandwidth estimator
|
||||
}
|
||||
}
|
||||
} else if videoPipeline != "" {
|
||||
log.Warn().Msg("you are setting both single video pipeline and multiple video pipelines, ignoring single video pipeline")
|
||||
@@ -485,7 +470,7 @@ func (s *Capture) Set() {
|
||||
}
|
||||
|
||||
func (s *Capture) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
var ok bool
|
||||
|
||||
@@ -496,7 +481,7 @@ func (s *Capture) SetV2() {
|
||||
if display := viper.GetString("display"); display != "" {
|
||||
s.Display = display
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_DISPLAY' which is deprecated, please use 'NEKO_CAPTURE_VIDEO_DISPLAY' and/or 'NEKO_DESKTOP_DISPLAY' instead, also consider using 'DISPLAY' env variable if both should be the same")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
modifiedVideoCodec := false
|
||||
@@ -507,29 +492,29 @@ func (s *Capture) SetV2() {
|
||||
s.VideoCodec = codec.VP8()
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_VIDEO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_VIDEO_CODEC' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedVideoCodec = true
|
||||
}
|
||||
|
||||
if viper.GetBool("vp8") {
|
||||
s.VideoCodec = codec.VP8()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP8=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp8' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("vp9") {
|
||||
s.VideoCodec = codec.VP9()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_VP9=true', use 'NEKO_CAPTURE_VIDEO_CODEC=vp9' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("h264") {
|
||||
s.VideoCodec = codec.H264()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_H264=true', use 'NEKO_CAPTURE_VIDEO_CODEC=h264' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedVideoCodec = true
|
||||
} else if viper.GetBool("av1") {
|
||||
s.VideoCodec = codec.AV1()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_CAPTURE_VIDEO_CODEC=av1' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedVideoCodec = true
|
||||
}
|
||||
|
||||
@@ -566,11 +551,7 @@ func (s *Capture) SetV2() {
|
||||
// Hacky way to disable pointer.
|
||||
GstPipeline: strings.Replace(pipeline, "show-pointer=true", "show-pointer=false", 1),
|
||||
},
|
||||
"legacy": {
|
||||
GstPipeline: pipeline,
|
||||
},
|
||||
}
|
||||
// we do not add legacy to VideoIDs so that its ignored by bandwidth estimator
|
||||
s.VideoIDs = []string{"main"}
|
||||
}
|
||||
}
|
||||
@@ -580,7 +561,7 @@ func (s *Capture) SetV2() {
|
||||
}
|
||||
|
||||
// TODO: add deprecated warning and proper alternative for HW enc, bitrate and max fps
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
//
|
||||
@@ -590,7 +571,7 @@ func (s *Capture) SetV2() {
|
||||
if audioDevice := viper.GetString("device"); audioDevice != "" {
|
||||
s.AudioDevice = audioDevice
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_DEVICE' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_DEVICE' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
modifiedAudioCodec := false
|
||||
@@ -601,29 +582,29 @@ func (s *Capture) SetV2() {
|
||||
s.AudioCodec = codec.Opus()
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_AUDIO_CODEC' which is deprecated, please use 'NEKO_CAPTURE_AUDIO_CODEC' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedAudioCodec = true
|
||||
}
|
||||
|
||||
if viper.GetBool("opus") {
|
||||
s.AudioCodec = codec.Opus()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_OPUS=true', use 'NEKO_CAPTURE_AUDIO_CODEC=opus' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("g722") {
|
||||
s.AudioCodec = codec.G722()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_G722=true', use 'NEKO_CAPTURE_AUDIO_CODEC=g722' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("pcmu") {
|
||||
s.AudioCodec = codec.PCMU()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMU=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcmu' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedAudioCodec = true
|
||||
} else if viper.GetBool("pcma") {
|
||||
s.AudioCodec = codec.PCMA()
|
||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_PCMA=true', use 'NEKO_CAPTURE_AUDIO_CODEC=pcma' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
modifiedAudioCodec = true
|
||||
}
|
||||
|
||||
@@ -644,7 +625,7 @@ func (s *Capture) SetV2() {
|
||||
}
|
||||
|
||||
// TODO: add deprecated warning and proper alternative for audio bitrate
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
//
|
||||
@@ -654,22 +635,20 @@ func (s *Capture) SetV2() {
|
||||
if viper.IsSet("broadcast_pipeline") {
|
||||
s.BroadcastPipeline = viper.GetString("broadcast_pipeline")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_PIPELINE' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_PIPELINE' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("broadcast_url") {
|
||||
s.BroadcastUrl = viper.GetString("broadcast_url")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_URL' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_URL' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("broadcast_autostart") {
|
||||
s.BroadcastAutostart = viper.GetBool("broadcast_autostart")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_BROADCAST_AUTOSTART' which is deprecated, please use 'NEKO_CAPTURE_BROADCAST_AUTOSTART' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func (s *Desktop) Set() {
|
||||
}
|
||||
|
||||
func (s *Desktop) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
if viper.IsSet("screen") {
|
||||
r := regexp.MustCompile(`([0-9]{1,4})x([0-9]{1,4})@([0-9]{1,3})`)
|
||||
@@ -128,12 +128,10 @@ func (s *Desktop) SetV2() {
|
||||
}
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_SCREEN' which is deprecated, please use 'NEKO_DESKTOP_SCREEN' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ func (s *Member) Set() {
|
||||
}
|
||||
|
||||
func (s *Member) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
if viper.IsSet("password") || viper.IsSet("password_admin") {
|
||||
s.Provider = "multiuser"
|
||||
@@ -281,12 +281,10 @@ func (s *Member) SetV2() {
|
||||
s.Multiuser.AdminPassword = "admin"
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_PASSWORD' and 'NEKO_PASSWORD_ADMIN' which are deprecated, please use 'NEKO_MEMBER_MULTIUSER_USER_PASSWORD' and 'NEKO_MEMBER_MULTIUSER_ADMIN_PASSWORD' with 'NEKO_MEMBER_PROVIDER=multiuser' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
type Root struct {
|
||||
Config string
|
||||
Legacy bool
|
||||
|
||||
LogLevel zerolog.Level
|
||||
LogTime string
|
||||
@@ -34,14 +33,6 @@ func (Root) Init(cmd *cobra.Command) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// whether legacy configs and api should be enabled.
|
||||
// - if not specified, it will be automatically enabled if at least one legacy config entry is found.
|
||||
// - if it is specified, it will be enabled/disabled regardless of the presence of legacy config entries.
|
||||
cmd.PersistentFlags().Bool("legacy", true, "enable legacy mode")
|
||||
if err := viper.BindPFlag("legacy", cmd.PersistentFlags().Lookup("legacy")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().String("log.level", "info", "set log level (trace, debug, info, warn, error, fatal, panic, disabled)")
|
||||
if err := viper.BindPFlag("log.level", cmd.PersistentFlags().Lookup("log.level")); err != nil {
|
||||
return err
|
||||
@@ -81,10 +72,6 @@ func (Root) InitV2(cmd *cobra.Command) error {
|
||||
|
||||
func (s *Root) Set() {
|
||||
s.Config = viper.GetString("config")
|
||||
s.Legacy = viper.GetBool("legacy")
|
||||
if s.Legacy {
|
||||
log.Info().Msg("legacy configuration is enabled")
|
||||
}
|
||||
|
||||
logLevel := viper.GetString("log.level")
|
||||
level, err := zerolog.ParseLevel(logLevel)
|
||||
@@ -121,8 +108,6 @@ func (s *Root) Set() {
|
||||
}
|
||||
|
||||
func (s *Root) SetV2() {
|
||||
enableLegacy := false
|
||||
|
||||
if viper.IsSet("logs") {
|
||||
if viper.GetBool("logs") {
|
||||
logs := filepath.Join(".", "logs")
|
||||
@@ -134,12 +119,5 @@ func (s *Root) SetV2() {
|
||||
s.LogDir = ""
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_LOGS' which is deprecated, please use 'NEKO_LOG_DIR=/path/to/logs' instead")
|
||||
enableLegacy = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,37 +127,37 @@ func (s *Server) Set() {
|
||||
}
|
||||
|
||||
func (s *Server) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
if viper.IsSet("cert") {
|
||||
s.Cert = viper.GetString("cert")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_CERT' which is deprecated, please use 'NEKO_SERVER_CERT' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("key") {
|
||||
s.Key = viper.GetString("key")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_KEY' which is deprecated, please use 'NEKO_SERVER_KEY' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("bind") {
|
||||
s.Bind = viper.GetString("bind")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_BIND' which is deprecated, please use 'NEKO_SERVER_BIND' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("proxy") {
|
||||
s.Proxy = viper.GetBool("proxy")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_PROXY' which is deprecated, please use 'NEKO_SERVER_PROXY' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("static") {
|
||||
s.Static = viper.GetString("static")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_STATIC' which is deprecated, please use 'NEKO_SERVER_STATIC' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("path_prefix") {
|
||||
s.PathPrefix = path.Join("/", path.Clean(viper.GetString("path_prefix")))
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_PATH_PREFIX' which is deprecated, please use 'NEKO_SERVER_PATH_PREFIX' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("cors") {
|
||||
s.CORS = viper.GetStringSlice("cors")
|
||||
@@ -166,13 +166,11 @@ func (s *Server) SetV2() {
|
||||
log.Warn().Msg("CORS is enabled for all origins, this is not recommended for production environments")
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_CORS' which is deprecated, please use 'NEKO_SERVER_CORS' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ func (s *Session) Set() {
|
||||
}
|
||||
|
||||
func (s *Session) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
if viper.IsSet("locks") {
|
||||
locks := viper.GetStringSlice("locks")
|
||||
@@ -185,28 +185,26 @@ func (s *Session) SetV2() {
|
||||
}
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_LOCKS' which is deprecated, please use 'NEKO_SESSION_LOCKED_CONTROLS' and 'NEKO_SESSION_LOCKED_LOGINS' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
if viper.IsSet("implicit_control") {
|
||||
s.ImplicitHosting = viper.GetBool("implicit_control")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_IMPLICIT_CONTROL' which is deprecated, please use 'NEKO_SESSION_IMPLICIT_HOSTING' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("control_protection") {
|
||||
s.ControlProtection = viper.GetBool("control_protection")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_CONTROL_PROTECTION' which is deprecated, please use 'NEKO_SESSION_CONTROL_PROTECTION' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("heartbeat_interval") {
|
||||
s.HeartbeatInterval = viper.GetInt("heartbeat_interval")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_HEARTBEAT_INTERVAL' which is deprecated, please use 'NEKO_SESSION_HEARTBEAT_INTERVAL' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,27 +318,27 @@ func (s *WebRTC) Set() {
|
||||
}
|
||||
|
||||
func (s *WebRTC) SetV2() {
|
||||
enableLegacy := false
|
||||
deprecatedV2ConfigUsed := false
|
||||
|
||||
if viper.IsSet("nat1to1") {
|
||||
s.NAT1To1IPs = viper.GetStringSlice("nat1to1")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_NAT1TO1' which is deprecated, please use 'NEKO_WEBRTC_NAT1TO1' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("tcpmux") {
|
||||
s.TCPMux = viper.GetInt("tcpmux")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_TCPMUX' which is deprecated, please use 'NEKO_WEBRTC_TCPMUX' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("udpmux") {
|
||||
s.UDPMux = viper.GetInt("udpmux")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_UDPMUX' which is deprecated, please use 'NEKO_WEBRTC_UDPMUX' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
if viper.IsSet("icelite") {
|
||||
s.ICELite = viper.GetBool("icelite")
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_ICELITE' which is deprecated, please use 'NEKO_WEBRTC_ICELITE' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
if viper.IsSet("iceservers") {
|
||||
@@ -353,7 +353,7 @@ func (s *WebRTC) SetV2() {
|
||||
s.ICEServersFrontend = iceServers
|
||||
s.ICEServersBackend = iceServers
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_ICESERVERS' which is deprecated, please use 'NEKO_WEBRTC_ICESERVERS_FRONTEND' and/or 'NEKO_WEBRTC_ICESERVERS_BACKEND' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
if viper.IsSet("iceserver") {
|
||||
@@ -363,7 +363,7 @@ func (s *WebRTC) SetV2() {
|
||||
s.ICEServersBackend = append(s.ICEServersBackend, types.ICEServer{URLs: iceServerSlice})
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_ICESERVER' which is deprecated, please use 'NEKO_WEBRTC_ICESERVERS_FRONTEND' and/or 'NEKO_WEBRTC_ICESERVERS_BACKEND' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
if viper.IsSet("ipfetch") {
|
||||
@@ -376,7 +376,7 @@ func (s *WebRTC) SetV2() {
|
||||
s.NAT1To1IPs = append(s.NAT1To1IPs, ip)
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_IPFETCH' which is deprecated, please use 'NEKO_WEBRTC_IP_RETRIEVAL_URL' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
if viper.IsSet("epr") {
|
||||
@@ -404,12 +404,10 @@ func (s *WebRTC) SetV2() {
|
||||
s.EphemeralMax = max
|
||||
}
|
||||
log.Warn().Msg("you are using v2 configuration 'NEKO_EPR' which is deprecated, please use 'NEKO_WEBRTC_EPR' instead")
|
||||
enableLegacy = true
|
||||
deprecatedV2ConfigUsed = true
|
||||
}
|
||||
|
||||
// set legacy flag if any V2 configuration was used
|
||||
if !viper.IsSet("legacy") && enableLegacy {
|
||||
log.Warn().Msg("legacy configuration is enabled because at least one V2 configuration was used, please migrate to V3 configuration, visit https://neko.m1k1o.net/docs/v3/migration-from-v2 for more details")
|
||||
viper.Set("legacy", true)
|
||||
if deprecatedV2ConfigUsed {
|
||||
log.Warn().Msg("deprecated V2 configuration is in use; migrate to V3 configuration, see https://neko.m1k1o.net/docs/v3/migration-from-v2")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package event
|
||||
|
||||
const (
|
||||
SYSTEM_INIT = "system/init"
|
||||
SYSTEM_DISCONNECT = "system/disconnect"
|
||||
SYSTEM_ERROR = "system/error"
|
||||
)
|
||||
|
||||
const (
|
||||
CLIENT_HEARTBEAT = "client/heartbeat"
|
||||
)
|
||||
|
||||
const (
|
||||
SIGNAL_OFFER = "signal/offer"
|
||||
SIGNAL_ANSWER = "signal/answer"
|
||||
SIGNAL_PROVIDE = "signal/provide"
|
||||
SIGNAL_CANDIDATE = "signal/candidate"
|
||||
)
|
||||
|
||||
const (
|
||||
MEMBER_LIST = "member/list"
|
||||
MEMBER_CONNECTED = "member/connected"
|
||||
MEMBER_DISCONNECTED = "member/disconnected"
|
||||
)
|
||||
|
||||
const (
|
||||
CONTROL_LOCKED = "control/locked"
|
||||
CONTROL_RELEASE = "control/release"
|
||||
CONTROL_REQUEST = "control/request"
|
||||
CONTROL_REQUESTING = "control/requesting"
|
||||
CONTROL_GIVE = "control/give"
|
||||
CONTROL_CLIPBOARD = "control/clipboard"
|
||||
CONTROL_KEYBOARD = "control/keyboard"
|
||||
)
|
||||
|
||||
const (
|
||||
CHAT_MESSAGE = "chat/message"
|
||||
CHAT_EMOTE = "chat/emote"
|
||||
)
|
||||
|
||||
const (
|
||||
FILETRANSFER_LIST = "filetransfer/list"
|
||||
FILETRANSFER_REFRESH = "filetransfer/refresh"
|
||||
)
|
||||
|
||||
const (
|
||||
SCREEN_CONFIGURATIONS = "screen/configurations"
|
||||
SCREEN_RESOLUTION = "screen/resolution"
|
||||
SCREEN_SET = "screen/set"
|
||||
)
|
||||
|
||||
const (
|
||||
BROADCAST_STATUS = "broadcast/status"
|
||||
BROADCAST_CREATE = "broadcast/create"
|
||||
BROADCAST_DESTROY = "broadcast/destroy"
|
||||
)
|
||||
|
||||
const (
|
||||
ADMIN_BAN = "admin/ban"
|
||||
ADMIN_KICK = "admin/kick"
|
||||
ADMIN_LOCK = "admin/lock"
|
||||
ADMIN_MUTE = "admin/mute"
|
||||
ADMIN_UNLOCK = "admin/unlock"
|
||||
ADMIN_UNMUTE = "admin/unmute"
|
||||
ADMIN_CONTROL = "admin/control"
|
||||
ADMIN_RELEASE = "admin/release"
|
||||
ADMIN_GIVE = "admin/give"
|
||||
)
|
||||
@@ -1,463 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/api"
|
||||
oldEvent "github.com/m1k1o/neko/server/internal/http/legacy/event"
|
||||
oldMessage "github.com/m1k1o/neko/server/internal/http/legacy/message"
|
||||
oldTypes "github.com/m1k1o/neko/server/internal/http/legacy/types"
|
||||
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/types/event"
|
||||
"github.com/m1k1o/neko/server/pkg/types/message"
|
||||
"github.com/m1k1o/neko/server/pkg/utils"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultUpgrader specifies the parameters for upgrading an HTTP
|
||||
// connection to a WebSocket connection.
|
||||
DefaultUpgrader = &websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type LegacyHandler struct {
|
||||
logger zerolog.Logger
|
||||
serverAddr string
|
||||
pathPrefix string
|
||||
bannedIPs map[string]struct{}
|
||||
sessionIPs map[string]string
|
||||
wsDialer *websocket.Dialer
|
||||
}
|
||||
|
||||
func New(serverAddr, pathPrefix string) *LegacyHandler {
|
||||
// Init
|
||||
|
||||
return &LegacyHandler{
|
||||
logger: log.With().Str("module", "legacy").Logger(),
|
||||
serverAddr: serverAddr,
|
||||
pathPrefix: pathPrefix,
|
||||
bannedIPs: make(map[string]struct{}),
|
||||
sessionIPs: make(map[string]string),
|
||||
wsDialer: &websocket.Dialer{
|
||||
Proxy: nil, // disable proxy for local requests
|
||||
HandshakeTimeout: 45 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LegacyHandler) Route(r types.Router) {
|
||||
r.Get("/ws", func(w http.ResponseWriter, r *http.Request) error {
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new websocket connection
|
||||
connClient, err := DefaultUpgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return utils.HttpError(http.StatusInternalServerError).
|
||||
WithInternalErr(err).
|
||||
Msg("couldn't upgrade connection to websocket")
|
||||
}
|
||||
defer connClient.Close()
|
||||
s.connClient = connClient
|
||||
|
||||
if h.isBanned(r) {
|
||||
s.toClient(&oldMessage.SystemMessage{
|
||||
Event: oldEvent.SYSTEM_DISCONNECT,
|
||||
Title: "banned ip",
|
||||
Message: "you are banned",
|
||||
})
|
||||
}
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("username")
|
||||
password := r.URL.Query().Get("password")
|
||||
err = s.create(username, password)
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("couldn't create a new session")
|
||||
|
||||
s.toClient(&oldMessage.SystemMessage{
|
||||
Event: oldEvent.SYSTEM_DISCONNECT,
|
||||
Title: "couldn't create a new session",
|
||||
Message: err.Error(),
|
||||
})
|
||||
|
||||
// we can't return HTTP error here because the connection is already upgraded
|
||||
return nil
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
// dial to the remote backend
|
||||
backendURL := "ws://" + h.serverAddr + path.Join(s.pathPrefix, "/api/ws")
|
||||
backendHeaders := make(http.Header)
|
||||
if s.token != "" {
|
||||
backendURL += "?token=" + url.QueryEscape(s.token)
|
||||
} else {
|
||||
backendHeaders.Set("Cookie", s.cookie)
|
||||
}
|
||||
connBackend, _, err := h.wsDialer.Dial(backendURL, backendHeaders)
|
||||
if err != nil {
|
||||
h.logger.Error().Err(err).Msg("couldn't dial to the remote backend")
|
||||
|
||||
s.toClient(&oldMessage.SystemMessage{
|
||||
Event: oldEvent.SYSTEM_DISCONNECT,
|
||||
Title: "couldn't dial to the remote backend",
|
||||
Message: err.Error(),
|
||||
})
|
||||
|
||||
// we can't return HTTP error here because the connection is already upgraded
|
||||
return nil
|
||||
}
|
||||
defer connBackend.Close()
|
||||
s.connBackend = connBackend
|
||||
|
||||
// request signal
|
||||
videoAuto := true
|
||||
if err = s.toBackend(event.SIGNAL_REQUEST, message.SignalRequest{
|
||||
Video: types.PeerVideoRequest{
|
||||
Auto: &videoAuto,
|
||||
},
|
||||
}); err != nil {
|
||||
h.logger.Error().Err(err).Msg("couldn't request signal")
|
||||
s.toClient(&oldMessage.SystemMessage{
|
||||
Event: oldEvent.SYSTEM_DISCONNECT,
|
||||
Title: "couldn't request signal",
|
||||
Message: err.Error(),
|
||||
})
|
||||
|
||||
// we can't return HTTP error here because the connection is already upgraded
|
||||
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) error) {
|
||||
for {
|
||||
msgType, msg, err := src.ReadMessage()
|
||||
if err != nil {
|
||||
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
|
||||
if e, ok := err.(*websocket.CloseError); ok {
|
||||
if e.Code != websocket.CloseNoStatusReceived {
|
||||
m = websocket.FormatCloseMessage(e.Code, e.Text)
|
||||
}
|
||||
}
|
||||
errc <- fmt.Errorf("src read message error: %w", err)
|
||||
dst.WriteMessage(websocket.CloseMessage, m)
|
||||
break
|
||||
}
|
||||
|
||||
// handle text messages
|
||||
if msgType == websocket.TextMessage {
|
||||
err = rewriteTextMessage(msg)
|
||||
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrBackendRespone) {
|
||||
h.logger.Error().Err(err).Msg("backend response error")
|
||||
|
||||
s.toClient(&oldMessage.SystemMessage{
|
||||
Event: oldEvent.SYSTEM_ERROR,
|
||||
Title: "backend response error",
|
||||
Message: strings.ReplaceAll(err.Error(), ErrBackendRespone.Error()+": ", ""),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if errors.Is(err, ErrWebsocketSend) {
|
||||
errc <- fmt.Errorf("dst write message error: %w", err)
|
||||
break
|
||||
}
|
||||
|
||||
h.logger.Error().Err(err).Msg("couldn't rewrite text message")
|
||||
continue
|
||||
}
|
||||
|
||||
// forward ping pong messages
|
||||
if msgType == websocket.PingMessage ||
|
||||
msgType == websocket.PongMessage {
|
||||
err = dst.WriteMessage(msgType, msg)
|
||||
if err != nil {
|
||||
errc <- err
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// backend -> client
|
||||
go replicateWebsocketConn(connClient, connBackend, errClient, s.wsToClient)
|
||||
|
||||
// client -> backend
|
||||
go replicateWebsocketConn(connBackend, connClient, errBackend, s.wsToBackend)
|
||||
|
||||
var message string
|
||||
select {
|
||||
case err = <-errClient:
|
||||
message = "websocketproxy: Error when copying from backend to client"
|
||||
case err = <-errBackend:
|
||||
message = "websocketproxy: Error when copying from client to backend"
|
||||
}
|
||||
|
||||
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
|
||||
h.logger.Error().Err(err).Msg(message)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
r.Get("/stats", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
if !s.isAdmin {
|
||||
return utils.HttpUnauthorized().Msg("bad authorization")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// get all sessions
|
||||
sessions := []api.SessionDataPayload{}
|
||||
err = s.apiReq(http.MethodGet, "/api/sessions", nil, &sessions)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// get stats
|
||||
newStats := types.Stats{}
|
||||
err = s.apiReq(http.MethodGet, "/api/stats", nil, &newStats)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// get settings
|
||||
settings := types.Settings{}
|
||||
err = s.apiReq(http.MethodGet, "/api/room/settings", nil, &settings)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
var stats oldTypes.Stats
|
||||
|
||||
// create empty array so that it's not null in json
|
||||
stats.Members = []*oldTypes.Member{}
|
||||
|
||||
for _, session := range sessions {
|
||||
if session.State.IsConnected {
|
||||
stats.Connections++
|
||||
member, err := profileToMember(session.ID, session.Profile)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
// append members
|
||||
stats.Members = append(stats.Members, member)
|
||||
}
|
||||
}
|
||||
|
||||
locks, err := s.settingsToLocks(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stats.Host = newStats.HostId
|
||||
// TODO: stats.Banned, not implemented yet
|
||||
stats.Locked = locks
|
||||
stats.ServerStartedAt = newStats.ServerStartedAt
|
||||
stats.LastAdminLeftAt = newStats.LastAdminLeftAt
|
||||
stats.LastUserLeftAt = newStats.LastUserLeftAt
|
||||
stats.ControlProtection = settings.ControlProtection
|
||||
stats.ImplicitControl = settings.ImplicitHosting
|
||||
|
||||
return json.NewEncoder(w).Encode(stats)
|
||||
})
|
||||
|
||||
r.Get("/screenshot.jpg", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
if !s.isAdmin {
|
||||
return utils.HttpUnauthorized().Msg("bad authorization")
|
||||
}
|
||||
|
||||
quality := r.URL.Query().Get("quality")
|
||||
|
||||
// get the screenshot
|
||||
body, headers, err := s.req(http.MethodGet, "/api/room/screen/shot.jpg?quality="+url.QueryEscape(quality), nil, nil)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// copy headers
|
||||
w.Header().Set("Content-Length", headers.Get("Content-Length"))
|
||||
w.Header().Set("Content-Type", headers.Get("Content-Type"))
|
||||
|
||||
// copy the body to the response writer
|
||||
_, err = io.Copy(w, body)
|
||||
return err
|
||||
})
|
||||
|
||||
// allow downloading and uploading files
|
||||
r.Get("/file", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
|
||||
body, headers, err := s.req(http.MethodGet, "/api/filetransfer?filename="+url.QueryEscape(filename), r.Header, nil)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// copy headers
|
||||
w.Header().Set("Content-Length", headers.Get("Content-Length"))
|
||||
w.Header().Set("Content-Type", headers.Get("Content-Type"))
|
||||
|
||||
// copy the body to the response writer
|
||||
_, err = io.Copy(w, body)
|
||||
return err
|
||||
})
|
||||
|
||||
r.Post("/file", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
body, _, err := s.req(http.MethodPost, "/api/filetransfer", r.Header, r.Body)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// copy the body to the response writer
|
||||
_, err = io.Copy(w, body)
|
||||
return err
|
||||
})
|
||||
|
||||
r.Delete("/file", func(w http.ResponseWriter, r *http.Request) error {
|
||||
if h.isBanned(r) {
|
||||
return utils.HttpForbidden("banned ip")
|
||||
}
|
||||
|
||||
s := h.newSession(r)
|
||||
|
||||
// create a new session
|
||||
username := r.URL.Query().Get("usr")
|
||||
password := r.URL.Query().Get("pwd")
|
||||
err := s.create(username, password)
|
||||
if err != nil {
|
||||
return utils.HttpForbidden(err.Error())
|
||||
}
|
||||
defer s.destroy()
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
|
||||
body, _, err := s.req(http.MethodDelete, "/api/filetransfer?filename="+url.QueryEscape(filename), r.Header, nil)
|
||||
if err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err)
|
||||
}
|
||||
|
||||
// copy the body to the response writer
|
||||
_, err = io.Copy(w, body)
|
||||
return err
|
||||
})
|
||||
|
||||
r.Get("/health", func(w http.ResponseWriter, r *http.Request) error {
|
||||
_, err := w.Write([]byte("true"))
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (h *LegacyHandler) ban(sessionId string) error {
|
||||
// find session by id
|
||||
ip, ok := h.sessionIPs[sessionId]
|
||||
if !ok {
|
||||
return fmt.Errorf("session not found")
|
||||
}
|
||||
|
||||
h.bannedIPs[ip] = struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *LegacyHandler) isBanned(r *http.Request) bool {
|
||||
ip := getIp(r)
|
||||
_, ok := h.bannedIPs[ip]
|
||||
return ok
|
||||
}
|
||||
|
||||
func getIp(r *http.Request) string {
|
||||
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
if e, ok := err.(*net.AddrError); ok && e.Err == "missing port in address" {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
return ip
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"github.com/m1k1o/neko/server/internal/http/legacy/types"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Event string `json:"event"`
|
||||
}
|
||||
|
||||
type SystemInit struct {
|
||||
Event string `json:"event"`
|
||||
Locks map[string]string `json:"locks"`
|
||||
ImplicitHosting bool `json:"implicit_hosting"`
|
||||
FileTransfer bool `json:"file_transfer"`
|
||||
HeartbeatInterval int `json:"heartbeat_interval"`
|
||||
}
|
||||
|
||||
type SystemMessage struct {
|
||||
Event string `json:"event"`
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type SignalProvide struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
SDP string `json:"sdp"`
|
||||
Lite bool `json:"lite"`
|
||||
ICE []webrtc.ICEServer `json:"ice"`
|
||||
}
|
||||
|
||||
type SignalOffer struct {
|
||||
Event string `json:"event"`
|
||||
SDP string `json:"sdp"`
|
||||
}
|
||||
|
||||
type SignalAnswer struct {
|
||||
Event string `json:"event"`
|
||||
DisplayName string `json:"displayname"`
|
||||
SDP string `json:"sdp"`
|
||||
}
|
||||
|
||||
type SignalCandidate struct {
|
||||
Event string `json:"event"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
type MembersList struct {
|
||||
Event string `json:"event"`
|
||||
Members []*types.Member `json:"members"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
Event string `json:"event"`
|
||||
*types.Member
|
||||
}
|
||||
type MemberDisconnected struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type Clipboard struct {
|
||||
Event string `json:"event"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type Keyboard struct {
|
||||
Event string `json:"event"`
|
||||
Layout *string `json:"layout,omitempty"`
|
||||
CapsLock *bool `json:"capsLock,omitempty"`
|
||||
NumLock *bool `json:"numLock,omitempty"`
|
||||
ScrollLock *bool `json:"scrollLock,omitempty"` // TODO: ScrollLock is deprecated.
|
||||
}
|
||||
|
||||
type Control struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type ControlTarget struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
type ChatReceive struct {
|
||||
Event string `json:"event"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type ChatSend struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type EmoteReceive struct {
|
||||
Event string `json:"event"`
|
||||
Emote string `json:"emote"`
|
||||
}
|
||||
|
||||
type EmoteSend struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
Emote string `json:"emote"`
|
||||
}
|
||||
|
||||
type FileTransferList struct {
|
||||
Event string `json:"event"`
|
||||
Cwd string `json:"cwd"`
|
||||
UserDownload bool `json:"user_download"`
|
||||
UserUpload bool `json:"user_upload"`
|
||||
UserDelete bool `json:"user_delete"`
|
||||
Files []types.FileListItem `json:"files"`
|
||||
}
|
||||
|
||||
type Admin struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type AdminTarget struct {
|
||||
Event string `json:"event"`
|
||||
Target string `json:"target"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type AdminLock struct {
|
||||
Event string `json:"event"`
|
||||
Resource string `json:"resource"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
type ScreenResolution struct {
|
||||
Event string `json:"event"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rate int16 `json:"rate"`
|
||||
}
|
||||
|
||||
type ScreenConfigurations struct {
|
||||
Event string `json:"event"`
|
||||
Configurations map[int]types.ScreenConfiguration `json:"configurations"`
|
||||
}
|
||||
|
||||
type BroadcastStatus struct {
|
||||
Event string `json:"event"`
|
||||
URL string `json:"url"`
|
||||
IsActive bool `json:"isActive"`
|
||||
}
|
||||
|
||||
type BroadcastCreate struct {
|
||||
Event string `json:"event"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
oldTypes "github.com/m1k1o/neko/server/internal/http/legacy/types"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/api"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWebsocketSend = fmt.Errorf("failed to send message to websocket")
|
||||
ErrBackendRespone = fmt.Errorf("error response from backend")
|
||||
)
|
||||
|
||||
type memberStruct struct {
|
||||
member *oldTypes.Member
|
||||
connected bool
|
||||
sent bool
|
||||
}
|
||||
|
||||
type session struct {
|
||||
r *http.Request
|
||||
h *LegacyHandler
|
||||
|
||||
logger zerolog.Logger
|
||||
serverAddr string
|
||||
pathPrefix string
|
||||
|
||||
id, ip string
|
||||
token string
|
||||
cookie string
|
||||
name string
|
||||
isAdmin bool
|
||||
client *http.Client
|
||||
|
||||
lastHostID string
|
||||
lockedControls bool
|
||||
lockedLogins bool
|
||||
lockedFileTransfer bool
|
||||
sessions map[string]*memberStruct
|
||||
|
||||
muClient sync.Mutex
|
||||
connClient *websocket.Conn
|
||||
muBackend sync.Mutex
|
||||
connBackend *websocket.Conn
|
||||
}
|
||||
|
||||
func (h *LegacyHandler) newSession(r *http.Request) *session {
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
transport.Proxy = nil // disable proxy for local requests
|
||||
|
||||
return &session{
|
||||
r: r,
|
||||
h: h,
|
||||
logger: h.logger,
|
||||
serverAddr: h.serverAddr,
|
||||
pathPrefix: h.pathPrefix,
|
||||
client: &http.Client{
|
||||
Transport: transport,
|
||||
},
|
||||
sessions: make(map[string]*memberStruct),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *session) req(method, reqPath string, headers http.Header, request io.Reader) (io.ReadCloser, http.Header, error) {
|
||||
req, err := http.NewRequest(method, "http://"+s.serverAddr+path.Join(s.pathPrefix, reqPath), request)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
|
||||
if s.token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+s.token)
|
||||
} else if s.cookie != "" {
|
||||
req.Header.Set("Cookie", s.cookie)
|
||||
}
|
||||
|
||||
res, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
defer res.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
// try to unmarsal as json error message
|
||||
var apiErr struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &apiErr); err == nil {
|
||||
return nil, nil, fmt.Errorf("%w: %s", ErrBackendRespone, apiErr.Message)
|
||||
}
|
||||
// return raw body if failed to unmarshal
|
||||
return nil, nil, fmt.Errorf("unexpected status code: %d, body: %s", res.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
return res.Body, res.Header, nil
|
||||
}
|
||||
|
||||
func (s *session) apiReq(method, path string, request, response any) error {
|
||||
return s.apiReqWithHeaders(method, path, request, response, nil)
|
||||
}
|
||||
|
||||
func (s *session) apiReqWithHeaders(method, path string, request, response any, headers http.Header) error {
|
||||
reqBody, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if headers == nil {
|
||||
headers = make(http.Header)
|
||||
}
|
||||
headers.Set("Content-Type", "application/json")
|
||||
|
||||
resBody, _, err := s.req(method, path, headers, bytes.NewReader(reqBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resBody.Close()
|
||||
|
||||
if resBody == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if response == nil {
|
||||
io.Copy(io.Discard, resBody)
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.NewDecoder(resBody).Decode(response)
|
||||
}
|
||||
|
||||
// send message to client (in old format)
|
||||
func (s *session) toClient(payload any) error {
|
||||
s.muClient.Lock()
|
||||
defer s.muClient.Unlock()
|
||||
|
||||
err := s.connClient.WriteJSON(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrWebsocketSend, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// send message to backend (in new format)
|
||||
func (s *session) toBackend(event string, payload any) error {
|
||||
s.muBackend.Lock()
|
||||
defer s.muBackend.Unlock()
|
||||
|
||||
rawPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.connBackend.WriteJSON(types.WebSocketMessage{
|
||||
Event: event,
|
||||
Payload: rawPayload,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrWebsocketSend, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) create(username, password string) error {
|
||||
data := api.SessionDataPayload{}
|
||||
if cookie := s.r.Header.Get("Cookie"); cookie != "" {
|
||||
err := s.apiReqWithHeaders(http.MethodGet, "/api/whoami", nil, &data, http.Header{
|
||||
"Cookie": []string{cookie},
|
||||
})
|
||||
if err == nil {
|
||||
s.cookie = cookie
|
||||
s.setSessionData(data)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err := s.apiReq(http.MethodPost, "/api/login", api.SessionLoginPayload{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.setSessionData(data)
|
||||
|
||||
if s.token == "" {
|
||||
return fmt.Errorf("token not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *session) setSessionData(data api.SessionDataPayload) {
|
||||
s.id, s.ip = data.ID, getIp(s.r)
|
||||
s.h.sessionIPs[s.id] = s.ip // save session ip by id
|
||||
s.token = data.Token
|
||||
s.name = data.Profile.Name
|
||||
s.isAdmin = data.Profile.IsAdmin
|
||||
}
|
||||
|
||||
func (s *session) destroy() {
|
||||
defer s.client.CloseIdleConnections()
|
||||
|
||||
// logout session
|
||||
err := s.apiReq(http.MethodPost, "/api/logout", nil, nil)
|
||||
if err != nil {
|
||||
s.logger.Error().Err(err).Msg("failed to logout")
|
||||
}
|
||||
|
||||
// remove session id from ip map
|
||||
delete(s.h.sessionIPs, s.id)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
)
|
||||
|
||||
func TestCreateUsesExistingSessionCookie(t *testing.T) {
|
||||
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/whoami":
|
||||
if r.Method != http.MethodGet {
|
||||
t.Fatalf("method = %s, want GET", r.Method)
|
||||
}
|
||||
if got := r.Header.Get("Cookie"); got != "NEKO_SESSION=session-token" {
|
||||
t.Fatalf("cookie = %q", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"id": "oauth:user-123",
|
||||
"profile": types.MemberProfile{
|
||||
Name: "Ada Lovelace",
|
||||
IsAdmin: true,
|
||||
},
|
||||
})
|
||||
case "/api/protected":
|
||||
if got := r.Header.Get("Cookie"); got != "NEKO_SESSION=session-token" {
|
||||
t.Fatalf("authenticated request cookie = %q", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(true)
|
||||
default:
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}))
|
||||
defer backend.Close()
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://neko.test/ws", nil)
|
||||
request.Header.Set("Cookie", "NEKO_SESSION=session-token")
|
||||
handler := &LegacyHandler{sessionIPs: make(map[string]string)}
|
||||
session := &session{
|
||||
r: request,
|
||||
h: handler,
|
||||
serverAddr: strings.TrimPrefix(backend.URL, "http://"),
|
||||
client: backend.Client(),
|
||||
}
|
||||
|
||||
if err := session.create("", ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if session.id != "oauth:user-123" || session.name != "Ada Lovelace" || !session.isAdmin {
|
||||
t.Fatalf("session = %#v", session)
|
||||
}
|
||||
if session.cookie != "NEKO_SESSION=session-token" || session.token != "" {
|
||||
t.Fatalf("authentication state = cookie %q token %q", session.cookie, session.token)
|
||||
}
|
||||
if err := session.apiReq(http.MethodGet, "/api/protected", nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
type Stats struct {
|
||||
Connections uint32 `json:"connections"`
|
||||
Host string `json:"host"`
|
||||
Members []*Member `json:"members"`
|
||||
|
||||
Banned map[string]string `json:"banned"` // IP -> session ID (that banned it)
|
||||
Locked map[string]string `json:"locked"` // resource name -> session ID (that locked it)
|
||||
|
||||
ServerStartedAt time.Time `json:"server_started_at"`
|
||||
LastAdminLeftAt *time.Time `json:"last_admin_left_at"`
|
||||
LastUserLeftAt *time.Time `json:"last_user_left_at"`
|
||||
|
||||
ControlProtection bool `json:"control_protection"`
|
||||
ImplicitControl bool `json:"implicit_control"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"displayname"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Admin bool `json:"admin"`
|
||||
Muted bool `json:"muted"`
|
||||
}
|
||||
|
||||
type FileListItem struct {
|
||||
Filename string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type ScreenConfiguration struct {
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Rates map[int]int16 `json:"rates"`
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
|
||||
oldEvent "github.com/m1k1o/neko/server/internal/http/legacy/event"
|
||||
oldMessage "github.com/m1k1o/neko/server/internal/http/legacy/message"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/api/room"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/chat"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/filetransfer"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/openinapp"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/types/event"
|
||||
"github.com/m1k1o/neko/server/pkg/types/message"
|
||||
)
|
||||
|
||||
func (s *session) wsToBackend(msg []byte) error {
|
||||
header := oldMessage.Message{}
|
||||
err := json.Unmarshal(msg, &header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch header.Event {
|
||||
// Client Events
|
||||
case oldEvent.CLIENT_HEARTBEAT:
|
||||
// do nothing
|
||||
return nil
|
||||
|
||||
// Signal Events
|
||||
case oldEvent.SIGNAL_OFFER:
|
||||
request := &oldMessage.SignalOffer{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(event.SIGNAL_OFFER, &message.SignalDescription{
|
||||
SDP: request.SDP,
|
||||
})
|
||||
|
||||
case oldEvent.SIGNAL_ANSWER:
|
||||
request := &oldMessage.SignalAnswer{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if request.DisplayName != "" {
|
||||
// Update display name if it is not set
|
||||
if s.name == "" {
|
||||
err = s.apiReq(http.MethodPost, "/api/profile", map[string]any{
|
||||
"name": request.DisplayName,
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.name = request.DisplayName
|
||||
}
|
||||
}
|
||||
|
||||
// try to set legacy video stream, if it fails, it will be ignored
|
||||
if err := s.toBackend(event.SIGNAL_VIDEO, &message.SignalVideo{
|
||||
PeerVideoRequest: types.PeerVideoRequest{
|
||||
Selector: &types.StreamSelector{
|
||||
Type: types.StreamSelectorTypeExact,
|
||||
ID: "legacy",
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(event.SIGNAL_ANSWER, &message.SignalDescription{
|
||||
SDP: request.SDP,
|
||||
})
|
||||
|
||||
case oldEvent.SIGNAL_CANDIDATE:
|
||||
request := &oldMessage.SignalCandidate{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var candidate webrtc.ICECandidateInit
|
||||
err = json.Unmarshal([]byte(request.Data), &candidate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(event.SIGNAL_CANDIDATE, &message.SignalCandidate{
|
||||
ICECandidateInit: candidate,
|
||||
})
|
||||
|
||||
// Control Events
|
||||
case oldEvent.CONTROL_RELEASE:
|
||||
return s.toBackend(event.CONTROL_RELEASE, nil)
|
||||
|
||||
case oldEvent.CONTROL_REQUEST:
|
||||
return s.toBackend(event.CONTROL_REQUEST, nil)
|
||||
|
||||
case oldEvent.CONTROL_GIVE:
|
||||
request := &oldMessage.Control{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/room/control/give/"+request.ID, nil, nil)
|
||||
|
||||
case oldEvent.CONTROL_CLIPBOARD:
|
||||
request := &oldMessage.Clipboard{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(event.CLIPBOARD_SET, &message.ClipboardData{
|
||||
Text: request.Text,
|
||||
})
|
||||
|
||||
case oldEvent.CONTROL_KEYBOARD:
|
||||
request := &oldMessage.Keyboard{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if request.Layout != nil {
|
||||
err = s.toBackend(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 = s.toBackend(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 oldEvent.CHAT_MESSAGE:
|
||||
request := &oldMessage.ChatReceive{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(chat.CHAT_MESSAGE, &chat.Content{
|
||||
Text: request.Content,
|
||||
})
|
||||
|
||||
case oldEvent.CHAT_EMOTE:
|
||||
request := &oldMessage.EmoteReceive{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// loopback emote
|
||||
err = s.toClient(&oldMessage.EmoteSend{
|
||||
Event: oldEvent.CHAT_EMOTE,
|
||||
ID: s.id,
|
||||
Emote: request.Emote,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast emote to other users
|
||||
return s.toBackend(event.SEND_BROADCAST, &message.SendBroadcast{
|
||||
Sender: s.id,
|
||||
Subject: "emote",
|
||||
Body: request.Emote,
|
||||
})
|
||||
|
||||
// File Transfer Events
|
||||
case oldEvent.FILETRANSFER_REFRESH:
|
||||
return s.toBackend(filetransfer.FILETRANSFER_UPDATE, nil)
|
||||
|
||||
// Open In App Events
|
||||
case openinapp.OPENINAPP_OPENLINK:
|
||||
request := &openinapp.Url{}
|
||||
if err := json.Unmarshal(msg, request); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.apiReq(http.MethodPost, "/api/openinapp/openlink", request, nil)
|
||||
|
||||
// Screen Events
|
||||
case oldEvent.SCREEN_RESOLUTION:
|
||||
response := &types.ScreenSize{}
|
||||
err := s.apiReq(http.MethodGet, "/api/room/screen", nil, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toClient(&oldMessage.ScreenResolution{
|
||||
Event: oldEvent.SCREEN_RESOLUTION,
|
||||
Width: response.Width,
|
||||
Height: response.Height,
|
||||
Rate: response.Rate,
|
||||
})
|
||||
|
||||
case oldEvent.SCREEN_CONFIGURATIONS:
|
||||
response := &[]types.ScreenSize{}
|
||||
err := s.apiReq(http.MethodGet, "/api/room/screen/configurations", nil, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toClient(&oldMessage.ScreenConfigurations{
|
||||
Event: oldEvent.SCREEN_CONFIGURATIONS,
|
||||
Configurations: screenConfigurations(*response),
|
||||
})
|
||||
|
||||
case oldEvent.SCREEN_SET:
|
||||
request := &oldMessage.ScreenResolution{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toBackend(event.SCREEN_SET, &message.ScreenSize{
|
||||
ScreenSize: types.ScreenSize{
|
||||
Width: request.Width,
|
||||
Height: request.Height,
|
||||
Rate: request.Rate,
|
||||
},
|
||||
})
|
||||
|
||||
// Broadcast Events
|
||||
case oldEvent.BROADCAST_CREATE:
|
||||
request := &oldMessage.BroadcastCreate{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/room/broadcast/start", room.BroadcastStatusPayload{
|
||||
URL: request.URL,
|
||||
IsActive: true,
|
||||
}, nil)
|
||||
|
||||
case oldEvent.BROADCAST_DESTROY:
|
||||
return s.apiReq(http.MethodPost, "/api/room/broadcast/stop", nil, nil)
|
||||
|
||||
// Admin Events
|
||||
case oldEvent.ADMIN_LOCK:
|
||||
request := &oldMessage.AdminLock{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := map[string]any{}
|
||||
|
||||
switch request.Resource {
|
||||
case "login":
|
||||
data["locked_logins"] = true
|
||||
case "control":
|
||||
data["locked_controls"] = true
|
||||
case "file_transfer":
|
||||
data["plugins"] = map[string]any{
|
||||
"filetransfer.enabled": false,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown resource: %s", request.Resource)
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/room/settings", data, nil)
|
||||
|
||||
case oldEvent.ADMIN_UNLOCK:
|
||||
request := &oldMessage.AdminLock{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := map[string]any{}
|
||||
|
||||
switch request.Resource {
|
||||
case "login":
|
||||
data["locked_logins"] = false
|
||||
case "control":
|
||||
data["locked_controls"] = false
|
||||
case "file_transfer":
|
||||
data["plugins"] = map[string]any{
|
||||
"filetransfer.enabled": true,
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown resource: %s", request.Resource)
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/room/settings", data, nil)
|
||||
|
||||
case oldEvent.ADMIN_CONTROL:
|
||||
return s.apiReq(http.MethodPost, "/api/room/control/take", nil, nil)
|
||||
|
||||
case oldEvent.ADMIN_RELEASE:
|
||||
return s.apiReq(http.MethodPost, "/api/room/control/reset", nil, nil)
|
||||
|
||||
case oldEvent.ADMIN_GIVE:
|
||||
request := &oldMessage.Admin{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.apiReq(http.MethodPost, "/api/room/control/give/"+request.ID, nil, nil)
|
||||
|
||||
case oldEvent.ADMIN_BAN:
|
||||
request := &oldMessage.Admin{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.h.ban(request.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fallthrough // continue to kick
|
||||
|
||||
case oldEvent.ADMIN_KICK:
|
||||
request := &oldMessage.Admin{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/members/"+request.ID, map[string]any{
|
||||
"can_login": false,
|
||||
}, nil)
|
||||
|
||||
case oldEvent.ADMIN_MUTE:
|
||||
request := &oldMessage.Admin{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/members/"+request.ID, map[string]any{
|
||||
"plugins": map[string]any{
|
||||
"chat.can_send": false,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
case oldEvent.ADMIN_UNMUTE:
|
||||
request := &oldMessage.Admin{}
|
||||
err := json.Unmarshal(msg, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.apiReq(http.MethodPost, "/api/members/"+request.ID, map[string]any{
|
||||
"plugins": map[string]any{
|
||||
"chat.can_send": true,
|
||||
},
|
||||
}, nil)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unknown event type: %s", header.Event)
|
||||
}
|
||||
}
|
||||
@@ -1,778 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
oldEvent "github.com/m1k1o/neko/server/internal/http/legacy/event"
|
||||
oldMessage "github.com/m1k1o/neko/server/internal/http/legacy/message"
|
||||
oldTypes "github.com/m1k1o/neko/server/internal/http/legacy/types"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/plugins/chat"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/filetransfer"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/openinapp"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/types/event"
|
||||
"github.com/m1k1o/neko/server/pkg/types/message"
|
||||
)
|
||||
|
||||
func profileToMember(id string, profile types.MemberProfile) (*oldTypes.Member, error) {
|
||||
settings := chat.Settings{
|
||||
CanSend: true, // defaults to true
|
||||
CanReceive: true, // defaults to true
|
||||
}
|
||||
|
||||
err := 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: profile.Name,
|
||||
Avatar: profile.Avatar,
|
||||
Admin: profile.IsAdmin,
|
||||
Muted: !settings.CanSend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func screenConfigurations(screenSizes []types.ScreenSize) map[int]oldTypes.ScreenConfiguration {
|
||||
rates := map[string]map[int16]struct{}{}
|
||||
for _, size := range screenSizes {
|
||||
key := fmt.Sprintf("%dx%d", size.Width, size.Height)
|
||||
if _, ok := rates[key]; !ok {
|
||||
rates[key] = map[int16]struct{}{}
|
||||
}
|
||||
rates[key][size.Rate] = struct{}{}
|
||||
}
|
||||
|
||||
usedScreenSizes := map[string]struct{}{}
|
||||
screenSizesList := map[int]oldTypes.ScreenConfiguration{}
|
||||
for i, size := range screenSizes {
|
||||
key := fmt.Sprintf("%dx%d", size.Width, size.Height)
|
||||
if _, ok := usedScreenSizes[key]; ok {
|
||||
continue
|
||||
}
|
||||
usedScreenSizes[key] = struct{}{}
|
||||
|
||||
c, ratesMap := 0, map[int]int16{}
|
||||
for rate := range rates[key] {
|
||||
ratesMap[c] = rate
|
||||
c++
|
||||
}
|
||||
|
||||
screenSizesList[i] = oldTypes.ScreenConfiguration{
|
||||
Width: size.Width,
|
||||
Height: size.Height,
|
||||
Rates: ratesMap,
|
||||
}
|
||||
}
|
||||
|
||||
return screenSizesList
|
||||
}
|
||||
|
||||
func (s *session) settingsToLocks(settings types.Settings) (map[string]string, error) {
|
||||
//
|
||||
// FileTransfer
|
||||
//
|
||||
|
||||
filetransferSettings := filetransfer.Settings{
|
||||
Enabled: true, // defaults to true
|
||||
}
|
||||
|
||||
err := settings.Plugins.Unmarshal(filetransfer.PluginName, &filetransferSettings)
|
||||
if err != nil && !errors.Is(err, types.ErrPluginSettingsNotFound) {
|
||||
return nil, fmt.Errorf("unable to unmarshal %s plugin settings from global settings: %w", filetransfer.PluginName, err)
|
||||
}
|
||||
|
||||
//
|
||||
// Locks
|
||||
//
|
||||
|
||||
locks := map[string]string{}
|
||||
if settings.LockedLogins {
|
||||
locks["login"] = "" // TODO: We don't know who locked the login.
|
||||
s.lockedLogins = true
|
||||
}
|
||||
if 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 locks, 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 if request.ID == "" {
|
||||
return s.toClient(&oldMessage.Control{
|
||||
Event: oldEvent.CONTROL_LOCKED,
|
||||
ID: request.HostID,
|
||||
})
|
||||
} else {
|
||||
return s.toClient(&oldMessage.ControlTarget{
|
||||
Event: oldEvent.CONTROL_GIVE,
|
||||
ID: request.ID,
|
||||
Target: request.HostID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// if profile change is the reason for disconnect, it's a kick event
|
||||
if request.Message == "profile changed" {
|
||||
request.Message = "kicked"
|
||||
}
|
||||
|
||||
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]*memberStruct{}
|
||||
for id, session := range request.Sessions {
|
||||
if !session.State.IsConnected {
|
||||
continue
|
||||
}
|
||||
member, err := profileToMember(id, session.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
membersList = append(membersList, member)
|
||||
s.sessions[id] = &memberStruct{
|
||||
sent: member.Name != "",
|
||||
connected: true,
|
||||
member: 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
|
||||
}
|
||||
|
||||
locks, err := s.settingsToLocks(request.Settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.toClient(&oldMessage.SystemInit{
|
||||
Event: oldEvent.SYSTEM_INIT,
|
||||
ImplicitHosting: request.Settings.ImplicitHosting,
|
||||
Locks: locks,
|
||||
// TODO: hack - we don't know if file transfer is enabled, we would need to check the global config.
|
||||
FileTransfer: viper.GetBool("filetransfer.enabled") || (viper.GetBool("legacy") && viper.GetBool("file_transfer_enabled")),
|
||||
HeartbeatInterval: request.Settings.HeartbeatInterval,
|
||||
})
|
||||
|
||||
case event.SYSTEM_ADMIN:
|
||||
request := &message.SystemAdmin{}
|
||||
err := json.Unmarshal(data.Payload, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//
|
||||
// ScreenSizesList
|
||||
//
|
||||
|
||||
err = s.toClient(&oldMessage.ScreenConfigurations{
|
||||
Event: oldEvent.SCREEN_CONFIGURATIONS,
|
||||
Configurations: screenConfigurations(request.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 := profileToMember(request.ID, request.Profile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// only save session - will be notified on connect
|
||||
s.sessions[request.ID] = &memberStruct{
|
||||
member: 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_PROFILE:
|
||||
request := &message.MemberProfile{}
|
||||
err := json.Unmarshal(data.Payload, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// session profile is expected to change when updating a name after connecting
|
||||
m, ok := s.sessions[request.ID]
|
||||
if !ok || m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// update member profile
|
||||
member, err := profileToMember(request.ID, request.MemberProfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mutedChanged := m.member.Muted != member.Muted
|
||||
m.member = member
|
||||
|
||||
if m.connected && !m.sent && member.Name != "" {
|
||||
m.sent = true
|
||||
|
||||
// oldEvent.MEMBER_CONNECTED if not sent already
|
||||
err = s.toClient(&oldMessage.Member{
|
||||
Event: oldEvent.MEMBER_CONNECTED,
|
||||
Member: member,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mutedChanged && member.Muted {
|
||||
return s.toClient(&oldMessage.AdminTarget{
|
||||
Event: oldEvent.ADMIN_MUTE,
|
||||
ID: "", // TODO: We don't know who (un)muted the user.
|
||||
Target: request.ID,
|
||||
})
|
||||
} else if mutedChanged && !member.Muted {
|
||||
return s.toClient(&oldMessage.AdminTarget{
|
||||
Event: oldEvent.ADMIN_UNMUTE,
|
||||
ID: "", // TODO: We don't know who (un)muted the user.
|
||||
Target: request.ID,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case event.SESSION_STATE:
|
||||
request := &message.SessionState{}
|
||||
err := json.Unmarshal(data.Payload, request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, ok := s.sessions[request.ID]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if request.IsConnected {
|
||||
m.connected = true
|
||||
|
||||
if m.member.Muted {
|
||||
err = s.toClient(&oldMessage.AdminTarget{
|
||||
Event: oldEvent.ADMIN_MUTE,
|
||||
ID: "", // TODO: We don't know who (un)muted the user.
|
||||
Target: request.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !m.sent && m.member.Name != "" {
|
||||
m.sent = true
|
||||
|
||||
// oldEvent.MEMBER_CONNECTED if not sent already
|
||||
return s.toClient(&oldMessage.Member{
|
||||
Event: oldEvent.MEMBER_CONNECTED,
|
||||
Member: m.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_VIDEO, event.SIGNAL_AUDIO:
|
||||
// ignore video and audio signals, because they are not part of the legacy protocol
|
||||
return nil
|
||||
|
||||
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.name,
|
||||
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_INIT:
|
||||
// ignore chat init, because it is not part of the legacy protocol
|
||||
return nil
|
||||
|
||||
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,
|
||||
UserDownload: request.UserDownload,
|
||||
UserUpload: request.UserUpload,
|
||||
UserDelete: request.UserDelete,
|
||||
Files: files,
|
||||
})
|
||||
|
||||
// Open In App Events
|
||||
case openinapp.OPENINAPP_INIT:
|
||||
request := &openinapp.Init{}
|
||||
if err := json.Unmarshal(data.Payload, request); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.toClient(&struct {
|
||||
Event string `json:"event"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}{
|
||||
Event: openinapp.OPENINAPP_INIT,
|
||||
Enabled: request.Enabled,
|
||||
})
|
||||
|
||||
// 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",
|
||||
ID: request.ID,
|
||||
})
|
||||
} else {
|
||||
err = s.toClient(&oldMessage.AdminLock{
|
||||
Event: oldEvent.ADMIN_UNLOCK,
|
||||
Resource: "control",
|
||||
ID: request.ID,
|
||||
})
|
||||
}
|
||||
|
||||
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",
|
||||
ID: request.ID,
|
||||
})
|
||||
} else {
|
||||
err = s.toClient(&oldMessage.AdminLock{
|
||||
Event: oldEvent.ADMIN_UNLOCK,
|
||||
Resource: "login",
|
||||
ID: request.ID,
|
||||
})
|
||||
}
|
||||
|
||||
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",
|
||||
ID: request.ID,
|
||||
})
|
||||
} else {
|
||||
err = s.toClient(&oldMessage.AdminLock{
|
||||
Event: oldEvent.ADMIN_UNLOCK,
|
||||
Resource: "file_transfer",
|
||||
ID: request.ID,
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package legacy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
)
|
||||
|
||||
func TestProfileToMemberPreservesAvatar(t *testing.T) {
|
||||
member, err := profileToMember("oauth:user-123", types.MemberProfile{
|
||||
Name: "Ada Lovelace",
|
||||
Avatar: "https://example.test/ada.png",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if member.Name != "Ada Lovelace" || member.Avatar != "https://example.test/ada.png" {
|
||||
t.Fatalf("member = %#v", member)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@@ -10,10 +9,8 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/config"
|
||||
"github.com/m1k1o/neko/server/internal/http/legacy"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
)
|
||||
|
||||
@@ -139,24 +136,6 @@ func (manager *HttpManagerCtx) Start() {
|
||||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("https listening on %s", manager.http.Addr)
|
||||
|
||||
// if we have legacy mode, we need to start local http server too
|
||||
if viper.GetBool("legacy") {
|
||||
// create a listener for the API server with a random port
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
manager.logger.Panic().Err(err).Msg("unable to start legacy http proxy")
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := http.Serve(listener, manager.router); err != http.ErrServerClosed {
|
||||
manager.logger.Panic().Err(err).Msg("unable to start http server")
|
||||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("legacy proxy listening on %s", listener.Addr().String())
|
||||
|
||||
legacy.New(listener.Addr().String(), manager.config.PathPrefix).Route(manager.router)
|
||||
}
|
||||
} else {
|
||||
go func() {
|
||||
if err := manager.http.ListenAndServe(); err != http.ErrServerClosed {
|
||||
@@ -164,11 +143,6 @@ func (manager *HttpManagerCtx) Start() {
|
||||
}
|
||||
}()
|
||||
manager.logger.Info().Msgf("http listening on %s", manager.http.Addr)
|
||||
|
||||
// start legacy proxy if enabled
|
||||
if viper.GetBool("legacy") {
|
||||
legacy.New(manager.http.Addr, manager.config.PathPrefix).Route(manager.router)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,16 +47,11 @@ func (session *SessionCtx) profileChanged() {
|
||||
}
|
||||
|
||||
if (!session.profile.CanConnect || !session.profile.CanLogin || !session.profile.CanWatch) && session.state.IsWatching {
|
||||
// TODO: Needed for legacy implementation. Websocket must die before webrtc and deliver signal close message
|
||||
// otherwise webrtc destroy would trigger websocket reconnect. In case of kick event, webrtc destroy is called
|
||||
// before websocket destroy that delivers the information about the kick.
|
||||
time.AfterFunc(time.Second, func() {
|
||||
// The peer may have been removed if the user disconnected while
|
||||
// waiting for the delayed teardown.
|
||||
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
|
||||
webrtcPeer.Destroy()
|
||||
}
|
||||
})
|
||||
// The peer may have been removed if the user disconnected while
|
||||
// waiting for the delayed teardown.
|
||||
if webrtcPeer := session.GetWebRTCPeer(); webrtcPeer != nil {
|
||||
webrtcPeer.Destroy()
|
||||
}
|
||||
}
|
||||
|
||||
if (!session.profile.CanConnect || !session.profile.CanLogin) && session.state.IsConnected {
|
||||
@@ -77,18 +72,6 @@ func (session *SessionCtx) IsHost() bool {
|
||||
return session.manager.isHost(session)
|
||||
}
|
||||
|
||||
// only needed for legacy webrtc handler
|
||||
func (session *SessionCtx) LegacyIsHost() bool {
|
||||
settings := session.manager.Settings()
|
||||
if !session.profile.CanHost || session.PrivateModeEnabled() {
|
||||
return false
|
||||
}
|
||||
if settings.LockedControls && !session.profile.IsAdmin {
|
||||
return false
|
||||
}
|
||||
return settings.ImplicitHosting || session.manager.isHost(session)
|
||||
}
|
||||
|
||||
func (session *SessionCtx) SetAsHost() {
|
||||
session.manager.setHost(session, session)
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
OP_MOVE = 0x01
|
||||
OP_SCROLL = 0x02
|
||||
OP_KEY_DOWN = 0x03
|
||||
OP_KEY_UP = 0x04
|
||||
OP_KEY_CLK = 0x05
|
||||
)
|
||||
|
||||
type PayloadHeader struct {
|
||||
Event uint8
|
||||
Length uint16
|
||||
}
|
||||
|
||||
type PayloadMove struct {
|
||||
PayloadHeader
|
||||
X uint16
|
||||
Y uint16
|
||||
}
|
||||
|
||||
type PayloadScroll struct {
|
||||
PayloadHeader
|
||||
X int16
|
||||
Y int16
|
||||
}
|
||||
|
||||
type PayloadKey struct {
|
||||
PayloadHeader
|
||||
Key uint64 // TODO: uint32
|
||||
}
|
||||
|
||||
func (manager *WebRTCManagerCtx) handleLegacy(
|
||||
logger zerolog.Logger, data []byte,
|
||||
session types.Session,
|
||||
) error {
|
||||
// continue only if session is host
|
||||
if !session.LegacyIsHost() {
|
||||
return nil
|
||||
}
|
||||
|
||||
buffer := bytes.NewBuffer(data)
|
||||
header := &PayloadHeader{}
|
||||
hbytes := make([]byte, 3)
|
||||
|
||||
if _, err := buffer.Read(hbytes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Read(bytes.NewBuffer(hbytes), binary.LittleEndian, header); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buffer = bytes.NewBuffer(data)
|
||||
|
||||
switch header.Event {
|
||||
case OP_MOVE:
|
||||
payload := &PayloadMove{}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manager.desktop.Move(int(payload.X), int(payload.Y))
|
||||
case OP_SCROLL:
|
||||
payload := &PayloadScroll{}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.
|
||||
Trace().
|
||||
Str("x", strconv.Itoa(int(payload.X))).
|
||||
Str("y", strconv.Itoa(int(payload.Y))).
|
||||
Msg("scroll")
|
||||
|
||||
manager.desktop.Scroll(int(payload.X), int(payload.Y), false)
|
||||
case OP_KEY_DOWN:
|
||||
payload := &PayloadKey{}
|
||||
if err := binary.Read(buffer, binary.LittleEndian, payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payload.Key < 8 {
|
||||
err := manager.desktop.ButtonDown(uint32(payload.Key))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("button down failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Trace().Msgf("button down %d", payload.Key)
|
||||
} else {
|
||||
err := manager.desktop.KeyDown(uint32(payload.Key))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("key down failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Trace().Msgf("key down %d", payload.Key)
|
||||
}
|
||||
case OP_KEY_UP:
|
||||
payload := &PayloadKey{}
|
||||
err := binary.Read(buffer, binary.LittleEndian, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if payload.Key < 8 {
|
||||
err := manager.desktop.ButtonUp(uint32(payload.Key))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("button up failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Trace().Msgf("button up %d", payload.Key)
|
||||
} else {
|
||||
err := manager.desktop.KeyUp(uint32(payload.Key))
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("key up failed")
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Trace().Msgf("key up %d", payload.Key)
|
||||
}
|
||||
case OP_KEY_CLK:
|
||||
// unused
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/pion/webrtc/v4"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/config"
|
||||
"github.com/m1k1o/neko/server/internal/webrtc/cursor"
|
||||
@@ -485,23 +484,6 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session) (*webrtc.Sess
|
||||
|
||||
connection.OnDataChannel(func(dc *webrtc.DataChannel) {
|
||||
logger.Info().Interface("data_channel", dc).Msg("got remote data channel")
|
||||
|
||||
//
|
||||
// old implementation created a new data channel on client side
|
||||
// new implementation creates a new data channel on server side
|
||||
//
|
||||
|
||||
if viper.GetBool("legacy") {
|
||||
// handle legacy data channel
|
||||
dc.OnMessage(func(message webrtc.DataChannelMessage) {
|
||||
if err := manager.handleLegacy(logger, message.Data, session); err != nil {
|
||||
logger.Err(err).Msg("data handle failed")
|
||||
}
|
||||
})
|
||||
|
||||
// handle legacy data channel
|
||||
peer.dataChannel = dc
|
||||
}
|
||||
})
|
||||
|
||||
var once sync.Once
|
||||
|
||||
@@ -69,7 +69,6 @@ type Session interface {
|
||||
Profile() MemberProfile
|
||||
State() SessionState
|
||||
IsHost() bool
|
||||
LegacyIsHost() bool
|
||||
SetAsHost()
|
||||
SetAsHostBy(session Session)
|
||||
ClearHost()
|
||||
|
||||
Reference in New Issue
Block a user