From e06c33218e2070b179240af60acb5d893edeadfd Mon Sep 17 00:00:00 2001 From: haveachin Date: Sat, 10 Feb 2024 18:14:45 +0100 Subject: [PATCH 1/6] refactor: configs --- pkg/infrared/filter.go | 23 ++--- pkg/infrared/infrared.go | 80 ++++++---------- pkg/infrared/infrared_test.go | 27 ++++-- pkg/infrared/proxy_protocol.go | 21 +++++ pkg/infrared/rate_limiter.go | 17 ++++ pkg/infrared/server.go | 162 +++++++++++++++++++++------------ 6 files changed, 199 insertions(+), 131 deletions(-) diff --git a/pkg/infrared/filter.go b/pkg/infrared/filter.go index 275b9af..a4c0381 100644 --- a/pkg/infrared/filter.go +++ b/pkg/infrared/filter.go @@ -14,29 +14,24 @@ func (f FilterFunc) Filter(c net.Conn) error { return f(c) } -type FilterConfigFunc func(cfg *FiltersConfig) - -func WithFilterConfig(c FiltersConfig) FilterConfigFunc { - return func(cfg *FiltersConfig) { - *cfg = c - } -} - type FiltersConfig struct { RateLimiter *RateLimiterConfig `yaml:"rateLimiter"` } +func NewFilterConfig() FiltersConfig { + rl := NewRateLimiterConfig() + + return FiltersConfig{ + RateLimiter: &rl, + } +} + type Filter struct { cfg FiltersConfig filterers []Filterer } -func NewFilter(fns ...FilterConfigFunc) Filter { - var cfg FiltersConfig - for _, fn := range fns { - fn(&cfg) - } - +func NewFilter(cfg FiltersConfig) Filter { filterers := make([]Filterer, 0) if cfg.RateLimiter != nil { diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index a174854..67734a3 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -5,7 +5,6 @@ import ( "io" "net" "strings" - "sync" "time" "github.com/haveachin/infrared/pkg/infrared/protocol" @@ -22,17 +21,11 @@ type Config struct { func NewConfig() Config { return Config{ - BindAddr: ":25565", - KeepAliveTimeout: 30 * time.Second, - FiltersConfig: FiltersConfig{ - RateLimiter: &RateLimiterConfig{ - RequestLimit: 10, - WindowLength: time.Second, - }, - }, - ProxyProtocolConfig: ProxyProtocolConfig{ - TrustedCIDRs: make([]string, 0), - }, + BindAddr: ":25565", + KeepAliveTimeout: 30 * time.Second, + ServerConfigs: make([]ServerConfig, 0), + FiltersConfig: NewFilterConfig(), + ProxyProtocolConfig: NewProxyProtocolConfig(), } } @@ -41,37 +34,28 @@ func (cfg Config) WithBindAddr(bindAddr string) Config { return cfg } -func (cfg Config) AddServerConfig(fns ...ServerConfigFunc) Config { - var sCfg ServerConfig - for _, fn := range fns { - fn(&sCfg) - } - cfg.ServerConfigs = append(cfg.ServerConfigs, sCfg) - return cfg -} - func (cfg Config) WithKeepAliveTimeout(d time.Duration) Config { cfg.KeepAliveTimeout = d return cfg } -func (cfg Config) WithProxyProtocolReceive(receive bool) Config { - cfg.ProxyProtocolConfig.Receive = receive +func (cfg Config) WithServerConfigs(sCfgs ...ServerConfig) Config { + cfg.ServerConfigs = sCfgs return cfg } -func (cfg Config) WithProxyProtocolTrustedCIDRs(trustedCIDRs ...string) Config { - cfg.ProxyProtocolConfig.TrustedCIDRs = trustedCIDRs +func (cfg Config) AddServerConfigs(sCfgs ...ServerConfig) Config { + cfg.ServerConfigs = append(cfg.ServerConfigs, sCfgs...) return cfg } -func (cfg Config) WithRateLimiterWindowLength(windowLength time.Duration) Config { - cfg.FiltersConfig.RateLimiter.WindowLength = windowLength +func (cfg Config) WithFiltersConfig(fCfg FiltersConfig) Config { + cfg.FiltersConfig = fCfg return cfg } -func (cfg Config) WithRateLimiterRequestLimit(requestLimit int) Config { - cfg.FiltersConfig.RateLimiter.RequestLimit = requestLimit +func (cfg Config) WithProxyProtocolConfig(ppCfg ProxyProtocolConfig) Config { + cfg.ProxyProtocolConfig = ppCfg return cfg } @@ -97,38 +81,29 @@ type Infrared struct { Logger zerolog.Logger NewListenerFunc NewListenerFunc NewServerRequesterFunc NewServerRequesterFunc + Filters []Filterer cfg Config - l net.Listener - filter Filter - bufPool sync.Pool - conns map[net.Addr]*clientConn - sr ServerRequester -} + l net.Listener + filter Filter + sr ServerRequester -func New() *Infrared { - return NewWithConfig(NewConfig()) + conns map[net.Addr]*clientConn } -func NewWithConfigProvider(prv ConfigProvider) *Infrared { - cfg := MustProvideConfig(prv.Config) - return NewWithConfig(cfg) -} - -func NewWithConfig(cfg Config) *Infrared { +func New(cfg Config) *Infrared { return &Infrared{ - cfg: cfg, - bufPool: sync.Pool{ - New: func() any { - b := make([]byte, 1<<15) - return &b - }, - }, + cfg: cfg, conns: make(map[net.Addr]*clientConn), } } +func NewWithConfigProvider(prv ConfigProvider) *Infrared { + cfg := MustProvideConfig(prv.Config) + return New(cfg) +} + func (ir *Infrared) initListener() error { ir.Logger.Info(). Str("bind", ir.cfg.BindAddr). @@ -164,7 +139,7 @@ func (ir *Infrared) initListener() error { func (ir *Infrared) initServerGateway() error { srvs := make([]*Server, 0) for _, sCfg := range ir.cfg.ServerConfigs { - srv, err := NewServer(WithServerConfig(sCfg)) + srv, err := NewServer(sCfg) if err != nil { return err } @@ -195,7 +170,8 @@ func (ir *Infrared) init() error { return err } - ir.filter = NewFilter(WithFilterConfig(ir.cfg.FiltersConfig)) + ir.filter = NewFilter(ir.cfg.FiltersConfig) + ir.filter.filterers = append(ir.filter.filterers, ir.Filters...) return nil } diff --git a/pkg/infrared/infrared_test.go b/pkg/infrared/infrared_test.go index 0795e40..6b6afb5 100644 --- a/pkg/infrared/infrared_test.go +++ b/pkg/infrared/infrared_test.go @@ -146,7 +146,7 @@ func NewVirtualInfrared( cfg ir.Config, sendProxyProtocol bool, ) (*VirtualInfrared, net.Conn) { - vir := ir.NewWithConfig(cfg) + vir := ir.New(cfg) connChan := make(chan net.Conn) vl := &VirtualListener{ @@ -227,8 +227,11 @@ func TestInfrared_SendProxyProtocol_False(t *testing.T) { func TestInfrared_ReceiveProxyProtocol_True(t *testing.T) { cfg := ir.NewConfig(). - WithProxyProtocolReceive(true). - WithProxyProtocolTrustedCIDRs("127.0.0.1/32") + WithProxyProtocolConfig( + ir.NewProxyProtocolConfig(). + WithReceive(true). + WithTrustedCIDRs("127.0.0.1/32"), + ) vi, _ := NewVirtualInfrared(cfg, false) go vi.MustListenAndServe(t) @@ -247,7 +250,10 @@ func TestInfrared_ReceiveProxyProtocol_True(t *testing.T) { func TestInfrared_ReceiveProxyProtocol_False(t *testing.T) { cfg := ir.NewConfig(). - WithProxyProtocolReceive(false) + WithProxyProtocolConfig( + ir.NewProxyProtocolConfig(). + WithReceive(false), + ) vi, _ := NewVirtualInfrared(cfg, false) go vi.MustListenAndServe(t) @@ -264,8 +270,10 @@ func TestInfrared_ReceiveProxyProtocol_False(t *testing.T) { func TestInfrared_ReceiveProxyProtocol_True_ErrNoTrustedCIDRs(t *testing.T) { cfg := ir.NewConfig(). - WithProxyProtocolReceive(true). - WithProxyProtocolTrustedCIDRs() + WithProxyProtocolConfig( + ir.NewProxyProtocolConfig(). + WithReceive(true), + ) vi, _ := NewVirtualInfrared(cfg, false) @@ -287,8 +295,11 @@ func TestInfrared_ReceiveProxyProtocol_True_ErrNoTrustedCIDRs(t *testing.T) { func TestInfrared_ReceiveProxyProtocol_True_UntrustedIP(t *testing.T) { cfg := ir.NewConfig(). - WithProxyProtocolReceive(true). - WithProxyProtocolTrustedCIDRs("127.0.0.1/32") + WithProxyProtocolConfig( + ir.NewProxyProtocolConfig(). + WithReceive(true). + WithTrustedCIDRs("127.0.0.1/32"), + ) vi, _ := NewVirtualInfrared(cfg, false) go vi.MustListenAndServe(t) diff --git a/pkg/infrared/proxy_protocol.go b/pkg/infrared/proxy_protocol.go index afcf372..fff5844 100644 --- a/pkg/infrared/proxy_protocol.go +++ b/pkg/infrared/proxy_protocol.go @@ -17,6 +17,27 @@ type ProxyProtocolConfig struct { TrustedCIDRs []string `yaml:"trustedCIDRs"` } +func NewProxyProtocolConfig() ProxyProtocolConfig { + return ProxyProtocolConfig{ + TrustedCIDRs: make([]string, 0), + } +} + +func (cfg ProxyProtocolConfig) WithReceive(receive bool) ProxyProtocolConfig { + cfg.Receive = receive + return cfg +} + +func (cfg ProxyProtocolConfig) WithTrustedCIDRs(trustedCIDRs ...string) ProxyProtocolConfig { + cfg.TrustedCIDRs = trustedCIDRs + return cfg +} + +func (cfg ProxyProtocolConfig) AddTrustedCIDRs(trustedCIDRs ...string) ProxyProtocolConfig { + cfg.TrustedCIDRs = append(cfg.TrustedCIDRs, trustedCIDRs...) + return cfg +} + func newProxyProtocolListener(l net.Listener, trustedCIDRs []string) (net.Listener, error) { if len(trustedCIDRs) == 0 { return nil, ErrNoTrustedCIDRs diff --git a/pkg/infrared/rate_limiter.go b/pkg/infrared/rate_limiter.go index 106ef2a..f193b07 100644 --- a/pkg/infrared/rate_limiter.go +++ b/pkg/infrared/rate_limiter.go @@ -17,6 +17,23 @@ type RateLimiterConfig struct { WindowLength time.Duration `yaml:"windowLength"` } +func NewRateLimiterConfig() RateLimiterConfig { + return RateLimiterConfig{ + RequestLimit: 10, + WindowLength: time.Second, + } +} + +func (cfg RateLimiterConfig) WithWindowLength(windowLength time.Duration) RateLimiterConfig { + cfg.WindowLength = windowLength + return cfg +} + +func (cfg RateLimiterConfig) WithRequestLimit(requestLimit int) RateLimiterConfig { + cfg.RequestLimit = requestLimit + return cfg +} + func RateLimit(requestLimit int, windowLength time.Duration, options ...RateLimiterOption) Filterer { return newRateLimiter(requestLimit, windowLength, options...).Filterer() } diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index 842884a..7934f49 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -1,8 +1,10 @@ package infrared import ( + "crypto/rand" "encoding/json" "errors" + "io" "net" "strings" "sync" @@ -12,10 +14,13 @@ import ( "github.com/cespare/xxhash/v2" "github.com/haveachin/infrared/pkg/infrared/protocol" "github.com/haveachin/infrared/pkg/infrared/protocol/status" + "github.com/rs/zerolog/log" ) var ( ErrNoServers = errors.New("no servers to route to") + + errServerNotReachable = errors.New("server not reachable") ) type ( @@ -23,42 +28,31 @@ type ( ServerDomain string ) -type ServerConfigFunc func(cfg *ServerConfig) - -func WithServerConfig(c ServerConfig) ServerConfigFunc { - return func(cfg *ServerConfig) { - *cfg = c - } +type ServerConfig struct { + Domains []ServerDomain `yaml:"domains"` + Addresses []ServerAddress `yaml:"addresses"` + SendProxyProtocol bool `yaml:"sendProxyProtocol"` } -func WithServerDomains(sd ...ServerDomain) ServerConfigFunc { - return func(cfg *ServerConfig) { - cfg.Domains = sd - } +func NewServerConfig() ServerConfig { + return ServerConfig{} } -func WithServerAddresses(addr ...ServerAddress) ServerConfigFunc { - return func(cfg *ServerConfig) { - cfg.Addresses = addr - } +func (cfg ServerConfig) WithDomains(sd ...ServerDomain) ServerConfig { + cfg.Domains = sd + return cfg } -type ServerConfig struct { - Domains []ServerDomain `yaml:"domains"` - Addresses []ServerAddress `yaml:"addresses"` - SendProxyProtocol bool `yaml:"sendProxyProtocol"` +func (cfg ServerConfig) WithAddresses(addr ...ServerAddress) ServerConfig { + cfg.Addresses = addr + return cfg } type Server struct { cfg ServerConfig } -func NewServer(fns ...ServerConfigFunc) (*Server, error) { - var cfg ServerConfig - for _, fn := range fns { - fn(&cfg) - } - +func NewServer(cfg ServerConfig) (*Server, error) { if len(cfg.Addresses) == 0 { return nil, errors.New("no addresses") } @@ -185,10 +179,12 @@ func (r DialServerResponder) respondeToStatusRequest(req ServerRequest, srv *Ser respProv, ok := r.respProvs[srv] if !ok { respProv = &statusResponseProvider{ - server: srv, - cacheTTL: 30 * time.Second, - statusHash: make(map[protocol.Version]uint64), - statusResponseCache: make(map[uint64]*statusCacheEntry), + server: srv, + cache: statusResponseCache{ + ttl: 3 * time.Second, + statusHash: make(map[protocol.Version]uint64), + statusResponseCache: make(map[uint64]*statusCacheEntry), + }, } r.respProvs[srv] = respProv } @@ -219,11 +215,57 @@ func (e statusCacheEntry) isExpired() bool { type statusResponseProvider struct { server *Server + cache statusResponseCache +} - mu sync.Mutex - cacheTTL time.Duration - statusHash map[protocol.Version]uint64 - statusResponseCache map[uint64]*statusCacheEntry +func (s *statusResponseProvider) StatusResponse( + cliAddr net.Addr, + protVer protocol.Version, + readPks [2]protocol.Packet, +) (status.ResponseJSON, protocol.Packet, error) { + cacheRespone := false + if s.cache.ttl > 0 { + statusResp, statusPk, err := s.cache.statusResponse(protVer) + if err == nil { + return statusResp, statusPk, nil + } + cacheRespone = true + } + + statusResp, statusPk, err := s.requestNewStatusResponseJSON(cliAddr, readPks) + switch { + case errors.Is(err, errServerNotReachable): + respJSON := status.ResponseJSON{ + Version: status.VersionJSON{ + Name: "Infrared", + Protocol: int(protocol.Version1_20_2), + }, + Description: status.DescriptionJSON{ + Text: "Hello there!", + }, + } + bb, err := json.Marshal(respJSON) + if err != nil { + return status.ResponseJSON{}, protocol.Packet{}, err + } + pk := protocol.Packet{} + status.ClientBoundResponse{ + JSONResponse: protocol.String(string(bb)), + }.Marshal(&pk) + return respJSON, pk, nil + default: + if err != nil { + return status.ResponseJSON{}, protocol.Packet{}, err + } + } + + if cacheRespone { + if err := s.cache.cacheStatusResponse(protVer, statusResp, statusPk); err != nil { + return status.ResponseJSON{}, protocol.Packet{}, err + } + } + + return statusResp, statusPk, nil } func (s *statusResponseProvider) requestNewStatusResponseJSON( @@ -232,7 +274,7 @@ func (s *statusResponseProvider) requestNewStatusResponseJSON( ) (status.ResponseJSON, protocol.Packet, error) { rc, err := s.server.Dial() if err != nil { - return status.ResponseJSON{}, protocol.Packet{}, err + return status.ResponseJSON{}, protocol.Packet{}, errServerNotReachable } if s.server.cfg.SendProxyProtocol { @@ -264,15 +306,16 @@ func (s *statusResponseProvider) requestNewStatusResponseJSON( return respJSON, pk, nil } -func (s *statusResponseProvider) StatusResponse( - cliAddr net.Addr, +type statusResponseCache struct { + mu sync.Mutex + ttl time.Duration + statusHash map[protocol.Version]uint64 + statusResponseCache map[uint64]*statusCacheEntry +} + +func (s *statusResponseCache) statusResponse( protVer protocol.Version, - readPks [2]protocol.Packet, ) (status.ResponseJSON, protocol.Packet, error) { - if s.cacheTTL <= 0 { - return s.requestNewStatusResponseJSON(cliAddr, readPks) - } - // Prunes all expired status reponses s.prune() @@ -282,35 +325,39 @@ func (s *statusResponseProvider) StatusResponse( hash, okHash := s.statusHash[protVer] entry, okCache := s.statusResponseCache[hash] if !okHash || !okCache { - return s.cacheResponse(cliAddr, protVer, readPks) + return status.ResponseJSON{}, protocol.Packet{}, errors.New("not in cache") } return entry.responseJSON, entry.responsePk, nil } -func (s *statusResponseProvider) cacheResponse( - cliAddr net.Addr, +func (s *statusResponseCache) cacheStatusResponse( protVer protocol.Version, - readPks [2]protocol.Packet, -) (status.ResponseJSON, protocol.Packet, error) { - newStatusResp, pk, err := s.requestNewStatusResponseJSON(cliAddr, readPks) - if err != nil { - return status.ResponseJSON{}, protocol.Packet{}, err - } + statusResp status.ResponseJSON, + respPk protocol.Packet, +) error { + s.mu.Lock() + defer s.mu.Unlock() - hash := xxhash.New().Sum64() - s.statusHash[protVer] = hash + log.Info().Msg("new cache entry") - s.statusResponseCache[hash] = &statusCacheEntry{ - expiresAt: time.Now().Add(s.cacheTTL), - responseJSON: newStatusResp, - responsePk: pk, + hash := xxhash.New() + if _, err := io.CopyN(hash, rand.Reader, 64); err != nil { + return err } + hashSum := hash.Sum64() - return newStatusResp, pk, nil + s.statusHash[protVer] = hashSum + s.statusResponseCache[hashSum] = &statusCacheEntry{ + expiresAt: time.Now().Add(s.ttl), + responseJSON: statusResp, + responsePk: respPk, + } + + return nil } -func (s *statusResponseProvider) prune() { +func (s *statusResponseCache) prune() { s.mu.Lock() defer s.mu.Unlock() @@ -325,6 +372,7 @@ func (s *statusResponseProvider) prune() { for protVer, hash := range s.statusHash { for _, expiredHash := range expiredHashes { if hash == expiredHash { + log.Info().Msg("delete cache entry") delete(s.statusHash, protVer) } } From 67387da3319a21e605304b2c9787cba04b65f77e Mon Sep 17 00:00:00 2001 From: haveachin Date: Sat, 10 Feb 2024 18:28:00 +0100 Subject: [PATCH 2/6] docs: touch up description --- docs/.vitepress/config.mts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 2683a39..d4d70ac 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,8 +3,8 @@ import { defineConfig} from 'vitepress' export default defineConfig({ lang: 'en-US', title: 'Infrared', - titleTemplate: ':title | Minecraft Proxy', - description: 'Minecraft Proxy', + titleTemplate: ':title | Reverse Proxy', + description: 'The Minecraft Reverse Proxy', cleanUrls: true, head: [ [ From 12d0e7bd9ef37db6f711712d4e5a869a9c3cfa7f Mon Sep 17 00:00:00 2001 From: haveachin Date: Sat, 24 Feb 2024 20:01:47 +0100 Subject: [PATCH 3/6] feat: add status response --- cmd/infrared/main.go | 3 +- pkg/infrared/conn.go | 1 + pkg/infrared/infrared.go | 13 +-- pkg/infrared/protocol/versions.go | 95 ++++++++++++++++----- pkg/infrared/server.go | 8 +- pkg/infrared/status_response.go | 137 ++++++++++++++++++++++++++++++ tools/malpk/main.go | 2 +- 7 files changed, 227 insertions(+), 32 deletions(-) create mode 100644 pkg/infrared/status_response.go diff --git a/cmd/infrared/main.go b/cmd/infrared/main.go index bbb8a43..7836b5a 100644 --- a/cmd/infrared/main.go +++ b/cmd/infrared/main.go @@ -75,6 +75,8 @@ func initLogger() { log.Debug(). Str("level", logLevel). Msg("Log level set") + + ir.Log = log.Logger } func main() { @@ -104,7 +106,6 @@ func run() error { ConfigPath: configPath, ProxiesPath: proxiesDir, }) - srv.Logger = log.Logger errChan := make(chan error, 1) go func() { diff --git a/pkg/infrared/conn.go b/pkg/infrared/conn.go index 5bc786c..49d01d6 100644 --- a/pkg/infrared/conn.go +++ b/pkg/infrared/conn.go @@ -113,6 +113,7 @@ type clientConn struct { handshake handshaking.ServerBoundHandshake loginStart login.ServerBoundLoginStart reqDomain ServerDomain + protoVer protocol.Version } func newClientConn(c net.Conn) (*clientConn, func()) { diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index 67734a3..140c64e 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -11,6 +11,8 @@ import ( "github.com/rs/zerolog" ) +var Log = zerolog.Nop() + type Config struct { BindAddr string `yaml:"bind"` KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"` @@ -78,7 +80,6 @@ type ( ) type Infrared struct { - Logger zerolog.Logger NewListenerFunc NewListenerFunc NewServerRequesterFunc NewServerRequesterFunc Filters []Filterer @@ -105,7 +106,7 @@ func NewWithConfigProvider(prv ConfigProvider) *Infrared { } func (ir *Infrared) initListener() error { - ir.Logger.Info(). + Log.Info(). Str("bind", ir.cfg.BindAddr). Msg("Starting listener") @@ -186,7 +187,7 @@ func (ir *Infrared) ListenAndServe() error { if errors.Is(err, net.ErrClosed) { return nil } else if err != nil { - ir.Logger.Debug(). + Log.Debug(). Err(err). Msg("Error accepting new connection") @@ -199,7 +200,7 @@ func (ir *Infrared) ListenAndServe() error { func (ir *Infrared) handleNewConn(c net.Conn) { if err := ir.filter.Filter(c); err != nil { - ir.Logger.Debug(). + Log.Debug(). Err(err). Msg("Filtered connection") return @@ -212,7 +213,7 @@ func (ir *Infrared) handleNewConn(c net.Conn) { }() if err := ir.handleConn(conn); err != nil { - ir.Logger.Debug(). + Log.Debug(). Err(err). Msg("Error while handling connection") } @@ -324,7 +325,7 @@ func (ir *Infrared) handlePipe(c *clientConn, resp ServerResponse) error { func (ir *Infrared) pipe(dst io.WriteCloser, src io.ReadCloser, srcClosedChan chan struct{}) { if _, err := io.Copy(dst, src); err != nil && !errors.Is(err, io.EOF) { - ir.Logger.Debug(). + Log.Debug(). Err(err). Msg("Connection closed unexpectedly") } diff --git a/pkg/infrared/protocol/versions.go b/pkg/infrared/protocol/versions.go index 3a6f82f..89d282f 100644 --- a/pkg/infrared/protocol/versions.go +++ b/pkg/infrared/protocol/versions.go @@ -1,31 +1,84 @@ package protocol -import "strconv" - type Version int32 const ( + Version1_7_2 Version = 4 + Version1_7_3 Version = 4 + Version1_7_4 Version = 4 + Version1_7_5 Version = 4 + Version1_7_6 Version = 5 + Version1_7_7 Version = 5 + Version1_7_8 Version = 5 + Version1_7_9 Version = 5 + Version1_7_10 Version = 5 + + Version1_8 Version = 47 + Version1_8_1 Version = 47 + Version1_8_2 Version = 47 + Version1_8_3 Version = 47 + Version1_8_4 Version = 47 + Version1_8_5 Version = 47 + Version1_8_6 Version = 47 + Version1_8_7 Version = 47 + Version1_8_8 Version = 47 + Version1_8_9 Version = 47 + + Version1_9 Version = 107 + Version1_9_1 Version = 108 + Version1_9_2 Version = 109 + Version1_9_3 Version = 110 + Version1_9_4 Version = 110 + + Version1_10 Version = 210 + Version1_10_1 Version = 210 + Version1_10_2 Version = 210 + + Version1_11 Version = 315 + Version1_11_1 Version = 316 + Version1_11_2 Version = 316 + + Version1_12 Version = 335 + Version1_12_1 Version = 338 + Version1_12_2 Version = 340 + + Version1_13 Version = 393 + Version1_13_1 Version = 401 + Version1_13_2 Version = 404 + + Version1_14 Version = 477 + Version1_14_1 Version = 480 + Version1_14_2 Version = 485 + Version1_14_3 Version = 490 + Version1_14_4 Version = 498 + + Version1_15 Version = 573 + Version1_15_1 Version = 575 + Version1_15_2 Version = 578 + + Version1_16 Version = 735 + Version1_16_1 Version = 736 + Version1_16_2 Version = 751 + Version1_16_3 Version = 753 + Version1_16_4 Version = 754 + Version1_16_5 Version = 754 + + Version1_17 Version = 755 + Version1_17_1 Version = 756 + + Version1_18 Version = 757 + Version1_18_1 Version = 757 Version1_18_2 Version = 758 + Version1_19 Version = 759 + Version1_19_1 Version = 760 + Version1_19_2 Version = 760 Version1_19_3 Version = 761 + Version1_19_4 Version = 762 + + Version1_20 Version = 763 + Version1_20_1 Version = 763 Version1_20_2 Version = 764 + Version1_20_3 Version = 765 + Version1_20_4 Version = 765 ) - -func (v Version) Name() string { - switch v { - case Version1_18_2: - return "1.18.2" - case Version1_19: - return "1.19" - case Version1_19_3: - return "1.19.3" - case Version1_20_2: - return "1.20.2" - default: - return strconv.Itoa(int(v)) - } -} - -func (v Version) ProtocolNumber() int32 { - return int32(v) -} diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index 7934f49..44a3a02 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -29,9 +29,11 @@ type ( ) type ServerConfig struct { - Domains []ServerDomain `yaml:"domains"` - Addresses []ServerAddress `yaml:"addresses"` - SendProxyProtocol bool `yaml:"sendProxyProtocol"` + Domains []ServerDomain `yaml:"domains"` + Addresses []ServerAddress `yaml:"addresses"` + SendProxyProtocol bool `yaml:"sendProxyProtocol"` + ServerStatusResponse ServerStatusResponseConfig `yaml:"statusResponse"` + OverrideServerStatusResponse OverrideServerStatusResponseConfig `yaml:"overrideStatusResponse"` } func NewServerConfig() ServerConfig { diff --git a/pkg/infrared/status_response.go b/pkg/infrared/status_response.go new file mode 100644 index 0000000..3ac927b --- /dev/null +++ b/pkg/infrared/status_response.go @@ -0,0 +1,137 @@ +package infrared + +import ( + "encoding/base64" + _ "image/png" + "os" + "strings" + "sync" + + "github.com/haveachin/infrared/pkg/infrared/protocol/status" +) + +type PlayerSampleConfig struct { + Name string `yaml:"name"` + UUID string `yaml:"uuid"` +} + +type PlayerSamples []PlayerSampleConfig + +func (ps PlayerSamples) PlayerSampleJSON() []status.PlayerSampleJSON { + ss := make([]status.PlayerSampleJSON, len(ps)) + for i, s := range ps { + ss[i] = status.PlayerSampleJSON{ + Name: s.Name, + ID: s.UUID, + } + } + return ss +} + +type ServerStatusResponseConfig struct { + VersionName string `yaml:"versionName"` + ProtocolNumber int `yaml:"protocolNumber"` + MaxPlayerCount int `yaml:"maxPlayerCount"` + PlayerCount int `yaml:"playerCount"` + PlayerSamples PlayerSamples `yaml:"playerSamples"` + Icon string `yaml:"icon"` + MOTD string `yaml:"motd"` + + once sync.Once + respJSON status.ResponseJSON +} + +func (r ServerStatusResponseConfig) ResponseJSON() status.ResponseJSON { + r.once.Do(func() { + r.respJSON = status.ResponseJSON{ + Version: status.VersionJSON{ + Name: r.VersionName, + Protocol: r.ProtocolNumber, + }, + Players: status.PlayersJSON{ + Max: r.MaxPlayerCount, + Online: r.PlayerCount, + Sample: r.PlayerSamples.PlayerSampleJSON(), + }, + Favicon: parseServerIcon(r.Icon), + Description: status.DescriptionJSON{ + Text: r.MOTD, + }, + } + }) + + return r.respJSON +} + +type OverrideServerStatusResponseConfig struct { + VersionName *string `yaml:"versionName"` + ProtocolNumber *int `yaml:"protocolNumber"` + MaxPlayerCount *int `yaml:"maxPlayerCount"` + PlayerCount *int `yaml:"playerCount"` + PlayerSamples PlayerSamples `yaml:"playerSamples"` + Icon *string `yaml:"icon"` + MOTD *string `yaml:"motd"` + + iconOnce sync.Once +} + +func (r OverrideServerStatusResponseConfig) OverrideResponseJSON(resp status.ResponseJSON) status.ResponseJSON { + if r.Icon != nil { + r.iconOnce.Do(func() { + icon := parseServerIcon(*r.Icon) + r.Icon = &icon + }) + resp.Favicon = *r.Icon + } + + if r.VersionName != nil { + resp.Version.Name = *r.VersionName + } + + if r.ProtocolNumber != nil { + resp.Version.Protocol = *r.ProtocolNumber + } + + if r.MaxPlayerCount != nil { + resp.Players.Max = *r.MaxPlayerCount + } + + if r.PlayerCount != nil { + resp.Players.Online = *r.PlayerCount + } + + if len(r.PlayerSamples) != 0 { + resp.Players.Sample = r.PlayerSamples.PlayerSampleJSON() + } + + if r.MOTD != nil { + resp.Description = status.DescriptionJSON{ + Text: *r.MOTD, + } + } + + return resp +} + +func parseServerIcon(iconStr string) string { + if iconStr == "" { + return "" + } + + const base64PNGHeader = "data:image/png;base64," + if strings.HasPrefix(iconStr, base64PNGHeader) { + return iconStr + } + + iconBytes, err := os.ReadFile(iconStr) + if err != nil { + Log.Error(). + Err(err). + Str("iconPath", iconStr). + Msg("Failed to open icon file") + return "" + } + + iconBase64 := base64.StdEncoding.EncodeToString(iconBytes) + return base64PNGHeader + iconBase64 +} diff --git a/tools/malpk/main.go b/tools/malpk/main.go index 08d8bf6..2520e6f 100644 --- a/tools/malpk/main.go +++ b/tools/malpk/main.go @@ -16,7 +16,7 @@ func initPayload() { buf := new(bytes.Buffer) _, _ = protocol.VarInt(0x200000).WriteTo(buf) _, _ = protocol.VarInt(handshaking.ServerBoundHandshakeID).WriteTo(buf) - _, _ = protocol.VarInt(protocol.Version1_20_2.ProtocolNumber()).WriteTo(buf) + _, _ = protocol.VarInt(protocol.Version1_20_2).WriteTo(buf) _, _ = protocol.String("localhost").WriteTo(buf) _, _ = protocol.UnsignedShort(25565).WriteTo(buf) _, _ = protocol.Byte(2).WriteTo(buf) From 62a98bf062d7460f90b78df44c85d72ea9027a2e Mon Sep 17 00:00:00 2001 From: haveachin Date: Fri, 1 Mar 2024 14:21:10 +0100 Subject: [PATCH 4/6] feat: add placeholder status --- pkg/infrared/conn.go | 1 - pkg/infrared/handshake_response.go | 204 ++++++++++++++++++ pkg/infrared/infrared.go | 11 +- .../protocol/status/clientbound_response.go | 10 +- pkg/infrared/server.go | 63 +++--- pkg/infrared/status_response.go | 137 ------------ 6 files changed, 250 insertions(+), 176 deletions(-) create mode 100644 pkg/infrared/handshake_response.go delete mode 100644 pkg/infrared/status_response.go diff --git a/pkg/infrared/conn.go b/pkg/infrared/conn.go index 49d01d6..5bc786c 100644 --- a/pkg/infrared/conn.go +++ b/pkg/infrared/conn.go @@ -113,7 +113,6 @@ type clientConn struct { handshake handshaking.ServerBoundHandshake loginStart login.ServerBoundLoginStart reqDomain ServerDomain - protoVer protocol.Version } func newClientConn(c net.Conn) (*clientConn, func()) { diff --git a/pkg/infrared/handshake_response.go b/pkg/infrared/handshake_response.go new file mode 100644 index 0000000..48f42f1 --- /dev/null +++ b/pkg/infrared/handshake_response.go @@ -0,0 +1,204 @@ +package infrared + +import ( + "encoding/base64" + "encoding/json" + _ "image/png" + "os" + "strings" + "sync" + + "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/login" + "github.com/haveachin/infrared/pkg/infrared/protocol/status" +) + +type PlayerSampleConfig struct { + Name string `yaml:"name"` + UUID string `yaml:"uuid"` +} + +type PlayerSamples []PlayerSampleConfig + +func (ps PlayerSamples) PlayerSampleJSON() []status.PlayerSampleJSON { + psJSON := make([]status.PlayerSampleJSON, len(ps)) + for i, s := range ps { + psJSON[i] = status.PlayerSampleJSON{ + Name: s.Name, + ID: s.UUID, + } + } + return psJSON +} + +type HandshakeStatusResponseConfig struct { + VersionName string `yaml:"versionName"` + ProtocolNumber int `yaml:"protocolNumber"` + MaxPlayerCount int `yaml:"maxPlayerCount"` + PlayerCount int `yaml:"playerCount"` + PlayerSamples PlayerSamples `yaml:"playerSamples"` + Icon string `yaml:"icon"` + MOTD string `yaml:"motd"` +} + +type HandshakeResponseConfig struct { + StatusConfig HandshakeStatusResponseConfig `yaml:"status"` + Message string `yaml:"message"` +} + +type HandshakeResponse struct { + Config HandshakeResponseConfig + + statusOnce sync.Once + statusRespJSON status.ResponseJSON + statusRespPk protocol.Packet + + loginOnce sync.Once + loginRespPk protocol.Packet +} + +func (r *HandshakeResponse) StatusResponse(protVer protocol.Version) (status.ResponseJSON, protocol.Packet) { + r.statusOnce.Do(func() { + cfg := r.Config.StatusConfig + + protNum := cfg.ProtocolNumber + if protNum < 0 { + protNum = int(protVer) + } + + r.statusRespJSON = status.ResponseJSON{ + Version: status.VersionJSON{ + Name: cfg.VersionName, + Protocol: protNum, + }, + Players: status.PlayersJSON{ + Max: cfg.MaxPlayerCount, + Online: cfg.PlayerCount, + Sample: cfg.PlayerSamples.PlayerSampleJSON(), + }, + Favicon: parseServerIcon(cfg.Icon), + Description: parseJSONTextComponent(cfg.MOTD), + } + + respBytes, err := json.Marshal(r.statusRespJSON) + if err != nil { + panic(err) + } + + statusPk := status.ClientBoundResponse{ + JSONResponse: protocol.String(string(respBytes)), + } + + if err := statusPk.Marshal(&r.statusRespPk); err != nil { + panic(err) + } + }) + + return r.statusRespJSON, r.statusRespPk +} + +func (r *HandshakeResponse) LoginReponse() protocol.Packet { + r.loginOnce.Do(func() { + msg := parseJSONTextComponent(r.Config.Message) + disconnectPk := login.ClientBoundDisconnect{ + Reason: protocol.String(msg), + } + + if err := disconnectPk.Marshal(&r.loginRespPk); err != nil { + panic(err) + } + }) + + return r.loginRespPk +} + +type OverrideStatusResponseConfig struct { + VersionName *string `yaml:"versionName"` + ProtocolNumber *int `yaml:"protocolNumber"` + MaxPlayerCount *int `yaml:"maxPlayerCount"` + PlayerCount *int `yaml:"playerCount"` + PlayerSamples PlayerSamples `yaml:"playerSamples"` + Icon *string `yaml:"icon"` + MOTD *string `yaml:"motd"` +} + +type OverrideStatusResponse struct { + Config OverrideStatusResponseConfig + + once sync.Once + overrideFn func(resp status.ResponseJSON) status.ResponseJSON +} + +func (r *OverrideStatusResponse) OverrideStatusResponseJSON(resp status.ResponseJSON) status.ResponseJSON { + r.once.Do(func() { + cfg := r.Config + icon := parseServerIcon(*cfg.Icon) + playerSamples := cfg.PlayerSamples.PlayerSampleJSON() + motd := parseJSONTextComponent(*cfg.MOTD) + + r.overrideFn = func(resp status.ResponseJSON) status.ResponseJSON { + if cfg.Icon != nil { + resp.Favicon = icon + } + + if cfg.VersionName != nil { + resp.Version.Name = *cfg.VersionName + } + + if cfg.ProtocolNumber != nil { + resp.Version.Protocol = *cfg.ProtocolNumber + } + + if cfg.MaxPlayerCount != nil { + resp.Players.Max = *cfg.MaxPlayerCount + } + + if cfg.PlayerCount != nil { + resp.Players.Online = *cfg.PlayerCount + } + + if len(cfg.PlayerSamples) != 0 { + resp.Players.Sample = playerSamples + } + + if cfg.MOTD != nil { + resp.Description = motd + } + + return resp + } + }) + + return r.overrideFn(resp) +} + +func parseServerIcon(iconStr string) string { + if iconStr == "" { + return "" + } + + const base64PNGHeader = "data:image/png;base64," + if strings.HasPrefix(iconStr, base64PNGHeader) { + return iconStr + } + + iconBytes, err := os.ReadFile(iconStr) + if err != nil { + Log.Error(). + Err(err). + Str("iconPath", iconStr). + Msg("Failed to open icon file") + return "" + } + + iconBase64 := base64.StdEncoding.EncodeToString(iconBytes) + return base64PNGHeader + iconBase64 +} + +func parseJSONTextComponent(s string) json.RawMessage { + var motdJSON json.RawMessage + if err := json.Unmarshal([]byte(s), &motdJSON); err != nil { + motdJSON = []byte(`{"text":"` + s + `"}`) + } + return motdJSON +} diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index 140c64e..305eca1 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -246,7 +246,12 @@ func (ir *Infrared) handleConn(c *clientConn) error { ReadPackets: c.readPks, }) if err != nil { - return err + switch { + case errors.Is(err, ErrServerNotReachable) && c.handshake.IsLoginRequest(): + return ir.handleLoginDisconnect(c, resp) + default: + return err + } } if c.handshake.IsStatusRequest() { @@ -273,6 +278,10 @@ func handleStatus(c *clientConn, resp ServerResponse) error { return nil } +func (ir *Infrared) handleLoginDisconnect(c *clientConn, resp ServerResponse) error { + return c.WritePacket(resp.StatusResponse) +} + func (ir *Infrared) handleLogin(c *clientConn, resp ServerResponse) error { hsVersion := protocol.Version(c.handshake.ProtocolVersion) if err := c.loginStart.Unmarshal(c.readPks[1], hsVersion); err != nil { diff --git a/pkg/infrared/protocol/status/clientbound_response.go b/pkg/infrared/protocol/status/clientbound_response.go index a3c223c..874a73f 100644 --- a/pkg/infrared/protocol/status/clientbound_response.go +++ b/pkg/infrared/protocol/status/clientbound_response.go @@ -1,6 +1,10 @@ package status -import "github.com/haveachin/infrared/pkg/infrared/protocol" +import ( + "encoding/json" + + "github.com/haveachin/infrared/pkg/infrared/protocol" +) const ( ClientBoundResponseID int32 = 0x00 @@ -31,8 +35,8 @@ type ResponseJSON struct { Version VersionJSON `json:"version"` Players PlayersJSON `json:"players"` // This has to be any to support the new chat style system - Description any `json:"description"` - Favicon string `json:"favicon,omitempty"` + Description json.RawMessage `json:"description"` + Favicon string `json:"favicon,omitempty"` // Added since 1.19 PreviewsChat bool `json:"previewsChat"` // Added since 1.19.1 diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index 44a3a02..25edc53 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -18,9 +18,9 @@ import ( ) var ( - ErrNoServers = errors.New("no servers to route to") - - errServerNotReachable = errors.New("server not reachable") + ErrNoServers = errors.New("no servers to route to") + ErrServerNotFound = errors.New("server not found") + ErrServerNotReachable = errors.New("server not reachable") ) type ( @@ -29,11 +29,11 @@ type ( ) type ServerConfig struct { - Domains []ServerDomain `yaml:"domains"` - Addresses []ServerAddress `yaml:"addresses"` - SendProxyProtocol bool `yaml:"sendProxyProtocol"` - ServerStatusResponse ServerStatusResponseConfig `yaml:"statusResponse"` - OverrideServerStatusResponse OverrideServerStatusResponseConfig `yaml:"overrideStatusResponse"` + Domains []ServerDomain `yaml:"domains"` + Addresses []ServerAddress `yaml:"addresses"` + SendProxyProtocol bool `yaml:"sendProxyProtocol"` + DialTimeoutResponse HandshakeResponseConfig `yaml:"dialTimeoutResponse"` + OverrideStatusResponse OverrideStatusResponseConfig `yaml:"overrideStatus"` } func NewServerConfig() ServerConfig { @@ -52,6 +52,9 @@ func (cfg ServerConfig) WithAddresses(addr ...ServerAddress) ServerConfig { type Server struct { cfg ServerConfig + + dialTimeoutResp HandshakeResponse + overrideStatusResp OverrideStatusResponse } func NewServer(cfg ServerConfig) (*Server, error) { @@ -61,10 +64,16 @@ func NewServer(cfg ServerConfig) (*Server, error) { return &Server{ cfg: cfg, + dialTimeoutResp: HandshakeResponse{ + Config: cfg.DialTimeoutResponse, + }, + overrideStatusResp: OverrideStatusResponse{ + Config: cfg.OverrideStatusResponse, + }, }, nil } -func (s Server) Dial() (*ServerConn, error) { +func (s *Server) Dial() (*ServerConn, error) { c, err := net.Dial("tcp", string(s.cfg.Addresses[0])) if err != nil { return nil, err @@ -143,7 +152,7 @@ func (sg *ServerGateway) findServer(domain ServerDomain) *Server { func (sg *ServerGateway) RequestServer(req ServerRequest) (ServerResponse, error) { srv := sg.findServer(req.Domain) if srv == nil { - return ServerResponse{}, errors.New("server not found") + return ServerResponse{}, ErrServerNotFound } return sg.responder.RespondeToServerRequest(req, srv) @@ -168,7 +177,9 @@ func (r DialServerResponder) RespondeToServerRequest(req ServerRequest, srv *Ser func (r DialServerResponder) respondeToLoginRequest(_ ServerRequest, srv *Server) (ServerResponse, error) { rc, err := srv.Dial() if err != nil { - return ServerResponse{}, err + return ServerResponse{ + StatusResponse: srv.dialTimeoutResp.LoginReponse(), + }, ErrServerNotReachable } return ServerResponse{ @@ -235,28 +246,12 @@ func (s *statusResponseProvider) StatusResponse( } statusResp, statusPk, err := s.requestNewStatusResponseJSON(cliAddr, readPks) - switch { - case errors.Is(err, errServerNotReachable): - respJSON := status.ResponseJSON{ - Version: status.VersionJSON{ - Name: "Infrared", - Protocol: int(protocol.Version1_20_2), - }, - Description: status.DescriptionJSON{ - Text: "Hello there!", - }, - } - bb, err := json.Marshal(respJSON) - if err != nil { - return status.ResponseJSON{}, protocol.Packet{}, err - } - pk := protocol.Packet{} - status.ClientBoundResponse{ - JSONResponse: protocol.String(string(bb)), - }.Marshal(&pk) - return respJSON, pk, nil - default: - if err != nil { + if err != nil { + switch { + case errors.Is(err, ErrServerNotReachable): + respJSON, respPk := s.server.dialTimeoutResp.StatusResponse(protVer) + return respJSON, respPk, nil + default: return status.ResponseJSON{}, protocol.Packet{}, err } } @@ -276,7 +271,7 @@ func (s *statusResponseProvider) requestNewStatusResponseJSON( ) (status.ResponseJSON, protocol.Packet, error) { rc, err := s.server.Dial() if err != nil { - return status.ResponseJSON{}, protocol.Packet{}, errServerNotReachable + return status.ResponseJSON{}, protocol.Packet{}, ErrServerNotReachable } if s.server.cfg.SendProxyProtocol { diff --git a/pkg/infrared/status_response.go b/pkg/infrared/status_response.go deleted file mode 100644 index 3ac927b..0000000 --- a/pkg/infrared/status_response.go +++ /dev/null @@ -1,137 +0,0 @@ -package infrared - -import ( - "encoding/base64" - _ "image/png" - "os" - "strings" - "sync" - - "github.com/haveachin/infrared/pkg/infrared/protocol/status" -) - -type PlayerSampleConfig struct { - Name string `yaml:"name"` - UUID string `yaml:"uuid"` -} - -type PlayerSamples []PlayerSampleConfig - -func (ps PlayerSamples) PlayerSampleJSON() []status.PlayerSampleJSON { - ss := make([]status.PlayerSampleJSON, len(ps)) - for i, s := range ps { - ss[i] = status.PlayerSampleJSON{ - Name: s.Name, - ID: s.UUID, - } - } - return ss -} - -type ServerStatusResponseConfig struct { - VersionName string `yaml:"versionName"` - ProtocolNumber int `yaml:"protocolNumber"` - MaxPlayerCount int `yaml:"maxPlayerCount"` - PlayerCount int `yaml:"playerCount"` - PlayerSamples PlayerSamples `yaml:"playerSamples"` - Icon string `yaml:"icon"` - MOTD string `yaml:"motd"` - - once sync.Once - respJSON status.ResponseJSON -} - -func (r ServerStatusResponseConfig) ResponseJSON() status.ResponseJSON { - r.once.Do(func() { - r.respJSON = status.ResponseJSON{ - Version: status.VersionJSON{ - Name: r.VersionName, - Protocol: r.ProtocolNumber, - }, - Players: status.PlayersJSON{ - Max: r.MaxPlayerCount, - Online: r.PlayerCount, - Sample: r.PlayerSamples.PlayerSampleJSON(), - }, - Favicon: parseServerIcon(r.Icon), - Description: status.DescriptionJSON{ - Text: r.MOTD, - }, - } - }) - - return r.respJSON -} - -type OverrideServerStatusResponseConfig struct { - VersionName *string `yaml:"versionName"` - ProtocolNumber *int `yaml:"protocolNumber"` - MaxPlayerCount *int `yaml:"maxPlayerCount"` - PlayerCount *int `yaml:"playerCount"` - PlayerSamples PlayerSamples `yaml:"playerSamples"` - Icon *string `yaml:"icon"` - MOTD *string `yaml:"motd"` - - iconOnce sync.Once -} - -func (r OverrideServerStatusResponseConfig) OverrideResponseJSON(resp status.ResponseJSON) status.ResponseJSON { - if r.Icon != nil { - r.iconOnce.Do(func() { - icon := parseServerIcon(*r.Icon) - r.Icon = &icon - }) - resp.Favicon = *r.Icon - } - - if r.VersionName != nil { - resp.Version.Name = *r.VersionName - } - - if r.ProtocolNumber != nil { - resp.Version.Protocol = *r.ProtocolNumber - } - - if r.MaxPlayerCount != nil { - resp.Players.Max = *r.MaxPlayerCount - } - - if r.PlayerCount != nil { - resp.Players.Online = *r.PlayerCount - } - - if len(r.PlayerSamples) != 0 { - resp.Players.Sample = r.PlayerSamples.PlayerSampleJSON() - } - - if r.MOTD != nil { - resp.Description = status.DescriptionJSON{ - Text: *r.MOTD, - } - } - - return resp -} - -func parseServerIcon(iconStr string) string { - if iconStr == "" { - return "" - } - - const base64PNGHeader = "data:image/png;base64," - if strings.HasPrefix(iconStr, base64PNGHeader) { - return iconStr - } - - iconBytes, err := os.ReadFile(iconStr) - if err != nil { - Log.Error(). - Err(err). - Str("iconPath", iconStr). - Msg("Failed to open icon file") - return "" - } - - iconBase64 := base64.StdEncoding.EncodeToString(iconBytes) - return base64PNGHeader + iconBase64 -} From 020d7a82b75b2878d46b082b40e65fa85fd7c038 Mon Sep 17 00:00:00 2001 From: haveachin Date: Tue, 11 Jun 2024 16:19:12 +0200 Subject: [PATCH 5/6] feat: add proxy config docs --- configs/proxy.yml | 52 +- docs/.vitepress/config.mts | 1 - docs/community-projects.md | 4 +- docs/config/server-status.md | 4 + docs/getting-started.md | 6 +- docs/package-lock.json | 1092 +++++++++++++++++----------- docs/package.json | 2 +- go.mod | 8 +- go.sum | 8 + pkg/infrared/handshake_response.go | 67 +- pkg/infrared/infrared.go | 2 +- pkg/infrared/server.go | 27 +- tools/cachespam/main.go | 78 ++ 13 files changed, 872 insertions(+), 479 deletions(-) create mode 100644 docs/config/server-status.md create mode 100644 tools/cachespam/main.go diff --git a/configs/proxy.yml b/configs/proxy.yml index d4de154..1f40706 100644 --- a/configs/proxy.yml +++ b/configs/proxy.yml @@ -13,4 +13,54 @@ addresses: # Send a PROXY Protocol Header to the server to # forward the players IP address # -#sendProxyProtocol: true \ No newline at end of file +#sendProxyProtocol: true + +# Reponse that the player sees when the connection to the backend server +# times out during status or login request. +# +dialTimeoutResponse: + # The status response that should be displayed to the players + # + status: + + # The name of the version. + # This is displayed on the client side when the version is incompatible. + # + versionName: "Infrared 1.20.6" + + # The protocol version number determens if the version of the client is compatible with the server. + # A list of protocol versions can be found here: https://wiki.vg/Protocol_version_numbers + # Set this to -1 to mirror the client version back. + # + protocolNumber: 766 + maxPlayerCount: 20 + playerCount: 0 + playerSamples: + - name: "" + uuid: "" + + # Server icon can be provided as base64 or path to a file. + # + icon: "" + + # MOTD can be just a string or a JSON Text Component. + # + motd: "Unable to reach backend server" + + # Reason why the player was disconnected during login. + # + message: "Unable to reach backend server" + +# Override Status is similar to the dial timeout response status +# but instead you just can override some fields in the status. +# +overrideStatus: + versionName: "1.20.6" + protocolNumber: 766 + maxPlayerCount: 20 + playerCount: 0 + playerSamples: + - name: "" + uuid: "" + icon: "" + motd: "Unable to reach backend server" \ No newline at end of file diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index d4d70ac..0c60c3f 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -3,7 +3,6 @@ import { defineConfig} from 'vitepress' export default defineConfig({ lang: 'en-US', title: 'Infrared', - titleTemplate: ':title | Reverse Proxy', description: 'The Minecraft Reverse Proxy', cleanUrls: true, head: [ diff --git a/docs/community-projects.md b/docs/community-projects.md index d4d57d6..e275bfa 100644 --- a/docs/community-projects.md +++ b/docs/community-projects.md @@ -1,12 +1,12 @@ # Community Projects > [!NOTE] -> These projects are managed by the Infrared Community. +> These projects are managed by the Community. > We do **not** provide official support for these projects. > Please use their dedicated issue trackers or support channels provided by the respective project. > Thanks for understanding. -## Infrared for Pterodactyl +## Pterodactyl Egg An egg to run Infrared in Pterodactyl. \ Repo: [Shadowner/Infrared-Pterodactyl-egg](https://github.com/Shadowner/Infrared-Pterodactyl-egg) \ diff --git a/docs/config/server-status.md b/docs/config/server-status.md new file mode 100644 index 0000000..907531e --- /dev/null +++ b/docs/config/server-status.md @@ -0,0 +1,4 @@ +# Server Status + +The server status is what the server sends to clients to display in the server list. + diff --git a/docs/getting-started.md b/docs/getting-started.md index 123ceb2..b1337cb 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -27,9 +27,9 @@ The URL of your download should look something like this: ``` https://github.com/haveachin/infrared/releases/download/{version}/infrared_{architecture}.tar.gz ``` -For example: +For latest: ``` -https://github.com/haveachin/infrared/releases/download/v1.3.4/infrared_Linux_x86_64.tar.gz +https://github.com/haveachin/infrared/releases/latest/download/infrared_Linux_x86_64.tar.gz ``` ::: tip @@ -43,7 +43,7 @@ curl -LO https://github.com/haveachin/infrared/releases/download/{version}/infra Downloading by using Powershell on Windows: ```Powershell -Invoke-WebRequest -Uri https://github.com/haveachin/infrared/releases/download/v1.3.4/infrared_Windows_x86_64.zip -OutFile c:\infrared.zip +Invoke-WebRequest -Uri https://github.com/haveachin/infrared/releases/latest/download/infrared_Windows_x86_64.zip -OutFile c:\infrared.zip ``` ### Extracting the binary diff --git a/docs/package-lock.json b/docs/package-lock.json index 008de61..613622c 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "devDependencies": { - "vitepress": "^1.0.0-rc.40" + "vitepress": "^1.2.3" } }, "node_modules/@algolia/autocomplete-core": { @@ -13,6 +13,7 @@ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", "dev": true, + "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", "@algolia/autocomplete-shared": "1.9.3" @@ -23,6 +24,7 @@ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", "dev": true, + "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.9.3" }, @@ -35,6 +37,7 @@ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", "dev": true, + "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.9.3" }, @@ -48,145 +51,181 @@ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", "dev": true, + "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "node_modules/@algolia/cache-browser-local-storage": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.20.0.tgz", - "integrity": "sha512-uujahcBt4DxduBTvYdwO3sBfHuJvJokiC3BP1+O70fglmE1ShkH8lpXqZBac1rrU3FnNYSUs4pL9lBdTKeRPOQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", + "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.20.0" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/cache-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.20.0.tgz", - "integrity": "sha512-vCfxauaZutL3NImzB2G9LjLt36vKAckc6DhMp05An14kVo8F1Yofb6SIl6U3SaEz8pG2QOB9ptwM5c+zGevwIQ==", - "dev": true + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", + "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==", + "dev": true, + "license": "MIT" }, "node_modules/@algolia/cache-in-memory": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.20.0.tgz", - "integrity": "sha512-Wm9ak/IaacAZXS4mB3+qF/KCoVSBV6aLgIGFEtQtJwjv64g4ePMapORGmCyulCFwfePaRAtcaTbMcJF+voc/bg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", + "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.20.0" + "@algolia/cache-common": "4.23.3" } }, "node_modules/@algolia/client-account": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.20.0.tgz", - "integrity": "sha512-GGToLQvrwo7am4zVkZTnKa72pheQeez/16sURDWm7Seyz+HUxKi3BM6fthVVPUEBhtJ0reyVtuK9ArmnaKl10Q==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", + "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-analytics": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.20.0.tgz", - "integrity": "sha512-EIr+PdFMOallRdBTHHdKI3CstslgLORQG7844Mq84ib5oVFRVASuuPmG4bXBgiDbcsMLUeOC6zRVJhv1KWI0ug==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", + "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.20.0.tgz", - "integrity": "sha512-P3WgMdEss915p+knMMSd/fwiHRHKvDu4DYRrCRaBrsfFw7EQHon+EbRSm4QisS9NYdxbS04kcvNoavVGthyfqQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", + "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-personalization": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.20.0.tgz", - "integrity": "sha512-N9+zx0tWOQsLc3K4PVRDV8GUeOLAY0i445En79Pr3zWB+m67V+n/8w4Kw1C5LlbHDDJcyhMMIlqezh6BEk7xAQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", + "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/client-search": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.20.0.tgz", - "integrity": "sha512-zgwqnMvhWLdpzKTpd3sGmMlr4c+iS7eyyLGiaO51zDZWGMkpgoNVmltkzdBwxOVXz0RsFMznIxB9zuarUv4TZg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", + "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/client-common": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/logger-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.20.0.tgz", - "integrity": "sha512-xouigCMB5WJYEwvoWW5XDv7Z9f0A8VoXJc3VKwlHJw/je+3p2RcDXfksLI4G4lIVncFUYMZx30tP/rsdlvvzHQ==", - "dev": true + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", + "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==", + "dev": true, + "license": "MIT" }, "node_modules/@algolia/logger-console": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.20.0.tgz", - "integrity": "sha512-THlIGG1g/FS63z0StQqDhT6bprUczBI8wnLT3JWvfAQDZX5P6fCg7dG+pIrUBpDIHGszgkqYEqECaKKsdNKOUA==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", + "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/logger-common": "4.20.0" + "@algolia/logger-common": "4.23.3" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", + "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.20.0.tgz", - "integrity": "sha512-HbzoSjcjuUmYOkcHECkVTwAelmvTlgs48N6Owt4FnTOQdwn0b8pdht9eMgishvk8+F8bal354nhx/xOoTfwiAw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", + "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.20.0" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/requester-common": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.20.0.tgz", - "integrity": "sha512-9h6ye6RY/BkfmeJp7Z8gyyeMrmmWsMOCRBXQDs4mZKKsyVlfIVICpcSibbeYcuUdurLhIlrOUkH3rQEgZzonng==", - "dev": true + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", + "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==", + "dev": true, + "license": "MIT" }, "node_modules/@algolia/requester-node-http": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.20.0.tgz", - "integrity": "sha512-ocJ66L60ABSSTRFnCHIEZpNHv6qTxsBwJEPfYaSBsLQodm0F9ptvalFkHMpvj5DfE22oZrcrLbOYM2bdPJRHng==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", + "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/requester-common": "4.20.0" + "@algolia/requester-common": "4.23.3" } }, "node_modules/@algolia/transporter": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.20.0.tgz", - "integrity": "sha512-Lsii1pGWOAISbzeyuf+r/GPhvHMPHSPrTDWNcIzOE1SG1inlJHICaVe2ikuoRjcpgxZNU54Jl+if15SUCsaTUg==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", + "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/cache-common": "4.20.0", - "@algolia/logger-common": "4.20.0", - "@algolia/requester-common": "4.20.0" + "@algolia/cache-common": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/requester-common": "4.23.3" } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, + "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -195,30 +234,33 @@ } }, "node_modules/@docsearch/css": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.5.2.tgz", - "integrity": "sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==", - "dev": true + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==", + "dev": true, + "license": "MIT" }, "node_modules/@docsearch/js": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.5.2.tgz", - "integrity": "sha512-p1YFTCDflk8ieHgFJYfmyHBki1D61+U9idwrLh+GQQMrBSP3DLGKpy0XUJtPjAOPltcVbqsTjiPFfH7JImjUNg==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", "dev": true, + "license": "MIT", "dependencies": { - "@docsearch/react": "3.5.2", + "@docsearch/react": "3.6.0", "preact": "^10.0.0" } }, "node_modules/@docsearch/react": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.5.2.tgz", - "integrity": "sha512-9Ahcrs5z2jq/DcAvYtvlqEBHImbm4YJI8M9y0x6Tqg598P40HTEkX7hsMcIuThI+hTFxRGZ9hll0Wygm2yEjng==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", "dev": true, + "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.9.3", "@algolia/autocomplete-preset-algolia": "1.9.3", - "@docsearch/css": "3.5.2", + "@docsearch/css": "3.6.0", "algoliasearch": "^4.19.1" }, "peerDependencies": { @@ -243,13 +285,14 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -259,13 +302,14 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -275,13 +319,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -291,13 +336,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -307,13 +353,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -323,13 +370,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -339,13 +387,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -355,13 +404,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -371,13 +421,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -387,13 +438,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -403,13 +455,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -419,13 +472,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -435,13 +489,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -451,13 +506,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -467,13 +523,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -483,13 +540,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -499,13 +557,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -515,13 +574,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -531,13 +591,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -547,13 +608,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -563,13 +625,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -579,13 +642,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -595,13 +659,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -614,216 +679,295 @@ "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz", - "integrity": "sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz", - "integrity": "sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz", - "integrity": "sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz", - "integrity": "sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz", - "integrity": "sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz", - "integrity": "sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz", - "integrity": "sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz", - "integrity": "sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz", - "integrity": "sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz", - "integrity": "sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz", - "integrity": "sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz", - "integrity": "sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz", - "integrity": "sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.2.tgz", + "integrity": "sha512-guW5JeDzZ7uwOjTfCOFZ2VtVXk5tmkMzBYbKGfXsmAH1qYOej49L5jQDcGmwd6/OgvpmWhzO2GNJkQIFnbwLPQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shikijs/transformers": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.2.tgz", + "integrity": "sha512-ndqTWyHnxmsLkowhKWTam26opw8hg5a34y6FAUG/Xf6E49n3MM//nenKxXiWpPYkNPl1KZnYXB1k+Ia46wjOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "shiki": "1.6.2" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/linkify-it": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.5.tgz", - "integrity": "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==", - "dev": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/markdown-it": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-13.0.7.tgz", - "integrity": "sha512-U/CBi2YUUcTHBt5tjO2r5QV/x0Po6nsYwQU4Y04fBS6vfoImaiZ6f8bi3CjTCxBPQSO1LMyUqkByzi8AidyxfA==", + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", - "integrity": "sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", - "integrity": "sha512-b8S5dVS40rgHdDrw+DQi/xOM9ed+kSRZzfm1T74bMmBDCd8XO87NKlFYInzCtwvtWwXZvo1QxE2OSspTATWrbA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz", + "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" }, @@ -833,131 +977,173 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.15.tgz", - "integrity": "sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", "entities": "^4.5.0", "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.15.tgz", - "integrity": "sha512-wox0aasVV74zoXyblarOM3AZQz/Z+OunYcIHe1OsGclCHt8RsRm04DObjefaI82u6XDzv+qGWZ24tIsRAIi5MQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.15.tgz", - "integrity": "sha512-LCn5M6QpkpFsh3GQvs2mJUOAlBQcCco8D60Bcqmf3O3w5a+KWS5GvYbrrJBkgvL1BDnTp+e8q0lXCLgHhKguBA==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.23.6", - "@vue/compiler-core": "3.4.15", - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15", + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", "estree-walker": "^2.0.2", - "magic-string": "^0.30.5", - "postcss": "^8.4.33", - "source-map-js": "^1.0.2" + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.15.tgz", - "integrity": "sha512-1jdeQyiGznr8gjFDadVmOJqZiLNSsMa5ZgqavkPZ8O2wjHv0tVuAEsw5hTdUoUW4232vpBbL/wJhzVW/JwY1Uw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/devtools-api": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.1.tgz", - "integrity": "sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==", - "dev": true + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.2.1.tgz", + "integrity": "sha512-6oNCtyFOrNdqm6GUkFujsCgFlpbsHLnZqq7edeM/+cxAbMyCWvsaCsIMUaz7AiluKLccCGEM8fhOsjaKgBvb7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.2.1.tgz", + "integrity": "sha512-Wak/fin1X0Q8LLIfCAHBrdaaB+R6IdpSXsDByPHbQ3BmkCP0/cIo/oEGp9i0U2+gEqD4L3V9RDjNf1S34DTzQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.2.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.2.1.tgz", + "integrity": "sha512-PCJF4UknJmOal68+X9XHyVeQ+idv0LFujkTOIW30+GaMJqwFVN9LkQKX4gLqn61KkGMdJTzQ1bt7EJag3TI6AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.3.1" + } }, "node_modules/@vue/reactivity": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.15.tgz", - "integrity": "sha512-55yJh2bsff20K5O84MxSvXKPHHt17I2EomHznvFiJCAZpJTNW8IuLj1xZWMLELRhBK3kkFV/1ErZGHJfah7i7w==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/shared": "3.4.15" + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.15.tgz", - "integrity": "sha512-6E3by5m6v1AkW0McCeAyhHTw+3y17YCOKG0U0HDKDscV4Hs0kgNT5G+GCHak16jKgcCDHpI9xe5NKb8sdLCLdw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.15.tgz", - "integrity": "sha512-EVW8D6vfFVq3V/yDKNPBFkZKGMFSvZrUQmx196o/v2tHKdwWdiZjYUBS+0Ez3+ohRyF8Njwy/6FH5gYJ75liUw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/runtime-core": "3.4.15", - "@vue/shared": "3.4.15", + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.15.tgz", - "integrity": "sha512-3HYzaidu9cHjrT+qGUuDhFYvF/j643bHC6uUN9BgM11DVy+pM6ATsG6uPBLnkwOgs7BpJABReLmpL3ZPAsUaqw==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { - "vue": "3.4.15" + "vue": "3.4.27" } }, "node_modules/@vue/shared": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.15.tgz", - "integrity": "sha512-KzfPTxVaWfB+eGcGdbSf4CWdaXcGDqckoeXUh7SB3fZdEtzPCK2Vq9B/lRRL3yutax/LWITz+SwvgyOxz5V75g==", - "dev": true + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==", + "dev": true, + "license": "MIT" }, "node_modules/@vueuse/core": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.2.tgz", - "integrity": "sha512-AOyAL2rK0By62Hm+iqQn6Rbu8bfmbgaIMXcE3TSr7BdQ42wnSFlwIdPjInO62onYsEMK/yDMU8C6oGfDAtZ2qQ==", + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.10.0.tgz", + "integrity": "sha512-vexJ/YXYs2S42B783rI95lMt3GzEwkxzC8Hb0Ndpd8rD+p+Lk/Za4bd797Ym7yq4jXqdSyj3JLChunF/vyYjUw==", "dev": true, + "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" + "@vueuse/metadata": "10.10.0", + "@vueuse/shared": "10.10.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -979,14 +1165,15 @@ } }, "node_modules/@vueuse/integrations": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.7.2.tgz", - "integrity": "sha512-+u3RLPFedjASs5EKPc69Ge49WNgqeMfSxFn+qrQTzblPXZg6+EFzhjarS5edj2qAf6xQ93f95TUxRwKStXj/sQ==", + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.10.0.tgz", + "integrity": "sha512-vHGeK7X6mkdkpcm1eE9t3Cpm21pNVfZRwrjwwbrEs9XftnSgszF4831G2rei8Dt9cIYJIfFV+iyx/29muimJPQ==", "dev": true, + "license": "MIT", "dependencies": { - "@vueuse/core": "10.7.2", - "@vueuse/shared": "10.7.2", - "vue-demi": ">=0.14.6" + "@vueuse/core": "10.10.0", + "@vueuse/shared": "10.10.0", + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -1045,11 +1232,12 @@ } }, "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -1071,32 +1259,35 @@ } }, "node_modules/@vueuse/metadata": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.2.tgz", - "integrity": "sha512-kCWPb4J2KGrwLtn1eJwaJD742u1k5h6v/St5wFe8Quih90+k2a0JP8BS4Zp34XUuJqS2AxFYMb1wjUL8HfhWsQ==", + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.10.0.tgz", + "integrity": "sha512-UNAo2sTCAW5ge6OErPEHb5z7NEAg3XcO9Cj7OK45aZXfLLH1QkexDcZD77HBi5zvEiLOm1An+p/4b5K3Worpug==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared": { - "version": "10.7.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.2.tgz", - "integrity": "sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==", + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.10.0.tgz", + "integrity": "sha512-2aW33Ac0Uk0U+9yo3Ypg9s5KcR42cuehRWl7vnUHadQyFvCktseyxxEPBi1Eiq4D2yBGACOnqLZpx1eMc7g5Og==", "dev": true, + "license": "MIT", "dependencies": { - "vue-demi": ">=0.14.6" + "vue-demi": ">=0.14.7" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.6", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz", - "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", + "version": "0.14.8", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz", + "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "vue-demi-fix": "bin/vue-demi-fix.js", "vue-demi-switch": "bin/vue-demi-switch.js" @@ -1118,38 +1309,42 @@ } }, "node_modules/algoliasearch": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.20.0.tgz", - "integrity": "sha512-y+UHEjnOItoNy0bYO+WWmLWBlPwDjKHW6mNHrPi0NkuhpQOOEbrkwQH/wgKFDLh7qlKjzoKeiRtlpewDPDG23g==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", + "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", "dev": true, + "license": "MIT", "dependencies": { - "@algolia/cache-browser-local-storage": "4.20.0", - "@algolia/cache-common": "4.20.0", - "@algolia/cache-in-memory": "4.20.0", - "@algolia/client-account": "4.20.0", - "@algolia/client-analytics": "4.20.0", - "@algolia/client-common": "4.20.0", - "@algolia/client-personalization": "4.20.0", - "@algolia/client-search": "4.20.0", - "@algolia/logger-common": "4.20.0", - "@algolia/logger-console": "4.20.0", - "@algolia/requester-browser-xhr": "4.20.0", - "@algolia/requester-common": "4.20.0", - "@algolia/requester-node-http": "4.20.0", - "@algolia/transporter": "4.20.0" + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-account": "4.23.3", + "@algolia/client-analytics": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-personalization": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/recommend": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" } }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -1158,11 +1353,12 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -1170,42 +1366,44 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/focus-trap": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", "dev": true, + "license": "MIT", "dependencies": { "tabbable": "^6.2.0" } @@ -1216,6 +1414,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1224,16 +1423,21 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" } }, "node_modules/mark.js": { @@ -1248,6 +1452,13 @@ "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==", "dev": true }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1259,6 +1470,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1266,16 +1478,24 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/picocolors": { + "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "license": "ISC" }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "dev": true, "funding": [ { @@ -1291,30 +1511,40 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/preact": { - "version": "10.19.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.19.2.tgz", - "integrity": "sha512-UA9DX/OJwv6YwP9Vn7Ti/vF80XL+YA5H2l7BpCtUr3ya8LWHFzpiO5R+N7dN16ujpIxhekRFuOOF82bXX7K/lg==", + "version": "10.22.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz", + "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==", "dev": true, + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.6.tgz", - "integrity": "sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "1.0.5" }, @@ -1326,58 +1556,59 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.9.6", - "@rollup/rollup-android-arm64": "4.9.6", - "@rollup/rollup-darwin-arm64": "4.9.6", - "@rollup/rollup-darwin-x64": "4.9.6", - "@rollup/rollup-linux-arm-gnueabihf": "4.9.6", - "@rollup/rollup-linux-arm64-gnu": "4.9.6", - "@rollup/rollup-linux-arm64-musl": "4.9.6", - "@rollup/rollup-linux-riscv64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-gnu": "4.9.6", - "@rollup/rollup-linux-x64-musl": "4.9.6", - "@rollup/rollup-win32-arm64-msvc": "4.9.6", - "@rollup/rollup-win32-ia32-msvc": "4.9.6", - "@rollup/rollup-win32-x64-msvc": "4.9.6", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, "node_modules/search-insights": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.11.0.tgz", - "integrity": "sha512-Uin2J8Bpm3xaZi9Y8QibSys6uJOFZ+REMrf42v20AA3FUDUrshKkMEP6liJbMAHCm71wO6ls4mwAf7a3gFVxLw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", + "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", "dev": true, + "license": "MIT", "peer": true }, - "node_modules/shikiji": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/shikiji/-/shikiji-0.10.2.tgz", - "integrity": "sha512-wtZg3T0vtYV2PnqusWQs3mDaJBdCPWxFDrBM/SE5LfrX92gjUvfEMlc+vJnoKY6Z/S44OWaCRzNIsdBRWcTAiw==", + "node_modules/shiki": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.2.tgz", + "integrity": "sha512-X3hSm5GzzBd/BmPmGfkueOUADLyBoZo1ojYQXhd+NU2VJn458yt4duaS0rVzC+WtqftSV7mTVvDw+OB9AHi3Eg==", "dev": true, + "license": "MIT", "dependencies": { - "shikiji-core": "0.10.2" + "@shikijs/core": "1.6.2" } }, - "node_modules/shikiji-core": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/shikiji-core/-/shikiji-core-0.10.2.tgz", - "integrity": "sha512-9Of8HMlF96usXJHmCL3Gd0Fcf0EcyJUF9m8EoAKKd98mHXi0La2AZl1h6PegSFGtiYcBDK/fLuKbDa1l16r1fA==", - "dev": true - }, - "node_modules/shikiji-transformers": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/shikiji-transformers/-/shikiji-transformers-0.10.2.tgz", - "integrity": "sha512-7IVTwl1af205ywYEq5bOAYOTOFW4V1dVX1EablP0nWKErqZeD1o93VMytxmtJomqS+YwbB8doY8SE3MFMn0aPQ==", + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, - "dependencies": { - "shikiji": "0.10.2" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1386,17 +1617,19 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/vite": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", - "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", + "version": "5.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.32", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -1444,33 +1677,35 @@ } }, "node_modules/vitepress": { - "version": "1.0.0-rc.40", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.0.0-rc.40.tgz", - "integrity": "sha512-1x9PCrcsJwqhpccyTR93uD6jpiPDeRC98CBCAQLLBb44a3VSXYBPzhCahi+2kwAYylu49p0XhseMPVM4IVcWcw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.2.3.tgz", + "integrity": "sha512-GvEsrEeNLiDE1+fuwDAYJCYLNZDAna+EtnXlPajhv/MYeTjbNK6Bvyg6NoTdO1sbwuQJ0vuJR99bOlH53bo6lg==", "dev": true, + "license": "MIT", "dependencies": { - "@docsearch/css": "^3.5.2", - "@docsearch/js": "^3.5.2", - "@types/markdown-it": "^13.0.7", - "@vitejs/plugin-vue": "^5.0.3", - "@vue/devtools-api": "^6.5.1", - "@vueuse/core": "^10.7.2", - "@vueuse/integrations": "^10.7.2", + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.6.2", + "@shikijs/transformers": "^1.6.2", + "@types/markdown-it": "^14.1.1", + "@vitejs/plugin-vue": "^5.0.5", + "@vue/devtools-api": "^7.2.1", + "@vue/shared": "^3.4.27", + "@vueuse/core": "^10.10.0", + "@vueuse/integrations": "^10.10.0", "focus-trap": "^7.5.4", "mark.js": "8.11.1", "minisearch": "^6.3.0", - "shikiji": "^0.10.0", - "shikiji-core": "^0.10.0", - "shikiji-transformers": "^0.10.0", - "vite": "^5.0.12", - "vue": "^3.4.15" + "shiki": "^1.6.2", + "vite": "^5.2.12", + "vue": "^3.4.27" }, "bin": { "vitepress": "bin/vitepress.js" }, "peerDependencies": { - "markdown-it-mathjax3": "^4.3.2", - "postcss": "^8.4.33" + "markdown-it-mathjax3": "^4", + "postcss": "^8" }, "peerDependenciesMeta": { "markdown-it-mathjax3": { @@ -1482,16 +1717,17 @@ } }, "node_modules/vue": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.15.tgz", - "integrity": "sha512-jC0GH4KkWLWJOEQjOpkqU1bQsBwf4R1rsFtw5GQJbjHVKWDzO6P0nWWBTmjp1xSemAioDFj1jdaK1qa3DnMQoQ==", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.15", - "@vue/compiler-sfc": "3.4.15", - "@vue/runtime-dom": "3.4.15", - "@vue/server-renderer": "3.4.15", - "@vue/shared": "3.4.15" + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" }, "peerDependencies": { "typescript": "*" diff --git a/docs/package.json b/docs/package.json index b921756..2b8a9d8 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "devDependencies": { - "vitepress": "^1.0.0-rc.40" + "vitepress": "^1.2.3" }, "scripts": { "docs:dev": "vitepress dev", diff --git a/go.mod b/go.mod index 13ef75d..a9d4db6 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,16 @@ go 1.21 require ( github.com/IGLOU-EU/go-wildcard v1.0.3 - github.com/cespare/xxhash/v2 v2.2.0 + github.com/cespare/xxhash/v2 v2.3.0 github.com/google/uuid v1.6.0 github.com/pires/go-proxyproto v0.7.0 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.33.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - golang.org/x/sys v0.12.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index b531d8a..eb2285a 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/IGLOU-EU/go-wildcard v1.0.3 h1:r8T46+8/9V1STciXJomTWRpPEv4nGJATDbJkdU github.com/IGLOU-EU/go-wildcard v1.0.3/go.mod h1:/qeV4QLmydCbwH0UMQJmXDryrFKJknWi/jjO8IiuQfY= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -11,18 +13,24 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/pkg/infrared/handshake_response.go b/pkg/infrared/handshake_response.go index 48f42f1..effcb44 100644 --- a/pkg/infrared/handshake_response.go +++ b/pkg/infrared/handshake_response.go @@ -58,43 +58,50 @@ type HandshakeResponse struct { } func (r *HandshakeResponse) StatusResponse(protVer protocol.Version) (status.ResponseJSON, protocol.Packet) { + cfg := r.Config.StatusConfig + if cfg.ProtocolNumber < 0 { + return r.renderStatusPacket(protVer) + } + r.statusOnce.Do(func() { - cfg := r.Config.StatusConfig + protVer = protocol.Version(cfg.ProtocolNumber) + r.statusRespJSON, r.statusRespPk = r.renderStatusPacket(protVer) + }) - protNum := cfg.ProtocolNumber - if protNum < 0 { - protNum = int(protVer) - } + return r.statusRespJSON, r.statusRespPk +} - r.statusRespJSON = status.ResponseJSON{ - Version: status.VersionJSON{ - Name: cfg.VersionName, - Protocol: protNum, - }, - Players: status.PlayersJSON{ - Max: cfg.MaxPlayerCount, - Online: cfg.PlayerCount, - Sample: cfg.PlayerSamples.PlayerSampleJSON(), - }, - Favicon: parseServerIcon(cfg.Icon), - Description: parseJSONTextComponent(cfg.MOTD), - } +func (r *HandshakeResponse) renderStatusPacket(protVer protocol.Version) (status.ResponseJSON, protocol.Packet) { + cfg := r.Config.StatusConfig + respJSON := status.ResponseJSON{ + Version: status.VersionJSON{ + Name: cfg.VersionName, + Protocol: int(protVer), + }, + Players: status.PlayersJSON{ + Max: cfg.MaxPlayerCount, + Online: cfg.PlayerCount, + Sample: cfg.PlayerSamples.PlayerSampleJSON(), + }, + Favicon: parseServerIcon(cfg.Icon), + Description: parseJSONTextComponent(cfg.MOTD), + } - respBytes, err := json.Marshal(r.statusRespJSON) - if err != nil { - panic(err) - } + respBytes, err := json.Marshal(r.statusRespJSON) + if err != nil { + panic(err) + } - statusPk := status.ClientBoundResponse{ - JSONResponse: protocol.String(string(respBytes)), - } + statusPk := status.ClientBoundResponse{ + JSONResponse: protocol.String(string(respBytes)), + } - if err := statusPk.Marshal(&r.statusRespPk); err != nil { - panic(err) - } - }) + var respPk protocol.Packet + if err := statusPk.Marshal(&respPk); err != nil { + panic(err) + } - return r.statusRespJSON, r.statusRespPk + return respJSON, respPk } func (r *HandshakeResponse) LoginReponse() protocol.Packet { diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index 305eca1..a89864f 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -23,7 +23,7 @@ type Config struct { func NewConfig() Config { return Config{ - BindAddr: ":25565", + BindAddr: "localhost:25565", KeepAliveTimeout: 30 * time.Second, ServerConfigs: make([]ServerConfig, 0), FiltersConfig: NewFilterConfig(), diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index 25edc53..3248eb4 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -1,10 +1,8 @@ package infrared import ( - "crypto/rand" "encoding/json" "errors" - "io" "net" "strings" "sync" @@ -336,21 +334,31 @@ func (s *statusResponseCache) cacheStatusResponse( s.mu.Lock() defer s.mu.Unlock() - log.Info().Msg("new cache entry") - hash := xxhash.New() - if _, err := io.CopyN(hash, rand.Reader, 64); err != nil { - return err - } + hash.Write(statusResp.Description) + hash.WriteString(statusResp.Favicon) hashSum := hash.Sum64() s.statusHash[protVer] = hashSum + + if _, ok := s.statusResponseCache[hashSum]; ok { + log.Debug(). + Int32("protVer", int32(protVer)). + Msg("Linked existing cache entry") + + return nil + } + s.statusResponseCache[hashSum] = &statusCacheEntry{ expiresAt: time.Now().Add(s.ttl), responseJSON: statusResp, responsePk: respPk, } + log.Debug(). + Int32("protVer", int32(protVer)). + Msg("Created new cache entry") + return nil } @@ -369,8 +377,11 @@ func (s *statusResponseCache) prune() { for protVer, hash := range s.statusHash { for _, expiredHash := range expiredHashes { if hash == expiredHash { - log.Info().Msg("delete cache entry") delete(s.statusHash, protVer) + + log.Debug(). + Int32("protVer", int32(protVer)). + Msg("Deleted cache entry") } } } diff --git a/tools/cachespam/main.go b/tools/cachespam/main.go new file mode 100644 index 0000000..1ca05e6 --- /dev/null +++ b/tools/cachespam/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "io" + "log" + "net" + "os" + "runtime" + "sync" + + "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/haveachin/infrared/pkg/infrared/protocol/handshaking" +) + +var statusRequestPayload = []byte{0x00} + +func handshake(w io.Writer, protVer int32) { + var pk protocol.Packet + handshaking.ServerBoundHandshake{ + ProtocolVersion: protocol.VarInt(protVer), + ServerAddress: "localhost", + ServerPort: 25565, + NextState: handshaking.StateStatusServerBoundHandshake, + }.Marshal(&pk) + pk.WriteTo(w) +} + +func statusRequest(w io.Writer) { + var pk protocol.Packet + pk.Encode(0x00) + pk.WriteTo(w) +} + +func main() { + runtime.GOMAXPROCS(4) + + targetAddr := "localhost:25565" + + if len(os.Args) < 2 { + log.Println("No target address specified") + log.Printf("Defaulting to %s\n", targetAddr) + } else { + targetAddr = os.Args[1] + } + + conn, err := net.Dial("tcp", targetAddr) + if err != nil { + log.Fatal(err) + } + _ = conn.Close() + + wg := sync.WaitGroup{} + + for i := 0; i <= 766; i++ { + log.Printf("Protocol version %d requests sent\n", i) + + wg.Add(1) + go func(protVer int32) { + defer func() { + log.Printf("Done %d\n", protVer) + wg.Done() + }() + + c, err := net.Dial("tcp", targetAddr) + if err != nil { + return + } + + handshake(c, protVer) + statusRequest(c) + var pk protocol.Packet + _, _ = pk.ReadFrom(c) + _ = c.Close() + }(int32(i)) + } + + wg.Wait() +} From 202df92bf4c117aba4e25a7896f60eebcadaa650 Mon Sep 17 00:00:00 2001 From: haveachin Date: Wed, 19 Jun 2024 15:25:41 +0200 Subject: [PATCH 6/6] refactor: handshake response --- configs/config.yml | 35 +++++++++++++++- configs/proxy.yml | 28 +------------ docs/faq.md | 6 +++ docs/features/status-cache.md | 10 +++++ go.mod | 4 ++ go.sum | 10 +++++ pkg/infrared/handshake_response.go | 42 ++++++++++--------- pkg/infrared/infrared.go | 10 ++++- .../protocol/status/clientbound_response.go | 19 +++++++++ pkg/infrared/server.go | 38 ++++++++--------- 10 files changed, 134 insertions(+), 68 deletions(-) create mode 100644 docs/faq.md create mode 100644 docs/features/status-cache.md diff --git a/configs/config.yml b/configs/config.yml index 4f84158..64addc9 100644 --- a/configs/config.yml +++ b/configs/config.yml @@ -40,4 +40,37 @@ filters: # Windows Length is the time frame for the Request Limit. # - windowLength: 1s \ No newline at end of file + windowLength: 1s + +# Reponse that the player sees when there is no server for that domain. +# +notFoundResponse: + # The status response that should be displayed to the players + # + status: + + # The name of the version. + # This is displayed on the client side when the version is incompatible. + # + versionName: "Infrared 1.20.6" + + # The protocol version number determens if the version of the client is compatible with the server. + # A list of protocol versions can be found here: https://wiki.vg/Protocol_version_numbers + # Set this to -1 to mirror the client version back. + # + protocolNumber: -1 + maxPlayerCount: 0 + playerCount: 0 + playerSamples: [] + + # Server icon can be provided as base64 or path to a file. + # + icon: "" + + # MOTD can be just a string or a JSON Text Component. + # + motd: "Server not found" + + # Reason why the player was disconnected during login. + # + message: "Server not found" \ No newline at end of file diff --git a/configs/proxy.yml b/configs/proxy.yml index 1f40706..8e6e5e7 100644 --- a/configs/proxy.yml +++ b/configs/proxy.yml @@ -19,36 +19,14 @@ addresses: # times out during status or login request. # dialTimeoutResponse: - # The status response that should be displayed to the players - # status: - - # The name of the version. - # This is displayed on the client side when the version is incompatible. - # versionName: "Infrared 1.20.6" - - # The protocol version number determens if the version of the client is compatible with the server. - # A list of protocol versions can be found here: https://wiki.vg/Protocol_version_numbers - # Set this to -1 to mirror the client version back. - # protocolNumber: 766 maxPlayerCount: 20 playerCount: 0 - playerSamples: - - name: "" - uuid: "" - - # Server icon can be provided as base64 or path to a file. - # + playerSamples: [] icon: "" - - # MOTD can be just a string or a JSON Text Component. - # motd: "Unable to reach backend server" - - # Reason why the player was disconnected during login. - # message: "Unable to reach backend server" # Override Status is similar to the dial timeout response status @@ -59,8 +37,6 @@ overrideStatus: protocolNumber: 766 maxPlayerCount: 20 playerCount: 0 - playerSamples: - - name: "" - uuid: "" + playerSamples: [] icon: "" motd: "Unable to reach backend server" \ No newline at end of file diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..fb8a587 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,6 @@ +# FAQ + +## Will you add (Query)[https://wiki.vg/Query]? + +No, it is fundamentally not compatibale with reverse proxies. + diff --git a/docs/features/status-cache.md b/docs/features/status-cache.md new file mode 100644 index 0000000..7bb7ad1 --- /dev/null +++ b/docs/features/status-cache.md @@ -0,0 +1,10 @@ +# Status Cache + +The status cache aims to reduces the ping frequency and the general load on your backend servers and Infrared. +It is implemented by via a double hash map to reduce it's memory footprint. + +## Enable Status Caching + +```yaml + +``` \ No newline at end of file diff --git a/go.mod b/go.mod index a9d4db6..4766050 100644 --- a/go.mod +++ b/go.mod @@ -15,5 +15,9 @@ require ( require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect golang.org/x/sys v0.21.0 // indirect ) diff --git a/go.sum b/go.sum index eb2285a..da34e42 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,16 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= diff --git a/pkg/infrared/handshake_response.go b/pkg/infrared/handshake_response.go index effcb44..816aae9 100644 --- a/pkg/infrared/handshake_response.go +++ b/pkg/infrared/handshake_response.go @@ -41,6 +41,10 @@ type HandshakeStatusResponseConfig struct { MOTD string `yaml:"motd"` } +func (cfg HandshakeStatusResponseConfig) IsProtocolNumberDynamic() bool { + return cfg.ProtocolNumber == -1 +} + type HandshakeResponseConfig struct { StatusConfig HandshakeStatusResponseConfig `yaml:"status"` Message string `yaml:"message"` @@ -49,29 +53,36 @@ type HandshakeResponseConfig struct { type HandshakeResponse struct { Config HandshakeResponseConfig - statusOnce sync.Once - statusRespJSON status.ResponseJSON - statusRespPk protocol.Packet + statusOnce sync.Once + statusResp status.ClientBoundResponse + statusRespPk protocol.Packet loginOnce sync.Once loginRespPk protocol.Packet } -func (r *HandshakeResponse) StatusResponse(protVer protocol.Version) (status.ResponseJSON, protocol.Packet) { +func (r *HandshakeResponse) StatusResponse(protVer protocol.Version) protocol.Packet { cfg := r.Config.StatusConfig - if cfg.ProtocolNumber < 0 { - return r.renderStatusPacket(protVer) - } r.statusOnce.Do(func() { protVer = protocol.Version(cfg.ProtocolNumber) - r.statusRespJSON, r.statusRespPk = r.renderStatusPacket(protVer) + r.renderStatusPacket(protVer) }) - return r.statusRespJSON, r.statusRespPk + if cfg.IsProtocolNumberDynamic() { + if err := r.statusResp.SetVersionProtocol(protVer); err != nil { + panic(err) + } + + if err := r.statusResp.Marshal(&r.statusRespPk); err != nil { + panic(err) + } + } + + return r.statusRespPk } -func (r *HandshakeResponse) renderStatusPacket(protVer protocol.Version) (status.ResponseJSON, protocol.Packet) { +func (r *HandshakeResponse) renderStatusPacket(protVer protocol.Version) { cfg := r.Config.StatusConfig respJSON := status.ResponseJSON{ Version: status.VersionJSON{ @@ -87,21 +98,14 @@ func (r *HandshakeResponse) renderStatusPacket(protVer protocol.Version) (status Description: parseJSONTextComponent(cfg.MOTD), } - respBytes, err := json.Marshal(r.statusRespJSON) + respBytes, err := json.Marshal(respJSON) if err != nil { panic(err) } - statusPk := status.ClientBoundResponse{ + r.statusResp = status.ClientBoundResponse{ JSONResponse: protocol.String(string(respBytes)), } - - var respPk protocol.Packet - if err := statusPk.Marshal(&respPk); err != nil { - panic(err) - } - - return respJSON, respPk } func (r *HandshakeResponse) LoginReponse() protocol.Packet { diff --git a/pkg/infrared/infrared.go b/pkg/infrared/infrared.go index a89864f..065c71e 100644 --- a/pkg/infrared/infrared.go +++ b/pkg/infrared/infrared.go @@ -249,6 +249,8 @@ func (ir *Infrared) handleConn(c *clientConn) error { switch { case errors.Is(err, ErrServerNotReachable) && c.handshake.IsLoginRequest(): return ir.handleLoginDisconnect(c, resp) + case errors.Is(err, ErrServerNotFound): + return ir.handleServerNotFound(c) default: return err } @@ -262,7 +264,7 @@ func (ir *Infrared) handleConn(c *clientConn) error { } func handleStatus(c *clientConn, resp ServerResponse) error { - if err := c.WritePacket(resp.StatusResponse); err != nil { + if err := c.WritePacket(resp.Packet); err != nil { return err } @@ -279,7 +281,11 @@ func handleStatus(c *clientConn, resp ServerResponse) error { } func (ir *Infrared) handleLoginDisconnect(c *clientConn, resp ServerResponse) error { - return c.WritePacket(resp.StatusResponse) + return c.WritePacket(resp.Packet) +} + +func (ir *Infrared) handleServerNotFound(c *clientConn) error { + return nil } func (ir *Infrared) handleLogin(c *clientConn, resp ServerResponse) error { diff --git a/pkg/infrared/protocol/status/clientbound_response.go b/pkg/infrared/protocol/status/clientbound_response.go index 874a73f..f32d360 100644 --- a/pkg/infrared/protocol/status/clientbound_response.go +++ b/pkg/infrared/protocol/status/clientbound_response.go @@ -2,8 +2,11 @@ package status import ( "encoding/json" + "errors" + "fmt" "github.com/haveachin/infrared/pkg/infrared/protocol" + "github.com/tidwall/sjson" ) const ( @@ -31,6 +34,22 @@ func (pk *ClientBoundResponse) Unmarshal(packet protocol.Packet) error { ) } +func (pk *ClientBoundResponse) SetVersionProtocol(protVer protocol.Version) error { + jsonStr := string(pk.JSONResponse) + if jsonStr == "" { + return errors.New("no json in packet") + } + + version := int32(protVer) + jsonStr, err := sjson.Set(jsonStr, "version.protocol", version) + if err != nil { + return fmt.Errorf("set version.protocol: %w", err) + } + pk.JSONResponse = protocol.String(jsonStr) + + return nil +} + type ResponseJSON struct { Version VersionJSON `json:"version"` Players PlayersJSON `json:"players"` diff --git a/pkg/infrared/server.go b/pkg/infrared/server.go index 3248eb4..d2e62dd 100644 --- a/pkg/infrared/server.go +++ b/pkg/infrared/server.go @@ -90,7 +90,7 @@ type ServerRequest struct { type ServerResponse struct { ServerConn *ServerConn - StatusResponse protocol.Packet + Packet protocol.Packet SendProxyProtocol bool } @@ -136,7 +136,7 @@ func NewServerGateway(servers []*Server, responder ServerRequestResponder) (*Ser }, nil } -func (sg *ServerGateway) findServer(domain ServerDomain) *Server { +func (sg ServerGateway) findServer(domain ServerDomain) *Server { dm := string(domain) dm = strings.ToLower(dm) for d, srv := range sg.servers { @@ -147,7 +147,7 @@ func (sg *ServerGateway) findServer(domain ServerDomain) *Server { return nil } -func (sg *ServerGateway) RequestServer(req ServerRequest) (ServerResponse, error) { +func (sg ServerGateway) RequestServer(req ServerRequest) (ServerResponse, error) { srv := sg.findServer(req.Domain) if srv == nil { return ServerResponse{}, ErrServerNotFound @@ -176,7 +176,7 @@ func (r DialServerResponder) respondeToLoginRequest(_ ServerRequest, srv *Server rc, err := srv.Dial() if err != nil { return ServerResponse{ - StatusResponse: srv.dialTimeoutResp.LoginReponse(), + Packet: srv.dialTimeoutResp.LoginReponse(), }, ErrServerNotReachable } @@ -200,23 +200,22 @@ func (r DialServerResponder) respondeToStatusRequest(req ServerRequest, srv *Ser r.respProvs[srv] = respProv } - _, pk, err := respProv.StatusResponse(req.ClientAddr, req.ProtocolVersion, req.ReadPackets) + pk, err := respProv.StatusResponse(req.ClientAddr, req.ProtocolVersion, req.ReadPackets) if err != nil { return ServerResponse{}, err } return ServerResponse{ - StatusResponse: pk, + Packet: pk, }, nil } type StatusResponseProvider interface { - StatusResponse(net.Addr, protocol.Version, [2]protocol.Packet) (status.ResponseJSON, protocol.Packet, error) + StatusResponse(net.Addr, protocol.Version, [2]protocol.Packet) (protocol.Packet, error) } type statusCacheEntry struct { expiresAt time.Time - responseJSON status.ResponseJSON responsePk protocol.Packet } @@ -233,12 +232,12 @@ func (s *statusResponseProvider) StatusResponse( cliAddr net.Addr, protVer protocol.Version, readPks [2]protocol.Packet, -) (status.ResponseJSON, protocol.Packet, error) { +) (protocol.Packet, error) { cacheRespone := false if s.cache.ttl > 0 { - statusResp, statusPk, err := s.cache.statusResponse(protVer) + statusPk, err := s.cache.statusResponse(protVer) if err == nil { - return statusResp, statusPk, nil + return statusPk, nil } cacheRespone = true } @@ -247,20 +246,20 @@ func (s *statusResponseProvider) StatusResponse( if err != nil { switch { case errors.Is(err, ErrServerNotReachable): - respJSON, respPk := s.server.dialTimeoutResp.StatusResponse(protVer) - return respJSON, respPk, nil + respPk := s.server.dialTimeoutResp.StatusResponse(protVer) + return respPk, nil default: - return status.ResponseJSON{}, protocol.Packet{}, err + return protocol.Packet{}, err } } if cacheRespone { if err := s.cache.cacheStatusResponse(protVer, statusResp, statusPk); err != nil { - return status.ResponseJSON{}, protocol.Packet{}, err + return protocol.Packet{}, err } } - return statusResp, statusPk, nil + return statusPk, nil } func (s *statusResponseProvider) requestNewStatusResponseJSON( @@ -310,7 +309,7 @@ type statusResponseCache struct { func (s *statusResponseCache) statusResponse( protVer protocol.Version, -) (status.ResponseJSON, protocol.Packet, error) { +) (protocol.Packet, error) { // Prunes all expired status reponses s.prune() @@ -320,10 +319,10 @@ func (s *statusResponseCache) statusResponse( hash, okHash := s.statusHash[protVer] entry, okCache := s.statusResponseCache[hash] if !okHash || !okCache { - return status.ResponseJSON{}, protocol.Packet{}, errors.New("not in cache") + return protocol.Packet{}, errors.New("not in cache") } - return entry.responseJSON, entry.responsePk, nil + return entry.responsePk, nil } func (s *statusResponseCache) cacheStatusResponse( @@ -351,7 +350,6 @@ func (s *statusResponseCache) cacheStatusResponse( s.statusResponseCache[hashSum] = &statusCacheEntry{ expiresAt: time.Now().Add(s.ttl), - responseJSON: statusResp, responsePk: respPk, }