diff --git a/.github/update.log b/.github/update.log index 06ecfeda9d..6b101c0009 100644 --- a/.github/update.log +++ b/.github/update.log @@ -974,3 +974,4 @@ Update On Sat Apr 12 20:33:39 CEST 2025 Update On Sun Apr 13 20:36:07 CEST 2025 Update On Mon Apr 14 20:39:52 CEST 2025 Update On Tue Apr 15 20:36:26 CEST 2025 +Update On Wed Apr 16 20:40:48 CEST 2025 diff --git a/clash-meta/adapter/outbound/vless.go b/clash-meta/adapter/outbound/vless.go index d4444cb0c5..cfb39e99d6 100644 --- a/clash-meta/adapter/outbound/vless.go +++ b/clash-meta/adapter/outbound/vless.go @@ -600,10 +600,13 @@ func NewVless(option VlessOption) (*Vless, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, - }) + }, v.option.Fingerprint) + if err != nil { + return nil, err + } if option.ServerName == "" { host, _, _ := net.SplitHostPort(v.addr) tlsConfig.ServerName = host diff --git a/clash-meta/adapter/outbound/vmess.go b/clash-meta/adapter/outbound/vmess.go index 285fdc8e47..35aacb7860 100644 --- a/clash-meta/adapter/outbound/vmess.go +++ b/clash-meta/adapter/outbound/vmess.go @@ -511,10 +511,13 @@ func NewVmess(option VmessOption) (*Vmess, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, - }) + }, v.option.Fingerprint) + if err != nil { + return nil, err + } if option.ServerName == "" { host, _, _ := net.SplitHostPort(v.addr) tlsConfig.ServerName = host diff --git a/clash-meta/common/net/tls.go b/clash-meta/common/net/tls.go index b28655031e..77c0d7ca39 100644 --- a/clash-meta/common/net/tls.go +++ b/clash-meta/common/net/tls.go @@ -3,8 +3,10 @@ package net import ( "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/hex" "encoding/pem" "fmt" "math/big" @@ -16,7 +18,11 @@ type Path interface { func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) { if certificate == "" && privateKey == "" { - return newRandomTLSKeyPair() + var err error + certificate, privateKey, _, err = NewRandomTLSKeyPair() + if err != nil { + return tls.Certificate{}, err + } } cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) if painTextErr == nil { @@ -32,10 +38,10 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro return cert, nil } -func newRandomTLSKeyPair() (tls.Certificate, error) { +func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return tls.Certificate{}, err + return } template := x509.Certificate{SerialNumber: big.NewInt(1)} certDER, err := x509.CreateCertificate( @@ -45,14 +51,15 @@ func newRandomTLSKeyPair() (tls.Certificate, error) { &key.PublicKey, key) if err != nil { - return tls.Certificate{}, err + return } - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - - tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + cert, err := x509.ParseCertificate(certDER) if err != nil { - return tls.Certificate{}, err + return } - return tlsCert, nil + hash := sha256.Sum256(cert.Raw) + fingerprint = hex.EncodeToString(hash[:]) + privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})) + certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})) + return } diff --git a/clash-meta/common/observable/observable.go b/clash-meta/common/observable/observable.go index 62b2e1537e..1e45cc66e1 100644 --- a/clash-meta/common/observable/observable.go +++ b/clash-meta/common/observable/observable.go @@ -10,6 +10,7 @@ type Observable[T any] struct { listener map[Subscription[T]]*Subscriber[T] mux sync.Mutex done bool + stopCh chan struct{} } func (o *Observable[T]) process() { @@ -31,6 +32,7 @@ func (o *Observable[T]) close() { for _, sub := range o.listener { sub.Close() } + close(o.stopCh) } func (o *Observable[T]) Subscribe() (Subscription[T], error) { @@ -59,6 +61,7 @@ func NewObservable[T any](iter Iterable[T]) *Observable[T] { observable := &Observable[T]{ iterable: iter, listener: map[Subscription[T]]*Subscriber[T]{}, + stopCh: make(chan struct{}), } go observable.process() return observable diff --git a/clash-meta/common/observable/observable_test.go b/clash-meta/common/observable/observable_test.go index d263cb9403..6be7f3aacc 100644 --- a/clash-meta/common/observable/observable_test.go +++ b/clash-meta/common/observable/observable_test.go @@ -70,9 +70,11 @@ func TestObservable_SubscribeClosedSource(t *testing.T) { src := NewObservable[int](iter) data, _ := src.Subscribe() <-data - - _, closed := src.Subscribe() - assert.NotNil(t, closed) + select { + case <-src.stopCh: + case <-time.After(time.Second): + assert.Fail(t, "timeout not stop") + } } func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { diff --git a/clash-meta/common/singledo/singledo_test.go b/clash-meta/common/singledo/singledo_test.go index 2f92f0ae0f..f27650fb35 100644 --- a/clash-meta/common/singledo/singledo_test.go +++ b/clash-meta/common/singledo/singledo_test.go @@ -11,12 +11,13 @@ import ( ) func TestBasic(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 shardCount := atomic.NewInt32(0) call := func() (int, error) { foo++ - time.Sleep(time.Millisecond * 5) + time.Sleep(time.Millisecond * 20) return 0, nil } @@ -39,7 +40,8 @@ func TestBasic(t *testing.T) { } func TestTimer(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 callM := func() (int, error) { foo++ @@ -47,7 +49,7 @@ func TestTimer(t *testing.T) { } _, _, _ = single.Do(callM) - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) _, _, shard := single.Do(callM) assert.Equal(t, 1, foo) @@ -55,7 +57,8 @@ func TestTimer(t *testing.T) { } func TestReset(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 callM := func() (int, error) { foo++ diff --git a/clash-meta/component/http/http.go b/clash-meta/component/http/http.go index 63ea5be7bc..a2c44d85c3 100644 --- a/clash-meta/component/http/http.go +++ b/clash-meta/component/http/http.go @@ -69,7 +69,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - if conn, err := inner.HandleTcp(address, specialProxy); err == nil { + if conn, err := inner.HandleTcp(inner.GetTunnel(), address, specialProxy); err == nil { return conn, nil } else { return dialer.DialContext(ctx, network, address) diff --git a/clash-meta/go.mod b/clash-meta/go.mod index 2a64051961..f40e0b89d8 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -20,7 +20,7 @@ require ( github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab github.com/metacubex/bart v0.19.0 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 - github.com/metacubex/chacha v0.1.1 + github.com/metacubex/chacha v0.1.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 github.com/metacubex/randv2 v0.2.0 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 8c2810266b..c7e852b004 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= -github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c= -github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= +github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc= +github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI= diff --git a/clash-meta/listener/http/server.go b/clash-meta/listener/http/server.go index e32b55dde3..3c1eacdd51 100644 --- a/clash-meta/listener/http/server.go +++ b/clash-meta/listener/http/server.go @@ -78,7 +78,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/listener/inbound/common_test.go b/clash-meta/listener/inbound/common_test.go new file mode 100644 index 0000000000..29d0456687 --- /dev/null +++ b/clash-meta/listener/inbound/common_test.go @@ -0,0 +1,232 @@ +package inbound_test + +import ( + "context" + "crypto/rand" + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "sync" + "testing" + "time" + + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/generater" + C "github.com/metacubex/mihomo/constant" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/stretchr/testify/assert" +) + +var httpPath = "/inbound_test" +var httpData = make([]byte, 10240) +var remoteAddr = netip.MustParseAddr("1.2.3.4") +var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair() +var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey)) +var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}} +var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "") +var realityPrivateKey, realityPublickey string +var realityDest = "itunes.apple.com" +var realityShortid = "10f897e26c4b9478" + +func init() { + rand.Read(httpData) + privateKey, err := generater.GeneratePrivateKey() + if err != nil { + panic(err) + } + publicKey := privateKey.PublicKey() + realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:]) + realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:]) +} + +type TestTunnel struct { + HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata) + HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata) + NatTableFn func() C.NatTable + CloseFn func() error + DoTestFn func(t *testing.T, proxy C.ProxyAdapter) +} + +func (tt *TestTunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { + tt.HandleTCPConnFn(conn, metadata) +} + +func (tt *TestTunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) { + tt.HandleUDPPacketFn(packet, metadata) +} + +func (tt *TestTunnel) NatTable() C.NatTable { + return tt.NatTableFn() +} + +func (tt *TestTunnel) Close() error { + return tt.CloseFn() +} + +func (tt *TestTunnel) DoTest(t *testing.T, proxy C.ProxyAdapter) { + tt.DoTestFn(t, proxy) +} + +type TestTunnelListener struct { + ch chan net.Conn + ctx context.Context + cancel context.CancelFunc + addr net.Addr +} + +func (t *TestTunnelListener) Accept() (net.Conn, error) { + select { + case conn, ok := <-t.ch: + if !ok { + return nil, net.ErrClosed + } + return conn, nil + case <-t.ctx.Done(): + return nil, t.ctx.Err() + } +} + +func (t *TestTunnelListener) Close() error { + t.cancel() + return nil +} + +func (t *TestTunnelListener) Addr() net.Addr { + return t.addr +} + +type WaitCloseConn struct { + net.Conn + ch chan struct{} + once sync.Once +} + +func (c *WaitCloseConn) Close() error { + err := c.Conn.Close() + c.once.Do(func() { + close(c.ch) + }) + return err +} + +var _ C.Tunnel = (*TestTunnel)(nil) +var _ net.Listener = (*TestTunnelListener)(nil) + +func NewHttpTestTunnel() *TestTunnel { + ctx, cancel := context.WithCancel(context.Background()) + ln := &TestTunnelListener{ch: make(chan net.Conn), ctx: ctx, cancel: cancel, addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(remoteAddr, 0))} + + r := chi.NewRouter() + r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) { + render.Data(w, r, httpData) + }) + go http.Serve(ln, r) + testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil) + assert.NoError(t, err) + req = req.WithContext(ctx) + + var dstPort uint16 = 80 + if proto == "https" { + dstPort = 443 + } + metadata := &C.Metadata{ + NetWork: C.TCP, + DstIP: remoteAddr, + DstPort: dstPort, + } + instance, err := proxy.DialContext(ctx, metadata) + assert.NoError(t, err) + defer instance.Close() + + transport := &http.Transport{ + DialContext: func(context.Context, string, string) (net.Conn, error) { + return instance, nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + // for our self-signed cert + TLSClientConfig: tlsClientConfig.Clone(), + // open http2 + ForceAttemptHTTP2: true, + } + + client := http.Client{ + Timeout: 30 * time.Second, + Transport: transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + defer client.CloseIdleConnections() + + resp, err := client.Do(req) + assert.NoError(t, err) + + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + data, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.Equal(t, httpData, data) + } + tunnel := &TestTunnel{ + HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) { + defer conn.Close() + if metadata.DstIP != remoteAddr && metadata.Host != realityDest { + return // not match, just return + } + c := &WaitCloseConn{ + Conn: conn, + ch: make(chan struct{}), + } + if metadata.DstPort == 443 { + tlsConn := tls.Server(c, tlsConfig.Clone()) + if metadata.Host == realityDest { // ignore the tls handshake error for realityDest + ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout) + defer cancel() + if err := tlsConn.HandshakeContext(ctx); err != nil { + return + } + } + ln.ch <- tlsConn + } else { + ln.ch <- c + } + <-c.ch + }, + CloseFn: ln.Close, + DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) { + wg := sync.WaitGroup{} + num := 50 + for i := 0; i < num; i++ { + wg.Add(1) + go func() { + testFn(t, proxy, "https") + defer wg.Done() + }() + } + for i := 0; i < num; i++ { + wg.Add(1) + go func() { + testFn(t, proxy, "http") + defer wg.Done() + }() + } + wg.Wait() + }, + } + return tunnel +} diff --git a/clash-meta/listener/inbound/shadowsocks_test.go b/clash-meta/listener/inbound/shadowsocks_test.go new file mode 100644 index 0000000000..044898a346 --- /dev/null +++ b/clash-meta/listener/inbound/shadowsocks_test.go @@ -0,0 +1,85 @@ +package inbound_test + +import ( + "crypto/rand" + "encoding/base64" + "net/netip" + "strings" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/listener/inbound" + + shadowsocks "github.com/metacubex/sing-shadowsocks" + "github.com/metacubex/sing-shadowsocks/shadowaead" + "github.com/metacubex/sing-shadowsocks/shadowaead_2022" + "github.com/metacubex/sing-shadowsocks/shadowstream" + "github.com/stretchr/testify/assert" +) + +var shadowsocksCipherList = []string{shadowsocks.MethodNone} +var shadowsocksPassword32 string +var shadowsocksPassword16 string + +func init() { + shadowsocksCipherList = append(shadowsocksCipherList, shadowaead.List...) + shadowsocksCipherList = append(shadowsocksCipherList, shadowaead_2022.List...) + shadowsocksCipherList = append(shadowsocksCipherList, shadowstream.List...) + passwordBytes := make([]byte, 32) + rand.Read(passwordBytes) + shadowsocksPassword32 = base64.StdEncoding.EncodeToString(passwordBytes) + shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16]) +} + +func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { + for _, cipher := range shadowsocksCipherList { + t.Run(cipher, func(t *testing.T) { + t.Parallel() + inboundOptions.Cipher = cipher + outboundOptions.Cipher = cipher + testInboundShadowSocks0(t, inboundOptions, outboundOptions) + }) + } +} + +func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { + password := shadowsocksPassword32 + if strings.Contains(inboundOptions.Cipher, "-128-") { + password = shadowsocksPassword16 + } + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "shadowsocks_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Password = password + in, err := inbound.NewShadowSocks(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "shadowsocks_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.Password = password + + out, err := outbound.NewShadowSocks(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundShadowSocks_Basic(t *testing.T) { + inboundOptions := inbound.ShadowSocksOption{} + outboundOptions := outbound.ShadowSocksOption{} + testInboundShadowSocks(t, inboundOptions, outboundOptions) +} diff --git a/clash-meta/listener/inbound/trojan_test.go b/clash-meta/listener/inbound/trojan_test.go new file mode 100644 index 0000000000..ad35b88198 --- /dev/null +++ b/clash-meta/listener/inbound/trojan_test.go @@ -0,0 +1,210 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "trojan_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.TrojanUser{ + {Username: "test", Password: userUUID}, + } + in, err := inbound.NewTrojan(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "trojan_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.Password = userUUID + + out, err := outbound.NewTrojan(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundTrojan_Tls(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss1(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss2(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Grpc1(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Grpc2(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Reality(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.TrojanOption{ + SNI: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + SNI: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Tls_TrojanSS(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + SSOption: inbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + SSOpts: outbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss_TrojanSS(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + SSOption: inbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + WsPath: "/ws", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + SSOpts: outbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} diff --git a/clash-meta/listener/inbound/vless_test.go b/clash-meta/listener/inbound/vless_test.go new file mode 100644 index 0000000000..ed7a507347 --- /dev/null +++ b/clash-meta/listener/inbound/vless_test.go @@ -0,0 +1,186 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outboundOptions outbound.VlessOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "vless_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.VlessUser{ + {Username: "test", UUID: userUUID, Flow: "xtls-rprx-vision"}, + } + in, err := inbound.NewVless(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "vless_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.UUID = userUUID + + out, err := outbound.NewVless(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundVless_Tls(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Wss1(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Wss2(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Grpc1(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} + +func TestInboundVless_Grpc2(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} + +func TestInboundVless_Reality(t *testing.T) { + inboundOptions := inbound.VlessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.VlessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.VlessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} diff --git a/clash-meta/listener/inbound/vmess_test.go b/clash-meta/listener/inbound/vmess_test.go new file mode 100644 index 0000000000..4fd63c411a --- /dev/null +++ b/clash-meta/listener/inbound/vmess_test.go @@ -0,0 +1,248 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "vmess_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.VmessUser{ + {Username: "test", UUID: userUUID, AlterID: 0}, + } + in, err := inbound.NewVmess(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "vmess_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.UUID = userUUID + outboundOptions.AlterID = 0 + outboundOptions.Cipher = "auto" + + out, err := outbound.NewVmess(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundVMess_Basic(t *testing.T) { + inboundOptions := inbound.VmessOption{} + outboundOptions := outbound.VmessOption{} + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Tls(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_ed1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws?ed=2048", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_ed2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + MaxEarlyData: 2048, + EarlyDataHeaderName: "Sec-WebSocket-Protocol", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_Upgrade1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + V2rayHttpUpgrade: true, + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_Upgrade2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + V2rayHttpUpgrade: true, + V2rayHttpUpgradeFastOpen: true, + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Wss1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Wss2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Grpc1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Grpc2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Reality(t *testing.T) { + inboundOptions := inbound.VmessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.VmessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.VmessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} diff --git a/clash-meta/listener/inner/tcp.go b/clash-meta/listener/inner/tcp.go index ee34ada661..95753fe9c4 100644 --- a/clash-meta/listener/inner/tcp.go +++ b/clash-meta/listener/inner/tcp.go @@ -3,8 +3,6 @@ package inner import ( "errors" "net" - "net/netip" - "strconv" N "github.com/metacubex/mihomo/common/net" C "github.com/metacubex/mihomo/constant" @@ -16,9 +14,13 @@ func New(t C.Tunnel) { tunnel = t } -func HandleTcp(address string, proxy string) (conn net.Conn, err error) { +func GetTunnel() C.Tunnel { + return tunnel +} + +func HandleTcp(tunnel C.Tunnel, address string, proxy string) (conn net.Conn, err error) { if tunnel == nil { - return nil, errors.New("tcp uninitialized") + return nil, errors.New("tunnel uninitialized") } // executor Parsed conn1, conn2 := N.Pipe() @@ -31,15 +33,8 @@ func HandleTcp(address string, proxy string) (conn net.Conn, err error) { if proxy != "" { metadata.SpecialProxy = proxy } - if h, port, err := net.SplitHostPort(address); err == nil { - if port, err := strconv.ParseUint(port, 10, 16); err == nil { - metadata.DstPort = uint16(port) - } - if ip, err := netip.ParseAddr(h); err == nil { - metadata.DstIP = ip - } else { - metadata.Host = h - } + if err = metadata.SetRemoteAddress(address); err != nil { + return nil, err } go tunnel.HandleTCPConn(conn2, metadata) diff --git a/clash-meta/listener/mixed/mixed.go b/clash-meta/listener/mixed/mixed.go index 6785b7fe5b..0ffdb02a3d 100644 --- a/clash-meta/listener/mixed/mixed.go +++ b/clash-meta/listener/mixed/mixed.go @@ -73,7 +73,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/listener/reality/reality.go b/clash-meta/listener/reality/reality.go index 222b036c67..16ccc01c10 100644 --- a/clash-meta/listener/reality/reality.go +++ b/clash-meta/listener/reality/reality.go @@ -9,6 +9,7 @@ import ( "net" "time" + C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/ntp" @@ -27,7 +28,7 @@ type Config struct { Proxy string } -func (c Config) Build() (*Builder, error) { +func (c Config) Build(tunnel C.Tunnel) (*Builder, error) { realityConfig := &utls.RealityConfig{} realityConfig.SessionTicketsDisabled = true realityConfig.Type = "tcp" @@ -67,7 +68,7 @@ func (c Config) Build() (*Builder, error) { } realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { - return inner.HandleTcp(address, c.Proxy) + return inner.HandleTcp(tunnel, address, c.Proxy) } return &Builder{realityConfig}, nil diff --git a/clash-meta/listener/sing_vless/server.go b/clash-meta/listener/sing_vless/server.go index 0614e4e622..137c8beb95 100644 --- a/clash-meta/listener/sing_vless/server.go +++ b/clash-meta/listener/sing_vless/server.go @@ -106,7 +106,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/listener/sing_vmess/server.go b/clash-meta/listener/sing_vmess/server.go index dd8f32af94..dc80e8cb8f 100644 --- a/clash-meta/listener/sing_vmess/server.go +++ b/clash-meta/listener/sing_vmess/server.go @@ -90,7 +90,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/listener/socks/tcp.go b/clash-meta/listener/socks/tcp.go index 5515360772..9e42a62505 100644 --- a/clash-meta/listener/socks/tcp.go +++ b/clash-meta/listener/socks/tcp.go @@ -72,7 +72,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/listener/trojan/server.go b/clash-meta/listener/trojan/server.go index 3141ab0b91..299575a562 100644 --- a/clash-meta/listener/trojan/server.go +++ b/clash-meta/listener/trojan/server.go @@ -84,7 +84,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/clash-meta/transport/shadowsocks/shadowstream/packet.go b/clash-meta/transport/shadowsocks/shadowstream/packet.go index 39d09a70e5..f8cd7e616f 100644 --- a/clash-meta/transport/shadowsocks/shadowstream/packet.go +++ b/clash-meta/transport/shadowsocks/shadowstream/packet.go @@ -29,6 +29,18 @@ func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { return dst[:len(iv)+len(plaintext)], nil } +// UnpackInplace decrypts pkt using stream cipher s. +// Returns a slice of pkt containing decrypted plaintext. +// Note: The data in the input dst will be changed +func UnpackInplace(pkt []byte, s Cipher) ([]byte, error) { + if len(pkt) < s.IVSize() { + return nil, ErrShortPacket + } + iv, dst := pkt[:s.IVSize()], pkt[s.IVSize():] + s.Decrypter(iv).XORKeyStream(dst, dst) + return dst, nil +} + // Unpack decrypts pkt using stream cipher s. // Returns a slice of dst containing decrypted plaintext. func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { @@ -71,7 +83,7 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return n, addr, err } - bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) + bb, err := UnpackInplace(b[:n], c.Cipher) if err != nil { return n, addr, err } @@ -84,7 +96,7 @@ func (c *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err if err != nil { return } - data, err = Unpack(data[c.IVSize():], data, c) + data, err = UnpackInplace(data, c.Cipher) if err != nil { if put != nil { put() diff --git a/clash-meta/transport/vless/vision/conn.go b/clash-meta/transport/vless/vision/conn.go index 69ccfba0c3..556626594c 100644 --- a/clash-meta/transport/vless/vision/conn.go +++ b/clash-meta/transport/vless/vision/conn.go @@ -5,6 +5,7 @@ import ( "crypto/subtle" gotls "crypto/tls" "encoding/binary" + "errors" "fmt" "io" "net" @@ -117,9 +118,11 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { case commandPaddingDirect: needReturn := false if vc.input != nil { - _, err := buffer.ReadFrom(vc.input) + _, err := buffer.ReadOnceFrom(vc.input) if err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } } if vc.input.Len() == 0 { needReturn = true @@ -129,9 +132,11 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } } if vc.rawInput != nil { - _, err := buffer.ReadFrom(vc.rawInput) + _, err := buffer.ReadOnceFrom(vc.rawInput) if err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } } needReturn = true if vc.rawInput.Len() == 0 { diff --git a/clash-meta/transport/vmess/websocket.go b/clash-meta/transport/vmess/websocket.go index 772c6fefb4..8ada88ecc1 100644 --- a/clash-meta/transport/vmess/websocket.go +++ b/clash-meta/transport/vmess/websocket.go @@ -467,7 +467,7 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, } } - conn = newWebsocketConn(conn, ws.StateClientSide) + conn = newWebsocketConn(bufferedConn, ws.StateClientSide) // websocketConn can't correct handle ReadDeadline // so call N.NewDeadlineConn to add a safe wrapper return N.NewDeadlineConn(conn), nil @@ -555,7 +555,7 @@ func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Co w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key"))) } w.WriteHeader(http.StatusSwitchingProtocols) - if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher { + if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher && writeHeaderShouldFlush { err = flusher.FlushError() if err != nil { return nil, fmt.Errorf("flush response: %w", err) diff --git a/clash-meta/transport/vmess/websocket_go120.go b/clash-meta/transport/vmess/websocket_go120.go new file mode 100644 index 0000000000..0b9ff5e3d6 --- /dev/null +++ b/clash-meta/transport/vmess/websocket_go120.go @@ -0,0 +1,7 @@ +//go:build !go1.21 + +package vmess + +// Golang1.20's net.http Flush will mistakenly call w.WriteHeader(StatusOK) internally after w.WriteHeader(http.StatusSwitchingProtocols) +// https://github.com/golang/go/issues/59564 +const writeHeaderShouldFlush = false diff --git a/clash-meta/transport/vmess/websocket_go121.go b/clash-meta/transport/vmess/websocket_go121.go new file mode 100644 index 0000000000..9419cd50a3 --- /dev/null +++ b/clash-meta/transport/vmess/websocket_go121.go @@ -0,0 +1,5 @@ +//go:build go1.21 + +package vmess + +const writeHeaderShouldFlush = true diff --git a/clash-nyanpasu/backend/Cargo.lock b/clash-nyanpasu/backend/Cargo.lock index b1df500363..fc43d47bea 100644 --- a/clash-nyanpasu/backend/Cargo.lock +++ b/clash-nyanpasu/backend/Cargo.lock @@ -1681,7 +1681,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -2831,7 +2831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -4899,7 +4899,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -7272,7 +7272,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7868,7 +7868,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7881,7 +7881,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.9.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9501,7 +9501,7 @@ dependencies = [ "getrandom 0.3.2", "once_cell", "rustix 1.0.3", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -9791,9 +9791,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.1" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", @@ -11125,7 +11125,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] diff --git a/clash-nyanpasu/frontend/nyanpasu/package.json b/clash-nyanpasu/frontend/nyanpasu/package.json index c605613843..5a7d1c3b21 100644 --- a/clash-nyanpasu/frontend/nyanpasu/package.json +++ b/clash-nyanpasu/frontend/nyanpasu/package.json @@ -53,7 +53,7 @@ "@csstools/normalize.css": "12.1.1", "@emotion/babel-plugin": "11.13.5", "@emotion/react": "11.14.0", - "@iconify/json": "2.2.327", + "@iconify/json": "2.2.328", "@monaco-editor/react": "4.7.0", "@tanstack/react-query": "5.71.10", "@tanstack/react-router": "1.114.34", diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 65594b03a8..b0e25f7fde 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.4", - "mihomo_alpha": "alpha-8fa4e81", + "mihomo_alpha": "alpha-984535f", "clash_rs": "v0.7.7", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.7.7-alpha+sha.2f70559" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-04-14T22:20:56.639Z" + "updated_at": "2025-04-15T22:21:12.013Z" } diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 4361f88431..69d7c751ef 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -333,8 +333,8 @@ importers: specifier: 11.14.0 version: 11.14.0(@types/react@19.0.12)(react@19.1.0) '@iconify/json': - specifier: 2.2.327 - version: 2.2.327 + specifier: 2.2.328 + version: 2.2.328 '@monaco-editor/react': specifier: 4.7.0 version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -605,8 +605,8 @@ importers: specifier: 2.26.22 version: 2.26.22 undici: - specifier: 7.6.0 - version: 7.6.0 + specifier: 7.8.0 + version: 7.8.0 yargs: specifier: 17.7.2 version: 17.7.2 @@ -1680,8 +1680,8 @@ packages: '@vue/compiler-sfc': optional: true - '@iconify/json@2.2.327': - resolution: {integrity: sha512-G9LFSD3ZSNHj+0O98ZStkBOd543I05fomTep3DA8UyrreJRnsbjswAe8Ux9teHc/GE/alcCuaaxNqWyCTdN6PA==} + '@iconify/json@2.2.328': + resolution: {integrity: sha512-hdkS6oE+U3tfR2onc1QcHZh+2hkKHjLEkagtdQHDZnx/OS2sFIDc6/XoEh+2BtuzDXfyANgGV/Kpd6oy0OBvqQ==} '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} @@ -7803,8 +7803,8 @@ packages: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} - undici@7.6.0: - resolution: {integrity: sha512-gaFsbThjrDGvAaD670r81RZro/s6H2PVZF640Qn0p5kZK+/rim7/mmyfp2W7VB5vOMaFM8vuFBJUaMlaZTYHlA==} + undici@7.8.0: + resolution: {integrity: sha512-vFv1GA99b7eKO1HG/4RPu2Is3FBTWBrmzqzO0mz+rLxN3yXkE4mqRcb8g8fHxzX4blEysrNZLqg5RbJLqX5buA==} engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: @@ -9545,7 +9545,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@iconify/json@2.2.327': + '@iconify/json@2.2.328': dependencies: '@iconify/types': 2.0.0 pathe: 1.1.2 @@ -16289,7 +16289,7 @@ snapshots: dependencies: '@fastify/busboy': 2.1.1 - undici@7.6.0: {} + undici@7.8.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} diff --git a/clash-nyanpasu/scripts/package.json b/clash-nyanpasu/scripts/package.json index 69b5246ba0..488f2b9135 100644 --- a/clash-nyanpasu/scripts/package.json +++ b/clash-nyanpasu/scripts/package.json @@ -26,7 +26,7 @@ "picocolors": "1.1.1", "tar": "7.4.3", "telegram": "2.26.22", - "undici": "7.6.0", + "undici": "7.8.0", "yargs": "17.7.2" } } diff --git a/clash-verge-rev/UPDATELOG.md b/clash-verge-rev/UPDATELOG.md index 3ec9828dde..3d44c581ff 100644 --- a/clash-verge-rev/UPDATELOG.md +++ b/clash-verge-rev/UPDATELOG.md @@ -17,6 +17,7 @@ #### 新增了: - 外部控制的开关 - 使用 socks 进行内核通信,以解决各种潜在的内核通信异常 + - 允许代理主机地址设置为非 127.0.0.1。对 WSL 代理友好。 #### 重构了: - Mihomo 内核不再使用 http 交互,而是使用 socket 进行通信 diff --git a/clash-verge-rev/package.json b/clash-verge-rev/package.json index 5d9c85a0ec..530ff4265c 100644 --- a/clash-verge-rev/package.json +++ b/clash-verge-rev/package.json @@ -42,7 +42,7 @@ "@tauri-apps/plugin-process": "^2.2.0", "@tauri-apps/plugin-shell": "2.2.0", "@tauri-apps/plugin-updater": "2.3.0", - "@tauri-apps/plugin-window-state": "^2.4.0", + "@tauri-apps/plugin-window-state": "^2.2.0", "@types/d3-shape": "^3.1.7", "@types/json-schema": "^7.0.15", "ahooks": "^3.8.4", diff --git a/clash-verge-rev/pnpm-lock.yaml b/clash-verge-rev/pnpm-lock.yaml index 1ded1edcbc..1a04c7eb62 100644 --- a/clash-verge-rev/pnpm-lock.yaml +++ b/clash-verge-rev/pnpm-lock.yaml @@ -19,25 +19,25 @@ importers: version: 3.2.2(react@18.3.1) '@emotion/react': specifier: ^11.14.0 - version: 11.14.0(@types/react@18.3.18)(react@18.3.1) + version: 11.14.0(@types/react@18.3.20)(react@18.3.1) '@emotion/styled': specifier: ^11.14.0 - version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) '@juggle/resize-observer': specifier: ^3.4.0 version: 3.4.0 '@mui/icons-material': specifier: ^6.4.8 - version: 6.4.8(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + version: 6.4.11(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) '@mui/lab': specifier: 6.0.0-beta.25 - version: 6.0.0-beta.25(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.0.0-beta.25(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/material': specifier: ^6.4.8 - version: 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mui/x-data-grid': specifier: ^7.28.0 - version: 7.28.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 7.28.3(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tauri-apps/api': specifier: 2.2.0 version: 2.2.0 @@ -46,10 +46,10 @@ importers: version: 2.2.2 '@tauri-apps/plugin-dialog': specifier: ^2.2.0 - version: 2.2.0 + version: 2.2.1 '@tauri-apps/plugin-fs': specifier: ^2.2.0 - version: 2.2.0 + version: 2.2.1 '@tauri-apps/plugin-global-shortcut': specifier: ^2.2.0 version: 2.2.0 @@ -58,13 +58,16 @@ importers: version: 2.2.2 '@tauri-apps/plugin-process': specifier: ^2.2.0 - version: 2.2.0 + version: 2.2.1 '@tauri-apps/plugin-shell': specifier: 2.2.0 version: 2.2.0 '@tauri-apps/plugin-updater': specifier: 2.3.0 version: 2.3.0 + '@tauri-apps/plugin-window-state': + specifier: ^2.2.0 + version: 2.2.2 '@types/d3-shape': specifier: ^3.1.7 version: 3.1.7 @@ -76,7 +79,7 @@ importers: version: 3.8.4(react@18.3.1) axios: specifier: ^1.8.3 - version: 1.8.3 + version: 1.8.4 cli-color: specifier: ^2.0.4 version: 2.0.4 @@ -88,13 +91,13 @@ importers: version: 1.11.13 foxact: specifier: ^0.2.44 - version: 0.2.44(react@18.3.1) + version: 0.2.45(react@18.3.1) glob: specifier: ^11.0.1 version: 11.0.1 i18next: specifier: ^24.2.3 - version: 24.2.3(typescript@5.8.2) + version: 24.2.3(typescript@5.8.3) js-base64: specifier: ^3.7.7 version: 3.7.7 @@ -127,16 +130,16 @@ importers: version: 4.1.2(react@18.3.1) react-hook-form: specifier: ^7.54.2 - version: 7.54.2(react@18.3.1) + version: 7.55.0(react@18.3.1) react-i18next: specifier: ^13.5.0 - version: 13.5.0(i18next@24.2.3(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 13.5.0(i18next@24.2.3(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-markdown: specifier: ^9.1.0 - version: 9.1.0(@types/react@18.3.18)(react@18.3.1) + version: 9.1.0(@types/react@18.3.20)(react@18.3.1) react-monaco-editor: specifier: ^0.56.2 - version: 0.56.2(@types/react@18.3.18)(monaco-editor@0.52.2)(react@18.3.1) + version: 0.56.2(@types/react@18.3.20)(monaco-editor@0.52.2)(react@18.3.1) react-router-dom: specifier: ^6.30.0 version: 6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -145,10 +148,10 @@ importers: version: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-virtuoso: specifier: ^4.12.5 - version: 4.12.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 4.12.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) recharts: specifier: ^2.15.1 - version: 2.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) sockette: specifier: ^2.0.6 version: 2.0.6 @@ -163,7 +166,7 @@ importers: version: 1.0.3 zustand: specifier: ^5.0.3 - version: 5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)) + version: 5.0.3(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) devDependencies: '@actions/github': specifier: ^6.0.0 @@ -182,19 +185,19 @@ importers: version: 4.17.12 '@types/react': specifier: ^18.3.18 - version: 18.3.18 + version: 18.3.20 '@types/react-dom': specifier: ^18.3.5 - version: 18.3.5(@types/react@18.3.18) + version: 18.3.6(@types/react@18.3.20) '@types/react-transition-group': specifier: ^4.4.12 - version: 4.4.12(@types/react@18.3.18) + version: 4.4.12(@types/react@18.3.20) '@vitejs/plugin-legacy': specifier: ^6.0.2 - version: 6.0.2(terser@5.39.0)(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0)) + version: 6.0.2(terser@5.39.0)(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)) '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0)) + version: 4.4.0(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)) adm-zip: specifier: ^0.5.16 version: 0.5.16 @@ -209,7 +212,7 @@ importers: version: 9.1.7 meta-json-schema: specifier: ^1.19.3 - version: 1.19.3 + version: 1.19.4 node-fetch: specifier: ^3.3.2 version: 3.3.2 @@ -221,22 +224,22 @@ importers: version: 4.1.1(prettier@3.5.3) sass: specifier: ^1.86.0 - version: 1.86.0 + version: 1.86.3 terser: specifier: ^5.39.0 version: 5.39.0 typescript: specifier: ^5.8.2 - version: 5.8.2 + version: 5.8.3 vite: specifier: ^6.2.2 - version: 6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0) + version: 6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) vite-plugin-monaco-editor: specifier: ^1.1.0 version: 1.1.0(monaco-editor@0.52.2) vite-plugin-svgr: specifier: ^4.3.0 - version: 4.3.0(rollup@4.36.0)(typescript@5.8.2)(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0)) + version: 4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)) packages: @@ -262,32 +265,32 @@ packages: resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} engines: {node: '>=6.9.0'} - '@babel/generator@7.26.10': - resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.25.9': resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.26.5': - resolution: {integrity: sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==} + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.26.9': - resolution: {integrity: sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==} + '@babel/helper-create-class-features-plugin@7.27.0': + resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.26.3': - resolution: {integrity: sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==} + '@babel/helper-create-regexp-features-plugin@7.27.0': + resolution: {integrity: sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.3': - resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} + '@babel/helper-define-polyfill-provider@0.6.4': + resolution: {integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -345,12 +348,12 @@ packages: resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.26.10': - resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==} + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.26.10': - resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==} + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} engines: {node: '>=6.0.0'} hasBin: true @@ -432,8 +435,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.25.9': - resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} + '@babel/plugin-transform-block-scoping@7.27.0': + resolution: {integrity: sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -648,8 +651,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.25.9': - resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + '@babel/plugin-transform-regenerator@7.27.0': + resolution: {integrity: sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -690,8 +693,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.26.7': - resolution: {integrity: sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw==} + '@babel/plugin-transform-typeof-symbol@7.27.0': + resolution: {integrity: sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -731,20 +734,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/runtime@7.26.10': - resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} - '@babel/template@7.26.9': - resolution: {integrity: sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==} + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.26.10': - resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==} + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.10': - resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==} + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} engines: {node: '>=6.9.0'} '@dnd-kit/accessibility@3.1.1': @@ -823,152 +826,152 @@ packages: '@emotion/weak-memoize@0.4.0': resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - '@esbuild/aix-ppc64@0.25.1': - resolution: {integrity: sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==} + '@esbuild/aix-ppc64@0.25.2': + resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.1': - resolution: {integrity: sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==} + '@esbuild/android-arm64@0.25.2': + resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.1': - resolution: {integrity: sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==} + '@esbuild/android-arm@0.25.2': + resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.1': - resolution: {integrity: sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==} + '@esbuild/android-x64@0.25.2': + resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.1': - resolution: {integrity: sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==} + '@esbuild/darwin-arm64@0.25.2': + resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.1': - resolution: {integrity: sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==} + '@esbuild/darwin-x64@0.25.2': + resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.1': - resolution: {integrity: sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==} + '@esbuild/freebsd-arm64@0.25.2': + resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.1': - resolution: {integrity: sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==} + '@esbuild/freebsd-x64@0.25.2': + resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.1': - resolution: {integrity: sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==} + '@esbuild/linux-arm64@0.25.2': + resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.1': - resolution: {integrity: sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==} + '@esbuild/linux-arm@0.25.2': + resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.1': - resolution: {integrity: sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==} + '@esbuild/linux-ia32@0.25.2': + resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.1': - resolution: {integrity: sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==} + '@esbuild/linux-loong64@0.25.2': + resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.1': - resolution: {integrity: sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==} + '@esbuild/linux-mips64el@0.25.2': + resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.1': - resolution: {integrity: sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==} + '@esbuild/linux-ppc64@0.25.2': + resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.1': - resolution: {integrity: sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==} + '@esbuild/linux-riscv64@0.25.2': + resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.1': - resolution: {integrity: sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==} + '@esbuild/linux-s390x@0.25.2': + resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.1': - resolution: {integrity: sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==} + '@esbuild/linux-x64@0.25.2': + resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.1': - resolution: {integrity: sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==} + '@esbuild/netbsd-arm64@0.25.2': + resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.1': - resolution: {integrity: sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==} + '@esbuild/netbsd-x64@0.25.2': + resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.1': - resolution: {integrity: sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==} + '@esbuild/openbsd-arm64@0.25.2': + resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.1': - resolution: {integrity: sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==} + '@esbuild/openbsd-x64@0.25.2': + resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.1': - resolution: {integrity: sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==} + '@esbuild/sunos-x64@0.25.2': + resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.1': - resolution: {integrity: sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==} + '@esbuild/win32-arm64@0.25.2': + resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.1': - resolution: {integrity: sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==} + '@esbuild/win32-ia32@0.25.2': + resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.1': - resolution: {integrity: sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==} + '@esbuild/win32-x64@0.25.2': + resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1036,14 +1039,14 @@ packages: '@types/react': optional: true - '@mui/core-downloads-tracker@6.4.8': - resolution: {integrity: sha512-vjP4+A1ybyCRhDZC7r5EPWu/gLseFZxaGyPdDl94vzVvk6Yj6gahdaqcjbhkaCrJjdZj90m3VioltWPAnWF/zw==} + '@mui/core-downloads-tracker@6.4.11': + resolution: {integrity: sha512-CzAQs9CTzlwbsF9ZYB4o4lLwBv1/qNE264NjuYao+ctAXsmlPtYa8RtER4UsUXSMxNN9Qi+aQdYcKl2sUpnmAw==} - '@mui/icons-material@6.4.8': - resolution: {integrity: sha512-LKGWiLWRyoOw3dWxZQ+lV//mK+4DVTTAiLd2ljmJdD6XV0rDB8JFKjRD9nyn9cJAU5XgWnii7ZR3c93ttUnMKg==} + '@mui/icons-material@6.4.11': + resolution: {integrity: sha512-+jjJGIrB1awNbMv4ZVPPdN/p7O1UKFZ+xqRvNIQ8B1KnlID5hPMPBLM6UUbRF4bu3UDCbu79rn9Nye5LGNzmeA==} engines: {node: '>=14.0.0'} peerDependencies: - '@mui/material': ^6.4.8 + '@mui/material': ^6.4.11 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: @@ -1071,13 +1074,13 @@ packages: '@types/react': optional: true - '@mui/material@6.4.8': - resolution: {integrity: sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q==} + '@mui/material@6.4.11': + resolution: {integrity: sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^6.4.8 + '@mui/material-pigment-css': ^6.4.11 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1091,8 +1094,8 @@ packages: '@types/react': optional: true - '@mui/private-theming@6.4.8': - resolution: {integrity: sha512-sWwQoNSn6elsPTAtSqCf+w5aaGoh7AASURNmpy+QTTD/zwJ0Jgwt0ZaaP6mXq2IcgHxYnYloM/+vJgHPMkRKTQ==} + '@mui/private-theming@6.4.9': + resolution: {integrity: sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1101,8 +1104,8 @@ packages: '@types/react': optional: true - '@mui/styled-engine@6.4.8': - resolution: {integrity: sha512-oyjx1b1FvUCI85ZMO4trrjNxGm90eLN3Ohy0AP/SqK5gWvRQg1677UjNf7t6iETOKAleHctJjuq0B3aXO2gtmw==} + '@mui/styled-engine@6.4.11': + resolution: {integrity: sha512-74AUmlHXaGNbyUqdK/+NwDJOZqgRQw6BcNvhoWYLq3LGbLTkE+khaJ7soz6cIabE4CPYqO2/QAIU1Z/HEjjpcw==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -1114,8 +1117,8 @@ packages: '@emotion/styled': optional: true - '@mui/system@6.4.8': - resolution: {integrity: sha512-gV7iBHoqlsIenU2BP0wq14BefRoZcASZ/4LeyuQglayBl+DfLX5rEd3EYR3J409V2EZpR0NOM1LATAGlNk2cyA==} + '@mui/system@6.4.11': + resolution: {integrity: sha512-gibtsrZEwnDaT5+I/KloOj/yHluX5G8heknuxBpQOdEQ3Gc0avjSImn5hSeKp8D4thiwZiApuggIjZw1dQguUA==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -1138,8 +1141,16 @@ packages: '@types/react': optional: true - '@mui/utils@6.4.8': - resolution: {integrity: sha512-C86gfiZ5BfZ51KqzqoHi1WuuM2QdSKoFhbkZeAfQRB+jCc4YNhhj11UXFVMMsqBgZ+Zy8IHNJW3M9Wj/LOwRXQ==} + '@mui/types@7.4.1': + resolution: {integrity: sha512-gUL8IIAI52CRXP/MixT1tJKt3SI6tVv4U/9soFsTtAsHzaJQptZ42ffdHZV3niX1ei0aUgMvOxBBN0KYqdG39g==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@6.4.9': + resolution: {integrity: sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1148,8 +1159,18 @@ packages: '@types/react': optional: true - '@mui/x-data-grid@7.28.0': - resolution: {integrity: sha512-rOAUB0m1kL2hmgodScJu5AI0AjbVBJtG7erRZ3IhDyk73oRRlgnKttWNks9iIuVCNxXbCbBkvH06rqxgkkuCsQ==} + '@mui/utils@7.0.2': + resolution: {integrity: sha512-72gcuQjPzhj/MLmPHLCgZjy2VjOH4KniR/4qRtXTTXIEwbkgcN+Y5W/rC90rWtMmZbjt9svZev/z+QHUI4j74w==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/x-data-grid@7.28.3': + resolution: {integrity: sha512-T2Vd+3pAnI7UD3B1y+Z7e1eB9N7PCgPbn44KhxM3iT1ldT49HHQ7c2wDHm2Qf4F5UHL6dxvsBF3sGnGyfo+JOA==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.9.0 @@ -1247,36 +1268,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -1304,10 +1331,6 @@ packages: resolution: {integrity: sha512-oRyzXE7nirAn+5yYjCdWQHg3EG2XXcYRoYNOK8Quqnmm+9FyK/2YWVunwudlYl++M3xY+gIAdf0vAYS+p0nKfQ==} engines: {node: '>=18'} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -1324,98 +1347,114 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.36.0': - resolution: {integrity: sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==} + '@rollup/rollup-android-arm-eabi@4.40.0': + resolution: {integrity: sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.36.0': - resolution: {integrity: sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==} + '@rollup/rollup-android-arm64@4.40.0': + resolution: {integrity: sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.36.0': - resolution: {integrity: sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==} + '@rollup/rollup-darwin-arm64@4.40.0': + resolution: {integrity: sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.36.0': - resolution: {integrity: sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==} + '@rollup/rollup-darwin-x64@4.40.0': + resolution: {integrity: sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.36.0': - resolution: {integrity: sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==} + '@rollup/rollup-freebsd-arm64@4.40.0': + resolution: {integrity: sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.36.0': - resolution: {integrity: sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==} + '@rollup/rollup-freebsd-x64@4.40.0': + resolution: {integrity: sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': - resolution: {integrity: sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==} + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': + resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.36.0': - resolution: {integrity: sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==} + '@rollup/rollup-linux-arm-musleabihf@4.40.0': + resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.36.0': - resolution: {integrity: sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==} + '@rollup/rollup-linux-arm64-gnu@4.40.0': + resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.36.0': - resolution: {integrity: sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==} + '@rollup/rollup-linux-arm64-musl@4.40.0': + resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': - resolution: {integrity: sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==} + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': + resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': - resolution: {integrity: sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==} + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': + resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-gnu@4.36.0': - resolution: {integrity: sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==} + '@rollup/rollup-linux-riscv64-gnu@4.40.0': + resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-s390x-gnu@4.36.0': - resolution: {integrity: sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==} + '@rollup/rollup-linux-riscv64-musl@4.40.0': + resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.40.0': + resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.36.0': - resolution: {integrity: sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==} + '@rollup/rollup-linux-x64-gnu@4.40.0': + resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.36.0': - resolution: {integrity: sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==} + '@rollup/rollup-linux-x64-musl@4.40.0': + resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-win32-arm64-msvc@4.36.0': - resolution: {integrity: sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==} + '@rollup/rollup-win32-arm64-msvc@4.40.0': + resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.36.0': - resolution: {integrity: sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==} + '@rollup/rollup-win32-ia32-msvc@4.40.0': + resolution: {integrity: sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.36.0': - resolution: {integrity: sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==} + '@rollup/rollup-win32-x64-msvc@4.40.0': + resolution: {integrity: sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==} cpu: [x64] os: [win32] @@ -1513,24 +1552,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.2.7': resolution: {integrity: sha512-+8HZ+txff/Y3YjAh80XcLXcX8kpGXVdr1P8AfjLHxHdS6QD4Md+acSxGTTNbplmHuBaSHJvuTvZf9tU1eDCTDg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tauri-apps/cli-linux-x64-gnu@2.2.7': resolution: {integrity: sha512-ahlSnuCnUntblp9dG7/w5ZWZOdzRFi3zl0oScgt7GF4KNAOEa7duADsxPA4/FT2hLRa0SvpqtD4IYFvCxoVv3Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tauri-apps/cli-linux-x64-musl@2.2.7': resolution: {integrity: sha512-+qKAWnJRSX+pjjRbKAQgTdFY8ecdcu8UdJ69i7wn3ZcRn2nMMzOO2LOMOTQV42B7/Q64D1pIpmZj9yblTMvadA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.2.7': resolution: {integrity: sha512-aa86nRnrwT04u9D9fhf5JVssuAZlUCCc8AjqQjqODQjMd4BMA2+d4K9qBMpEG/1kVh95vZaNsLogjEaqSTTw4A==} @@ -1558,11 +1601,11 @@ packages: '@tauri-apps/plugin-clipboard-manager@2.2.2': resolution: {integrity: sha512-bZvDLMqfcNmsw7Ag8I49jlaCjdpDvvlJHnpp6P+Gg/3xtpSERdwlDxm7cKGbs2mj46dsw4AuG3RoAgcpwgioUA==} - '@tauri-apps/plugin-dialog@2.2.0': - resolution: {integrity: sha512-6bLkYK68zyK31418AK5fNccCdVuRnNpbxquCl8IqgFByOgWFivbiIlvb79wpSXi0O+8k8RCSsIpOquebusRVSg==} + '@tauri-apps/plugin-dialog@2.2.1': + resolution: {integrity: sha512-wZmCouo4PgTosh/UoejPw9DPs6RllS5Pp3fuOV2JobCu36mR5AXU2MzU9NZiVaFi/5Zfc8RN0IhcZHnksJ1o8A==} - '@tauri-apps/plugin-fs@2.2.0': - resolution: {integrity: sha512-+08mApuONKI8/sCNEZ6AR8vf5vI9DXD4YfrQ9NQmhRxYKMLVhRW164vdW5BSLmMpuevftpQ2FVoL9EFkfG9Z+g==} + '@tauri-apps/plugin-fs@2.2.1': + resolution: {integrity: sha512-KdGzvvA4Eg0Dhw55MwczFbjxLxsTx0FvwwC/0StXlr6IxwPUxh5ziZQoaugkBFs8t+wfebdQrjBEzd8NmmDXNw==} '@tauri-apps/plugin-global-shortcut@2.2.0': resolution: {integrity: sha512-clI9Bg/BcxWXNDK+ij601o1qC2WxMEy8ovhGgEW5Ai17oPy0KK8uwzmc59KiVnOYKpBWHCUPqBxG+KBNUFXgzw==} @@ -1570,8 +1613,8 @@ packages: '@tauri-apps/plugin-notification@2.2.2': resolution: {integrity: sha512-d71rJdtkFUTcG4dqydnv6d7ZwlNZVcdjrVOPwc9GsF6y9DgVN1WCZ9T/vbfD2qrJslf7ai+rnNJc62TLLC2IdA==} - '@tauri-apps/plugin-process@2.2.0': - resolution: {integrity: sha512-uypN2Crmyop9z+KRJr3zl71OyVFgTuvHFjsJ0UxxQ/J5212jVa5w4nPEYjIewcn8bUEXacRebwE6F7owgrbhSw==} + '@tauri-apps/plugin-process@2.2.1': + resolution: {integrity: sha512-cF/k8J+YjjuowhNG1AboHNTlrGiOwgX5j6NzsX6WFf9FMzyZUchkCgZMxCdSE5NIgFX0vvOgLQhODFJgbMenLg==} '@tauri-apps/plugin-shell@2.2.0': resolution: {integrity: sha512-iC3Ic1hLmasoboG7BO+7p+AriSoqAwKrIk+Hpk+S/bjTQdXqbl2GbdclghI4gM32X0bls7xHzIFqhRdrlvJeaA==} @@ -1579,17 +1622,20 @@ packages: '@tauri-apps/plugin-updater@2.3.0': resolution: {integrity: sha512-qdzyZEUN69FZQ/nRx51fBub10tT6wffJl3DLVo9q922Gvw8Wk++rZhoD9eethPlZYbog/7RGgT8JkrfLh5BKAg==} + '@tauri-apps/plugin-window-state@2.2.2': + resolution: {integrity: sha512-7pFwmMtGhhhE/WgmM7PUrj0BSSWVAQMfDdYbRalphIqqF1tWBvxtlxclx8bTutpXHLJTQoCpIeWtBEIXsoAlGw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} '@types/babel__template@7.4.4': resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} '@types/d3-array@3.2.1': resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} @@ -1624,8 +1670,8 @@ packages: '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -1651,17 +1697,14 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.14.10': - resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} - '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/react-dom@18.3.5': - resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} + '@types/react-dom@18.3.6': + resolution: {integrity: sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==} peerDependencies: '@types/react': ^18.0.0 @@ -1670,8 +1713,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@18.3.18': - resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + '@types/react@18.3.20': + resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1689,8 +1732,8 @@ packages: terser: ^5.16.0 vite: ^6.0.0 - '@vitejs/plugin-react@4.3.4': - resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + '@vitejs/plugin-react@4.4.0': + resolution: {integrity: sha512-x/EztcTKVj+TDeANY1WjNeYsvZjZdfWRMP/KXi5Yn8BoTzpa13ZltaQqKfvWYbX8CE10GOHHdC5v86jY9x8i/g==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 @@ -1736,15 +1779,15 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.8.3: - resolution: {integrity: sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==} + axios@1.8.4: + resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} babel-plugin-macros@3.1.0: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} - babel-plugin-polyfill-corejs2@0.4.12: - resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} + babel-plugin-polyfill-corejs2@0.4.13: + resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1753,8 +1796,8 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.3: - resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} + babel-plugin-polyfill-regenerator@0.6.4: + resolution: {integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1801,8 +1844,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001706: - resolution: {integrity: sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==} + caniuse-lite@1.0.30001714: + resolution: {integrity: sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1998,8 +2041,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.120: - resolution: {integrity: sha512-oTUp3gfX1gZI+xfD2djr2rzQdHCwHzPQrrK0CD7WpTdF0nPdQ/INcRVjWgLdCT4a9W3jFObR9DAfsuyFQnI8CQ==} + electron-to-chromium@1.5.137: + resolution: {integrity: sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2044,8 +2087,8 @@ packages: es6-weak-map@2.0.3: resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} - esbuild@0.25.1: - resolution: {integrity: sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==} + esbuild@0.25.2: + resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} engines: {node: '>=18'} hasBin: true @@ -2123,8 +2166,8 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - foxact@0.2.44: - resolution: {integrity: sha512-+dBx0dXnfTDuYrDIDXYVeQw+Dw6YEDwyZwvmkycCuzP78rWfE7xBSzlEIX46ge14hD4DZ3i2tTEE3SoDTAVihA==} + foxact@0.2.45: + resolution: {integrity: sha512-pizJ+4hlSxs+mZKjJxhcwzY1uR4/7paZGgU/NQLrzY6Q0wa8RBZvhOxzg/HzXRAcglTt2y6DlIqPn85cTNdmcg==} peerDependencies: react: '*' peerDependenciesMeta: @@ -2151,10 +2194,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - glob@11.0.1: resolution: {integrity: sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==} engines: {node: 20 || >=22} @@ -2216,8 +2255,8 @@ packages: resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} engines: {node: '>= 4'} - immutable@5.0.3: - resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} + immutable@5.1.1: + resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} @@ -2278,9 +2317,6 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.1.0: resolution: {integrity: sha512-9DDdhb5j6cpeitCbvLO7n7J4IxnbM6hoF6O1g4HQ5TfhvvKN8ywDM7668ZhMHRqVmxqhps/F6syWK2KcPxYlkw==} engines: {node: 20 || >=22} @@ -2346,11 +2382,8 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@11.0.2: - resolution: {integrity: sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==} + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -2398,8 +2431,8 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} - meta-json-schema@1.19.3: - resolution: {integrity: sha512-jkVUw6tI/FsvIQWBkQeC21b0BVPpG5Mu99T8PefxB1Zin3oqSrSneDAgjofbORzeW1gm8RYOocj7mivyISptyQ==} + meta-json-schema@1.19.4: + resolution: {integrity: sha512-GACLHXqADmVe4sWwJAyyKprdKrL+EsxxF41UPM5Il+SDkDTb1aTxMZ06/ZzZHS42sRQBDIafJNT21sM2fXjZ4w==} engines: {node: '>=18', pnpm: '>=9'} micromark-core-commonmark@2.0.3: @@ -2481,16 +2514,12 @@ packages: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - minizlib@3.0.1: - resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} engines: {node: '>= 18'} mkdirp@3.0.1: @@ -2600,10 +2629,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - path-scurry@2.0.0: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} @@ -2666,8 +2691,8 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-hook-form@7.54.2: - resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==} + react-hook-form@7.55.0: + resolution: {integrity: sha512-XRnjsH3GVMQz1moZTW53MxfoWN7aDpUg/GpVNc4A3eXRVNdGXfbzJ4vM4aLQ8g6XCUh1nIbx70aaNCl7kxnjog==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -2691,8 +2716,8 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-is@19.1.0: + resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==} react-markdown@9.1.0: resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==} @@ -2707,8 +2732,8 @@ packages: monaco-editor: ^0.52.0 react: '>=16 <= 18' - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} react-router-dom@6.30.0: @@ -2736,8 +2761,8 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' - react-virtuoso@4.12.5: - resolution: {integrity: sha512-YeCbRRsC9CLf0buD0Rct7WsDbzf+yBU1wGbo05/XjbcN2nJuhgh040m3y3+6HVogTZxEqVm45ac9Fpae4/MxRQ==} + react-virtuoso@4.12.6: + resolution: {integrity: sha512-bfvS6aCL1ehXmq39KRiz/vxznGUbtA27I5I24TYCe1DhMf84O3aVNCIwrSjYQjkJGJGzY46ihdN8WkYlemuhMQ==} peerDependencies: react: '>=16 || >=17 || >= 18 || >= 19' react-dom: '>=16 || >=17 || >= 18 || >=19' @@ -2753,8 +2778,8 @@ packages: recharts-scale@0.4.5: resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} - recharts@2.15.1: - resolution: {integrity: sha512-v8PUTUlyiDe56qUj82w/EDVuzEFXwEHp9/xOowGAZwfLjB9uAy3GllQVIYMWF6nU+qibx85WF75zD7AjqoT54Q==} + recharts@2.15.2: + resolution: {integrity: sha512-xv9lVztv3ingk7V3Jf05wfAZbM9Q2umJzu5t/cfnAK7LUslNrGT7LPBr74G+ok8kSCeFMaePmWMg0rcYOnczTw==} engines: {node: '>=14'} peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2787,8 +2812,8 @@ packages: remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-rehype@11.1.1: - resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2805,17 +2830,13 @@ packages: engines: {node: '>= 0.4'} hasBin: true - rimraf@5.0.10: - resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} - hasBin: true - - rollup@4.36.0: - resolution: {integrity: sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==} + rollup@4.40.0: + resolution: {integrity: sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - sass@1.86.0: - resolution: {integrity: sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==} + sass@1.86.3: + resolution: {integrity: sha512-iGtg8kus4GrsGLRDLRBRHY9dNVA78ZaS7xr01cWnS7PEMQyFtTqBiyCrfpTYTZXRWM94akzckYjh8oADfFNTzw==} engines: {node: '>=14.0.0'} hasBin: true @@ -2963,16 +2984,13 @@ packages: types-pac@1.0.3: resolution: {integrity: sha512-MF2UAZGvGMOM+vHi9Zj/LvQqdNN1m1xSB+PjAW9B/GvFqaB4GwR18YaIbGIGDRTW/J8iqFXQHLZd5eJVtho46w==} - typescript@5.8.2: - resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} hasBin: true - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - undici@5.28.5: - resolution: {integrity: sha512-zICwjrDrcrUE0pyyJc1I2QzBkLM8FINsgOrt6WjA+BgajVq9Nxu2PbFFXUrAggLfDXlZGZBVZYw7WNV5KiBiBA==} + undici@5.29.0: + resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} engines: {node: '>=14.0'} unicode-canonical-property-names-ecmascript@2.0.1: @@ -3018,8 +3036,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - use-sync-external-store@1.4.0: - resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -3042,8 +3060,8 @@ packages: peerDependencies: vite: '>=2.6.0' - vite@6.2.2: - resolution: {integrity: sha512-yW7PeMM+LkDzc7CgJuRLMW2Jz0FxMOsVJ8Lv3gpgW9WLcb9cTW+121UEr1hvmfR7w3SegR5ItvYyzVz1vxNJgQ==} + vite@6.2.6: + resolution: {integrity: sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -3133,8 +3151,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} engines: {node: '>= 14'} hasBin: true @@ -3175,7 +3193,7 @@ snapshots: '@actions/http-client@2.2.3': dependencies: tunnel: 0.0.6 - undici: 5.28.5 + undici: 5.29.0 '@ampproject/remapping@2.3.0': dependencies: @@ -3194,14 +3212,14 @@ snapshots: dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.10 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) - '@babel/helpers': 7.26.10 - '@babel/parser': 7.26.10 - '@babel/template': 7.26.9 - '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 convert-source-map: 2.0.0 debug: 4.4.0 gensync: 1.0.0-beta.2 @@ -3210,19 +3228,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.26.10': + '@babel/generator@7.27.0': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 - '@babel/helper-compilation-targets@7.26.5': + '@babel/helper-compilation-targets@7.27.0': dependencies: '@babel/compat-data': 7.26.8 '@babel/helper-validator-option': 7.25.9 @@ -3230,7 +3248,7 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.26.9(@babel/core@7.26.10)': + '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 @@ -3238,22 +3256,22 @@ snapshots: '@babel/helper-optimise-call-expression': 7.25.9 '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.10)': + '@babel/helper-create-regexp-features-plugin@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.10)': + '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 debug: 4.4.0 lodash.debounce: 4.0.8 @@ -3263,15 +3281,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color @@ -3280,13 +3298,13 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-module-imports': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@babel/helper-plugin-utils@7.26.5': {} @@ -3295,7 +3313,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-wrap-function': 7.25.9 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3304,14 +3322,14 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-member-expression-to-functions': 7.25.9 '@babel/helper-optimise-call-expression': 7.25.9 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color @@ -3323,26 +3341,26 @@ snapshots: '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/template': 7.26.9 - '@babel/traverse': 7.26.10 - '@babel/types': 7.26.10 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 transitivePeerDependencies: - supports-color - '@babel/helpers@7.26.10': + '@babel/helpers@7.27.0': dependencies: - '@babel/template': 7.26.9 - '@babel/types': 7.26.10 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 - '@babel/parser@7.26.10': + '@babel/parser@7.27.0': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3369,7 +3387,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3390,7 +3408,7 @@ snapshots: '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.10)': @@ -3403,7 +3421,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3421,7 +3439,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-block-scoping@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 @@ -3429,7 +3447,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -3437,7 +3455,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -3446,10 +3464,10 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -3458,7 +3476,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/template': 7.26.9 + '@babel/template': 7.27.0 '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.10)': dependencies: @@ -3468,7 +3486,7 @@ snapshots: '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.10)': @@ -3479,7 +3497,7 @@ snapshots: '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.10)': @@ -3508,9 +3526,9 @@ snapshots: '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3556,7 +3574,7 @@ snapshots: '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.26.10 + '@babel/traverse': 7.27.0 transitivePeerDependencies: - supports-color @@ -3571,7 +3589,7 @@ snapshots: '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.10)': @@ -3592,7 +3610,7 @@ snapshots: '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) @@ -3625,7 +3643,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -3634,7 +3652,7 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-annotate-as-pure': 7.25.9 - '@babel/helper-create-class-features-plugin': 7.26.9(@babel/core@7.26.10) + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 transitivePeerDependencies: - supports-color @@ -3654,7 +3672,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.10)': + '@babel/plugin-transform-regenerator@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 @@ -3663,7 +3681,7 @@ snapshots: '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.10)': @@ -3694,7 +3712,7 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-typeof-symbol@7.26.7(@babel/core@7.26.10)': + '@babel/plugin-transform-typeof-symbol@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 @@ -3707,26 +3725,26 @@ snapshots: '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 - '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.10) + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) '@babel/helper-plugin-utils': 7.26.5 '@babel/preset-env@7.26.9(@babel/core@7.26.10)': dependencies: '@babel/compat-data': 7.26.8 '@babel/core': 7.26.10 - '@babel/helper-compilation-targets': 7.26.5 + '@babel/helper-compilation-targets': 7.27.0 '@babel/helper-plugin-utils': 7.26.5 '@babel/helper-validator-option': 7.25.9 '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.10) @@ -3742,7 +3760,7 @@ snapshots: '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.10) - '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.27.0(@babel/core@7.26.10) '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.10) '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.10) @@ -3776,22 +3794,22 @@ snapshots: '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.10) - '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.27.0(@babel/core@7.26.10) '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.10) '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.10) - '@babel/plugin-transform-typeof-symbol': 7.26.7(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.27.0(@babel/core@7.26.10) '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.10) '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) - babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.26.10) babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) - babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.26.10) core-js-compat: 3.41.0 semver: 6.3.1 transitivePeerDependencies: @@ -3801,32 +3819,32 @@ snapshots: dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 esutils: 2.0.3 - '@babel/runtime@7.26.10': + '@babel/runtime@7.27.0': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.26.9': + '@babel/template@7.27.0': dependencies: '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 - '@babel/traverse@7.26.10': + '@babel/traverse@7.27.0': dependencies: '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.10 - '@babel/parser': 7.26.10 - '@babel/template': 7.26.9 - '@babel/types': 7.26.10 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.26.10': + '@babel/types@7.27.0': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 @@ -3859,7 +3877,7 @@ snapshots: '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.25.9 - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 '@emotion/serialize': 1.3.3 @@ -3888,9 +3906,9 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1)': + '@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 '@emotion/babel-plugin': 11.13.5 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 @@ -3900,7 +3918,7 @@ snapshots: hoist-non-react-statics: 3.3.2 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 transitivePeerDependencies: - supports-color @@ -3914,18 +3932,18 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 '@emotion/babel-plugin': 11.13.5 '@emotion/is-prop-valid': 1.3.1 - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) '@emotion/utils': 1.4.2 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 transitivePeerDependencies: - supports-color @@ -3939,79 +3957,79 @@ snapshots: '@emotion/weak-memoize@0.4.0': {} - '@esbuild/aix-ppc64@0.25.1': + '@esbuild/aix-ppc64@0.25.2': optional: true - '@esbuild/android-arm64@0.25.1': + '@esbuild/android-arm64@0.25.2': optional: true - '@esbuild/android-arm@0.25.1': + '@esbuild/android-arm@0.25.2': optional: true - '@esbuild/android-x64@0.25.1': + '@esbuild/android-x64@0.25.2': optional: true - '@esbuild/darwin-arm64@0.25.1': + '@esbuild/darwin-arm64@0.25.2': optional: true - '@esbuild/darwin-x64@0.25.1': + '@esbuild/darwin-x64@0.25.2': optional: true - '@esbuild/freebsd-arm64@0.25.1': + '@esbuild/freebsd-arm64@0.25.2': optional: true - '@esbuild/freebsd-x64@0.25.1': + '@esbuild/freebsd-x64@0.25.2': optional: true - '@esbuild/linux-arm64@0.25.1': + '@esbuild/linux-arm64@0.25.2': optional: true - '@esbuild/linux-arm@0.25.1': + '@esbuild/linux-arm@0.25.2': optional: true - '@esbuild/linux-ia32@0.25.1': + '@esbuild/linux-ia32@0.25.2': optional: true - '@esbuild/linux-loong64@0.25.1': + '@esbuild/linux-loong64@0.25.2': optional: true - '@esbuild/linux-mips64el@0.25.1': + '@esbuild/linux-mips64el@0.25.2': optional: true - '@esbuild/linux-ppc64@0.25.1': + '@esbuild/linux-ppc64@0.25.2': optional: true - '@esbuild/linux-riscv64@0.25.1': + '@esbuild/linux-riscv64@0.25.2': optional: true - '@esbuild/linux-s390x@0.25.1': + '@esbuild/linux-s390x@0.25.2': optional: true - '@esbuild/linux-x64@0.25.1': + '@esbuild/linux-x64@0.25.2': optional: true - '@esbuild/netbsd-arm64@0.25.1': + '@esbuild/netbsd-arm64@0.25.2': optional: true - '@esbuild/netbsd-x64@0.25.1': + '@esbuild/netbsd-x64@0.25.2': optional: true - '@esbuild/openbsd-arm64@0.25.1': + '@esbuild/openbsd-arm64@0.25.2': optional: true - '@esbuild/openbsd-x64@0.25.1': + '@esbuild/openbsd-x64@0.25.2': optional: true - '@esbuild/sunos-x64@0.25.1': + '@esbuild/sunos-x64@0.25.2': optional: true - '@esbuild/win32-arm64@0.25.1': + '@esbuild/win32-arm64@0.25.2': optional: true - '@esbuild/win32-ia32@0.25.1': + '@esbuild/win32-ia32@0.25.2': optional: true - '@esbuild/win32-x64@0.25.1': + '@esbuild/win32-x64@0.25.2': optional: true '@fastify/busboy@2.1.1': {} @@ -4070,80 +4088,80 @@ snapshots: '@juggle/resize-observer@3.4.0': {} - '@mui/base@5.0.0-beta.69(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/base@5.0.0-beta.69(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.18) - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@mui/types': 7.4.1(@types/react@18.3.20) + '@mui/utils': 6.4.9(@types/react@18.3.20)(react@18.3.1) '@popperjs/core': 2.11.8 clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@mui/core-downloads-tracker@6.4.8': {} + '@mui/core-downloads-tracker@6.4.11': {} - '@mui/icons-material@6.4.8(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@mui/icons-material@6.4.11(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/material': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/material': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@mui/lab@6.0.0-beta.25(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/lab@6.0.0-beta.25(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/base': 5.0.0-beta.69(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/material': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.18) - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/base': 5.0.0-beta.69(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/material': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/system': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@mui/types': 7.4.1(@types/react@18.3.20) + '@mui/utils': 6.4.9(@types/react@18.3.20)(react@18.3.1) clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@types/react': 18.3.20 - '@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/core-downloads-tracker': 6.4.8 - '@mui/system': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.18) - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/core-downloads-tracker': 6.4.11 + '@mui/system': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@mui/types': 7.2.24(@types/react@18.3.20) + '@mui/utils': 6.4.9(@types/react@18.3.20)(react@18.3.1) '@popperjs/core': 2.11.8 - '@types/react-transition-group': 4.4.12(@types/react@18.3.18) + '@types/react-transition-group': 4.4.12(@types/react@18.3.20) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 19.0.0 + react-is: 19.1.0 react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@types/react': 18.3.20 - '@mui/private-theming@6.4.8(@types/react@18.3.18)(react@18.3.1)': + '@mui/private-theming@6.4.9(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/utils': 6.4.9(@types/react@18.3.20)(react@18.3.1) prop-types: 15.8.1 react: 18.3.1 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@mui/styled-engine@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)': + '@mui/styled-engine@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 @@ -4151,64 +4169,82 @@ snapshots: prop-types: 15.8.1 react: 18.3.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) - '@mui/system@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@mui/system@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/private-theming': 6.4.8(@types/react@18.3.18)(react@18.3.1) - '@mui/styled-engine': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.24(@types/react@18.3.18) - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/private-theming': 6.4.9(@types/react@18.3.20)(react@18.3.1) + '@mui/styled-engine': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.24(@types/react@18.3.20) + '@mui/utils': 6.4.9(@types/react@18.3.20)(react@18.3.1) clsx: 2.1.1 csstype: 3.1.3 prop-types: 15.8.1 react: 18.3.1 optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@types/react': 18.3.18 + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@types/react': 18.3.20 - '@mui/types@7.2.24(@types/react@18.3.18)': + '@mui/types@7.2.24(@types/react@18.3.20)': optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@mui/utils@6.4.8(@types/react@18.3.18)(react@18.3.1)': + '@mui/types@7.4.1(@types/react@18.3.20)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/types': 7.2.24(@types/react@18.3.18) + '@babel/runtime': 7.27.0 + optionalDependencies: + '@types/react': 18.3.20 + + '@mui/utils@6.4.9(@types/react@18.3.20)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/types': 7.2.24(@types/react@18.3.20) '@types/prop-types': 15.7.14 clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 - react-is: 19.0.0 + react-is: 19.1.0 optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@mui/x-data-grid@7.28.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/utils@7.0.2(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/material': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 6.4.8(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) - '@mui/x-internals': 7.28.0(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/types': 7.4.1(@types/react@18.3.20) + '@types/prop-types': 15.7.14 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 19.1.0 + optionalDependencies: + '@types/react': 18.3.20 + + '@mui/x-data-grid@7.28.3(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@mui/material@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.0 + '@mui/material': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/system': 6.4.11(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) + '@mui/utils': 7.0.2(@types/react@18.3.20)(react@18.3.1) + '@mui/x-internals': 7.28.0(@types/react@18.3.20)(react@18.3.1) clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) reselect: 5.1.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) optionalDependencies: - '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) - '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + '@emotion/react': 11.14.0(@types/react@18.3.20)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.20)(react@18.3.1))(@types/react@18.3.20)(react@18.3.1) transitivePeerDependencies: - '@types/react' - '@mui/x-internals@7.28.0(@types/react@18.3.18)(react@18.3.1)': + '@mui/x-internals@7.28.0(@types/react@18.3.20)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.10 - '@mui/utils': 6.4.8(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.27.0 + '@mui/utils': 7.0.2(@types/react@18.3.20)(react@18.3.1) react: 18.3.1 transitivePeerDependencies: - '@types/react' @@ -4336,76 +4372,76 @@ snapshots: dependencies: semver: 7.6.3 - '@pkgjs/parseargs@0.11.0': - optional: true - '@popperjs/core@2.11.8': {} '@remix-run/router@1.23.0': {} - '@rollup/pluginutils@5.1.4(rollup@4.36.0)': + '@rollup/pluginutils@5.1.4(rollup@4.40.0)': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 estree-walker: 2.0.2 picomatch: 4.0.2 optionalDependencies: - rollup: 4.36.0 + rollup: 4.40.0 - '@rollup/rollup-android-arm-eabi@4.36.0': + '@rollup/rollup-android-arm-eabi@4.40.0': optional: true - '@rollup/rollup-android-arm64@4.36.0': + '@rollup/rollup-android-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-arm64@4.36.0': + '@rollup/rollup-darwin-arm64@4.40.0': optional: true - '@rollup/rollup-darwin-x64@4.36.0': + '@rollup/rollup-darwin-x64@4.40.0': optional: true - '@rollup/rollup-freebsd-arm64@4.36.0': + '@rollup/rollup-freebsd-arm64@4.40.0': optional: true - '@rollup/rollup-freebsd-x64@4.36.0': + '@rollup/rollup-freebsd-x64@4.40.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.36.0': + '@rollup/rollup-linux-arm-gnueabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.36.0': + '@rollup/rollup-linux-arm-musleabihf@4.40.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.36.0': + '@rollup/rollup-linux-arm64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.36.0': + '@rollup/rollup-linux-arm64-musl@4.40.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.36.0': + '@rollup/rollup-linux-loongarch64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.36.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.36.0': + '@rollup/rollup-linux-riscv64-gnu@4.40.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.36.0': + '@rollup/rollup-linux-riscv64-musl@4.40.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.36.0': + '@rollup/rollup-linux-s390x-gnu@4.40.0': optional: true - '@rollup/rollup-linux-x64-musl@4.36.0': + '@rollup/rollup-linux-x64-gnu@4.40.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.36.0': + '@rollup/rollup-linux-x64-musl@4.40.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.36.0': + '@rollup/rollup-win32-arm64-msvc@4.40.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.36.0': + '@rollup/rollup-win32-ia32-msvc@4.40.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.40.0': optional: true '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.10)': @@ -4452,12 +4488,12 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.10) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.10) - '@svgr/core@8.1.0(typescript@5.8.2)': + '@svgr/core@8.1.0(typescript@5.8.3)': dependencies: '@babel/core': 7.26.10 '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.8.2) + cosmiconfig: 8.3.6(typescript@5.8.3) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -4465,14 +4501,14 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.2))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.8.3))': dependencies: '@babel/core': 7.26.10 '@svgr/babel-preset': 8.1.0(@babel/core@7.26.10) - '@svgr/core': 8.1.0(typescript@5.8.2) + '@svgr/core': 8.1.0(typescript@5.8.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: @@ -4527,11 +4563,11 @@ snapshots: dependencies: '@tauri-apps/api': 2.2.0 - '@tauri-apps/plugin-dialog@2.2.0': + '@tauri-apps/plugin-dialog@2.2.1': dependencies: '@tauri-apps/api': 2.2.0 - '@tauri-apps/plugin-fs@2.2.0': + '@tauri-apps/plugin-fs@2.2.1': dependencies: '@tauri-apps/api': 2.2.0 @@ -4543,7 +4579,7 @@ snapshots: dependencies: '@tauri-apps/api': 2.2.0 - '@tauri-apps/plugin-process@2.2.0': + '@tauri-apps/plugin-process@2.2.1': dependencies: '@tauri-apps/api': 2.2.0 @@ -4555,26 +4591,30 @@ snapshots: dependencies: '@tauri-apps/api': 2.2.0 + '@tauri-apps/plugin-window-state@2.2.2': + dependencies: + '@tauri-apps/api': 2.2.0 + '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 - '@types/babel__generator': 7.6.8 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 + '@types/babel__traverse': 7.20.7 - '@types/babel__generator@7.6.8': + '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.26.10 - '@babel/types': 7.26.10 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 - '@types/babel__traverse@7.20.6': + '@types/babel__traverse@7.20.7': dependencies: - '@babel/types': 7.26.10 + '@babel/types': 7.27.0 '@types/d3-array@3.2.1': {} @@ -4606,9 +4646,9 @@ snapshots: '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 - '@types/estree@1.0.6': {} + '@types/estree@1.0.7': {} '@types/hast@3.0.4': dependencies: @@ -4632,24 +4672,19 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.14.10': - dependencies: - undici-types: 5.26.5 - optional: true - '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.14': {} - '@types/react-dom@18.3.5(@types/react@18.3.18)': + '@types/react-dom@18.3.6(@types/react@18.3.20)': dependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@types/react-transition-group@4.4.12(@types/react@18.3.18)': + '@types/react-transition-group@4.4.12(@types/react@18.3.20)': dependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 - '@types/react@18.3.18': + '@types/react@18.3.20': dependencies: '@types/prop-types': 15.7.14 csstype: 3.1.3 @@ -4660,7 +4695,7 @@ snapshots: '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-legacy@6.0.2(terser@5.39.0)(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0))': + '@vitejs/plugin-legacy@6.0.2(terser@5.39.0)(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1))': dependencies: '@babel/core': 7.26.10 '@babel/preset-env': 7.26.9(@babel/core@7.26.10) @@ -4671,18 +4706,18 @@ snapshots: regenerator-runtime: 0.14.1 systemjs: 6.15.1 terser: 5.39.0 - vite: 6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0) + vite: 6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.4(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0))': + '@vitejs/plugin-react@4.4.0(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1))': dependencies: '@babel/core': 7.26.10 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0) + react-refresh: 0.17.0 + vite: 6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) transitivePeerDependencies: - supports-color @@ -4694,7 +4729,7 @@ snapshots: ahooks@3.8.4(react@18.3.1): dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 dayjs: 1.11.13 intersection-observer: 0.12.2 js-cookie: 3.0.5 @@ -4719,7 +4754,7 @@ snapshots: asynckit@0.4.0: {} - axios@1.8.3: + axios@1.8.4: dependencies: follow-redirects: 1.15.9 form-data: 4.0.2 @@ -4729,15 +4764,15 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 cosmiconfig: 7.1.0 resolve: 1.22.10 - babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.10): + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): dependencies: '@babel/compat-data': 7.26.8 '@babel/core': 7.26.10 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -4745,15 +4780,15 @@ snapshots: babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) core-js-compat: 3.41.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.10): + babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.26.10): dependencies: '@babel/core': 7.26.10 - '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.10) + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) transitivePeerDependencies: - supports-color @@ -4779,8 +4814,8 @@ snapshots: browserslist@4.24.4: dependencies: - caniuse-lite: 1.0.30001706 - electron-to-chromium: 1.5.120 + caniuse-lite: 1.0.30001714 + electron-to-chromium: 1.5.137 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.24.4) @@ -4795,7 +4830,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001706: {} + caniuse-lite@1.0.30001714: {} ccount@2.0.1: {} @@ -4859,14 +4894,14 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.8.2): + cosmiconfig@8.3.6(typescript@5.8.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.8.2 + typescript: 5.8.3 cross-env@7.0.3: dependencies: @@ -4952,7 +4987,7 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 csstype: 3.1.3 dot-case@3.0.4: @@ -4968,7 +5003,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.120: {} + electron-to-chromium@1.5.137: {} emoji-regex@8.0.0: {} @@ -5020,33 +5055,33 @@ snapshots: es6-iterator: 2.0.3 es6-symbol: 3.1.4 - esbuild@0.25.1: + esbuild@0.25.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.1 - '@esbuild/android-arm': 0.25.1 - '@esbuild/android-arm64': 0.25.1 - '@esbuild/android-x64': 0.25.1 - '@esbuild/darwin-arm64': 0.25.1 - '@esbuild/darwin-x64': 0.25.1 - '@esbuild/freebsd-arm64': 0.25.1 - '@esbuild/freebsd-x64': 0.25.1 - '@esbuild/linux-arm': 0.25.1 - '@esbuild/linux-arm64': 0.25.1 - '@esbuild/linux-ia32': 0.25.1 - '@esbuild/linux-loong64': 0.25.1 - '@esbuild/linux-mips64el': 0.25.1 - '@esbuild/linux-ppc64': 0.25.1 - '@esbuild/linux-riscv64': 0.25.1 - '@esbuild/linux-s390x': 0.25.1 - '@esbuild/linux-x64': 0.25.1 - '@esbuild/netbsd-arm64': 0.25.1 - '@esbuild/netbsd-x64': 0.25.1 - '@esbuild/openbsd-arm64': 0.25.1 - '@esbuild/openbsd-x64': 0.25.1 - '@esbuild/sunos-x64': 0.25.1 - '@esbuild/win32-arm64': 0.25.1 - '@esbuild/win32-ia32': 0.25.1 - '@esbuild/win32-x64': 0.25.1 + '@esbuild/aix-ppc64': 0.25.2 + '@esbuild/android-arm': 0.25.2 + '@esbuild/android-arm64': 0.25.2 + '@esbuild/android-x64': 0.25.2 + '@esbuild/darwin-arm64': 0.25.2 + '@esbuild/darwin-x64': 0.25.2 + '@esbuild/freebsd-arm64': 0.25.2 + '@esbuild/freebsd-x64': 0.25.2 + '@esbuild/linux-arm': 0.25.2 + '@esbuild/linux-arm64': 0.25.2 + '@esbuild/linux-ia32': 0.25.2 + '@esbuild/linux-loong64': 0.25.2 + '@esbuild/linux-mips64el': 0.25.2 + '@esbuild/linux-ppc64': 0.25.2 + '@esbuild/linux-riscv64': 0.25.2 + '@esbuild/linux-s390x': 0.25.2 + '@esbuild/linux-x64': 0.25.2 + '@esbuild/netbsd-arm64': 0.25.2 + '@esbuild/netbsd-x64': 0.25.2 + '@esbuild/openbsd-arm64': 0.25.2 + '@esbuild/openbsd-x64': 0.25.2 + '@esbuild/sunos-x64': 0.25.2 + '@esbuild/win32-arm64': 0.25.2 + '@esbuild/win32-ia32': 0.25.2 + '@esbuild/win32-x64': 0.25.2 escalade@3.2.0: {} @@ -5115,7 +5150,7 @@ snapshots: dependencies: fetch-blob: 3.2.0 - foxact@0.2.44(react@18.3.1): + foxact@0.2.45(react@18.3.1): dependencies: client-only: 0.0.1 server-only: 0.0.1 @@ -5147,15 +5182,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - glob@10.4.5: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - glob@11.0.1: dependencies: foreground-child: 3.3.1 @@ -5181,7 +5207,7 @@ snapshots: hast-util-to-jsx-runtime@2.3.6: dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 '@types/hast': 3.0.4 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 @@ -5222,15 +5248,15 @@ snapshots: husky@9.1.7: {} - i18next@24.2.3(typescript@5.8.2): + i18next@24.2.3(typescript@5.8.3): dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 optionalDependencies: - typescript: 5.8.2 + typescript: 5.8.3 ignore@7.0.3: {} - immutable@5.0.3: {} + immutable@5.1.1: {} import-fresh@3.3.1: dependencies: @@ -5279,12 +5305,6 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jackspeak@4.1.0: dependencies: '@isaacs/cliui': 8.0.2 @@ -5331,9 +5351,7 @@ snapshots: dependencies: tslib: 2.8.1 - lru-cache@10.4.3: {} - - lru-cache@11.0.2: {} + lru-cache@11.1.0: {} lru-cache@5.1.1: dependencies: @@ -5451,7 +5469,7 @@ snapshots: meow@13.2.0: {} - meta-json-schema@1.19.3: {} + meta-json-schema@1.19.4: {} micromark-core-commonmark@2.0.3: dependencies: @@ -5602,16 +5620,11 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.1 - minipass@7.1.2: {} - minizlib@3.0.1: + minizlib@3.0.2: dependencies: minipass: 7.1.2 - rimraf: 5.0.10 mkdirp@3.0.1: {} @@ -5646,7 +5659,7 @@ snapshots: vscode-languageserver-textdocument: 1.0.12 vscode-languageserver-types: 3.17.5 vscode-uri: 3.1.0 - yaml: 2.7.0 + yaml: 2.7.1 mri@1.2.0: {} @@ -5721,14 +5734,9 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 - path-scurry@2.0.0: dependencies: - lru-cache: 11.0.2 + lru-cache: 11.1.0 minipass: 7.1.2 path-type@4.0.0: {} @@ -5783,20 +5791,20 @@ snapshots: react-error-boundary@4.1.2(react@18.3.1): dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 react: 18.3.1 react-fast-compare@3.2.2: {} - react-hook-form@7.54.2(react@18.3.1): + react-hook-form@7.55.0(react@18.3.1): dependencies: react: 18.3.1 - react-i18next@13.5.0(i18next@24.2.3(typescript@5.8.2))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-i18next@13.5.0(i18next@24.2.3(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 html-parse-stringify: 3.0.1 - i18next: 24.2.3(typescript@5.8.2) + i18next: 24.2.3(typescript@5.8.3) react: 18.3.1 optionalDependencies: react-dom: 18.3.1(react@18.3.1) @@ -5805,34 +5813,34 @@ snapshots: react-is@18.3.1: {} - react-is@19.0.0: {} + react-is@19.1.0: {} - react-markdown@9.1.0(@types/react@18.3.18)(react@18.3.1): + react-markdown@9.1.0(@types/react@18.3.20)(react@18.3.1): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 18.3.18 + '@types/react': 18.3.20 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 mdast-util-to-hast: 13.2.0 react: 18.3.1 remark-parse: 11.0.0 - remark-rehype: 11.1.1 + remark-rehype: 11.1.2 unified: 11.0.5 unist-util-visit: 5.0.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color - react-monaco-editor@0.56.2(@types/react@18.3.18)(monaco-editor@0.52.2)(react@18.3.1): + react-monaco-editor@0.56.2(@types/react@18.3.20)(monaco-editor@0.52.2)(react@18.3.1): dependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 monaco-editor: 0.52.2 prop-types: 15.8.1 react: 18.3.1 - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} react-router-dom@6.30.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -5856,14 +5864,14 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-virtuoso@4.12.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-virtuoso@4.12.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -5878,7 +5886,7 @@ snapshots: dependencies: decimal.js-light: 2.5.1 - recharts@2.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + recharts@2.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 eventemitter3: 4.0.7 @@ -5901,7 +5909,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.26.10 + '@babel/runtime': 7.27.0 regexpu-core@6.2.0: dependencies: @@ -5927,7 +5935,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-rehype@11.1.1: + remark-rehype@11.1.2: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -5947,39 +5955,36 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - rimraf@5.0.10: + rollup@4.40.0: dependencies: - glob: 10.4.5 - - rollup@4.36.0: - dependencies: - '@types/estree': 1.0.6 + '@types/estree': 1.0.7 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.36.0 - '@rollup/rollup-android-arm64': 4.36.0 - '@rollup/rollup-darwin-arm64': 4.36.0 - '@rollup/rollup-darwin-x64': 4.36.0 - '@rollup/rollup-freebsd-arm64': 4.36.0 - '@rollup/rollup-freebsd-x64': 4.36.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.36.0 - '@rollup/rollup-linux-arm-musleabihf': 4.36.0 - '@rollup/rollup-linux-arm64-gnu': 4.36.0 - '@rollup/rollup-linux-arm64-musl': 4.36.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.36.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.36.0 - '@rollup/rollup-linux-riscv64-gnu': 4.36.0 - '@rollup/rollup-linux-s390x-gnu': 4.36.0 - '@rollup/rollup-linux-x64-gnu': 4.36.0 - '@rollup/rollup-linux-x64-musl': 4.36.0 - '@rollup/rollup-win32-arm64-msvc': 4.36.0 - '@rollup/rollup-win32-ia32-msvc': 4.36.0 - '@rollup/rollup-win32-x64-msvc': 4.36.0 + '@rollup/rollup-android-arm-eabi': 4.40.0 + '@rollup/rollup-android-arm64': 4.40.0 + '@rollup/rollup-darwin-arm64': 4.40.0 + '@rollup/rollup-darwin-x64': 4.40.0 + '@rollup/rollup-freebsd-arm64': 4.40.0 + '@rollup/rollup-freebsd-x64': 4.40.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.0 + '@rollup/rollup-linux-arm-musleabihf': 4.40.0 + '@rollup/rollup-linux-arm64-gnu': 4.40.0 + '@rollup/rollup-linux-arm64-musl': 4.40.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-gnu': 4.40.0 + '@rollup/rollup-linux-riscv64-musl': 4.40.0 + '@rollup/rollup-linux-s390x-gnu': 4.40.0 + '@rollup/rollup-linux-x64-gnu': 4.40.0 + '@rollup/rollup-linux-x64-musl': 4.40.0 + '@rollup/rollup-win32-arm64-msvc': 4.40.0 + '@rollup/rollup-win32-ia32-msvc': 4.40.0 + '@rollup/rollup-win32-x64-msvc': 4.40.0 fsevents: 2.3.3 - sass@1.86.0: + sass@1.86.3: dependencies: chokidar: 4.0.3 - immutable: 5.0.3 + immutable: 5.1.1 source-map-js: 1.2.1 optionalDependencies: '@parcel/watcher': 2.5.1 @@ -6069,7 +6074,7 @@ snapshots: dependencies: dequal: 2.0.3 react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) systemjs@6.15.1: {} @@ -6078,7 +6083,7 @@ snapshots: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 minipass: 7.1.2 - minizlib: 3.0.1 + minizlib: 3.0.2 mkdirp: 3.0.1 yallist: 5.0.0 @@ -6115,12 +6120,9 @@ snapshots: types-pac@1.0.3: {} - typescript@5.8.2: {} + typescript@5.8.3: {} - undici-types@5.26.5: - optional: true - - undici@5.28.5: + undici@5.29.0: dependencies: '@fastify/busboy': 2.1.1 @@ -6176,7 +6178,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - use-sync-external-store@1.4.0(react@18.3.1): + use-sync-external-store@1.5.0(react@18.3.1): dependencies: react: 18.3.1 @@ -6211,28 +6213,27 @@ snapshots: dependencies: monaco-editor: 0.52.2 - vite-plugin-svgr@4.3.0(rollup@4.36.0)(typescript@5.8.2)(vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0)): + vite-plugin-svgr@4.3.0(rollup@4.40.0)(typescript@5.8.3)(vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1)): dependencies: - '@rollup/pluginutils': 5.1.4(rollup@4.36.0) - '@svgr/core': 8.1.0(typescript@5.8.2) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.2)) - vite: 6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0) + '@rollup/pluginutils': 5.1.4(rollup@4.40.0) + '@svgr/core': 8.1.0(typescript@5.8.3) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.8.3)) + vite: 6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1) transitivePeerDependencies: - rollup - supports-color - typescript - vite@6.2.2(@types/node@20.14.10)(sass@1.86.0)(terser@5.39.0)(yaml@2.7.0): + vite@6.2.6(sass@1.86.3)(terser@5.39.0)(yaml@2.7.1): dependencies: - esbuild: 0.25.1 + esbuild: 0.25.2 postcss: 8.5.3 - rollup: 4.36.0 + rollup: 4.40.0 optionalDependencies: - '@types/node': 20.14.10 fsevents: 2.3.3 - sass: 1.86.0 + sass: 1.86.3 terser: 5.39.0 - yaml: 2.7.0 + yaml: 2.7.1 void-elements@3.1.0: {} @@ -6275,14 +6276,14 @@ snapshots: yaml@1.10.2: {} - yaml@2.7.0: {} + yaml@2.7.1: {} yocto-queue@0.1.0: {} - zustand@5.0.3(@types/react@18.3.18)(react@18.3.1)(use-sync-external-store@1.4.0(react@18.3.1)): + zustand@5.0.3(@types/react@18.3.20)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)): optionalDependencies: - '@types/react': 18.3.18 + '@types/react': 18.3.20 react: 18.3.1 - use-sync-external-store: 1.4.0(react@18.3.1) + use-sync-external-store: 1.5.0(react@18.3.1) zwitch@2.0.4: {} diff --git a/clash-verge-rev/src-tauri/Cargo.lock b/clash-verge-rev/src-tauri/Cargo.lock index bd306bdd5b..26decde9ae 100644 --- a/clash-verge-rev/src-tauri/Cargo.lock +++ b/clash-verge-rev/src-tauri/Cargo.lock @@ -1056,6 +1056,7 @@ dependencies = [ "dirs 6.0.0", "dunce", "futures", + "gethostname 1.0.1", "getrandom 0.3.2", "image", "imageproc", @@ -2416,6 +2417,16 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "gethostname" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e" +dependencies = [ + "rustix 1.0.3", + "windows-targets 0.52.6", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -9319,7 +9330,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" dependencies = [ - "gethostname", + "gethostname 0.4.3", "rustix 0.38.44", "x11rb-protocol", ] diff --git a/clash-verge-rev/src-tauri/Cargo.toml b/clash-verge-rev/src-tauri/Cargo.toml index 0b96e80b03..4497e0d6b5 100755 --- a/clash-verge-rev/src-tauri/Cargo.toml +++ b/clash-verge-rev/src-tauri/Cargo.toml @@ -75,6 +75,7 @@ mihomo_api = { path = "src_crates/crate_mihomo_api" } ab_glyph = "0.2.29" tungstenite = "0.26.2" libc = "0.2" +gethostname = "1.0.1" [target.'cfg(windows)'.dependencies] runas = "=1.2.0" diff --git a/clash-verge-rev/src-tauri/src/cmd/network.rs b/clash-verge-rev/src-tauri/src/cmd/network.rs index 5bc17163b6..ec2dabbf03 100644 --- a/clash-verge-rev/src-tauri/src/cmd/network.rs +++ b/clash-verge-rev/src-tauri/src/cmd/network.rs @@ -31,6 +31,25 @@ pub fn get_auto_proxy() -> CmdResult { Ok(map) } +/// 获取系统主机名 +#[tauri::command] +pub fn get_system_hostname() -> CmdResult { + use gethostname::gethostname; + + // 获取系统主机名,处理可能的非UTF-8字符 + let hostname = match gethostname().into_string() { + Ok(name) => name, + Err(os_string) => { + // 对于包含非UTF-8的主机名,使用调试格式化 + let fallback = format!("{:?}", os_string); + // 去掉可能存在的引号 + fallback.trim_matches('"').to_string() + } + }; + + Ok(hostname) +} + /// 获取网络接口列表 #[tauri::command] pub fn get_network_interfaces() -> Vec { diff --git a/clash-verge-rev/src-tauri/src/config/clash.rs b/clash-verge-rev/src-tauri/src/config/clash.rs index e2936b968d..6866cbf4bc 100644 --- a/clash-verge-rev/src-tauri/src/config/clash.rs +++ b/clash-verge-rev/src-tauri/src/config/clash.rs @@ -50,7 +50,6 @@ impl IClashTemp { map.insert("allow-lan".into(), false.into()); map.insert("ipv6".into(), true.into()); map.insert("mode".into(), "rule".into()); - map.insert("external-controller".into(), "127.0.0.1:9097".into()); #[cfg(not(target_os = "windows"))] map.insert("external-controller-unix".into(), "mihomo.sock".into()); #[cfg(target_os = "windows")] @@ -215,6 +214,10 @@ impl IClashTemp { .and_then(|value| match value.as_str() { Some(val_str) => { let val_str = val_str.trim(); + + if val_str.is_empty() { + return None; + } let val = match val_str.starts_with(':') { true => format!("127.0.0.1{val_str}"), @@ -227,11 +230,15 @@ impl IClashTemp { } None => None, }) - .unwrap_or("127.0.0.1:9097".into()) + .unwrap_or_else(|| String::new()) } pub fn guard_client_ctrl(config: &Mapping) -> String { let value = Self::guard_server_ctrl(config); + if value.is_empty() { + return value; + } + match SocketAddr::from_str(value.as_str()) { Ok(mut socket) => { if socket.ip().is_unspecified() { @@ -239,7 +246,7 @@ impl IClashTemp { } socket.to_string() } - Err(_) => "127.0.0.1:9097".into(), + Err(_) => String::new(), } } } @@ -278,12 +285,12 @@ fn test_clash_info() { assert_eq!( IClashTemp(IClashTemp::guard(Mapping::new())).get_client_info(), - get_result(7897, "127.0.0.1:9097") + get_result(7897, "") ); - assert_eq!(get_case("", ""), get_result(7897, "127.0.0.1:9097")); + assert_eq!(get_case("", ""), get_result(7897, "")); - assert_eq!(get_case(65537, ""), get_result(1, "127.0.0.1:9097")); + assert_eq!(get_case(65537, ""), get_result(1, "")); assert_eq!( get_case(8888, "127.0.0.1:8888"), @@ -292,7 +299,7 @@ fn test_clash_info() { assert_eq!( get_case(8888, " :98888 "), - get_result(8888, "127.0.0.1:9097") + get_result(8888, "") ); assert_eq!( @@ -317,7 +324,7 @@ fn test_clash_info() { assert_eq!( get_case(8888, "192.168.1.1:80800"), - get_result(8888, "127.0.0.1:9097") + get_result(8888, "") ); } diff --git a/clash-verge-rev/src-tauri/src/config/verge.rs b/clash-verge-rev/src-tauri/src/config/verge.rs index 2aa8430a24..79f0ab08b8 100644 --- a/clash-verge-rev/src-tauri/src/config/verge.rs +++ b/clash-verge-rev/src-tauri/src/config/verge.rs @@ -88,6 +88,9 @@ pub struct IVerge { /// pac script content pub pac_file_content: Option, + /// proxy host address + pub proxy_host: Option, + /// theme setting pub theme_setting: Option, @@ -275,6 +278,7 @@ impl IVerge { enable_system_proxy: Some(false), proxy_auto_config: Some(false), pac_file_content: Some(DEFAULT_PAC.into()), + proxy_host: Some("127.0.0.1".into()), enable_random_port: Some(false), #[cfg(not(target_os = "windows"))] verge_redir_port: Some(7895), @@ -368,7 +372,7 @@ impl IVerge { patch!(proxy_guard_duration); patch!(proxy_auto_config); patch!(pac_file_content); - + patch!(proxy_host); patch!(theme_setting); patch!(web_ui_list); patch!(clash_core); @@ -452,6 +456,7 @@ pub struct IVergeResponse { pub proxy_guard_duration: Option, pub proxy_auto_config: Option, pub pac_file_content: Option, + pub proxy_host: Option, pub theme_setting: Option, pub web_ui_list: Option>, pub clash_core: Option, @@ -520,6 +525,7 @@ impl From for IVergeResponse { proxy_guard_duration: verge.proxy_guard_duration, proxy_auto_config: verge.proxy_auto_config, pac_file_content: verge.pac_file_content, + proxy_host: verge.proxy_host, theme_setting: verge.theme_setting, web_ui_list: verge.web_ui_list, clash_core: verge.clash_core, diff --git a/clash-verge-rev/src-tauri/src/core/sysopt.rs b/clash-verge-rev/src-tauri/src/core/sysopt.rs index 9f31a4726c..173535f656 100644 --- a/clash-verge-rev/src-tauri/src/core/sysopt.rs +++ b/clash-verge-rev/src-tauri/src/core/sysopt.rs @@ -76,12 +76,13 @@ impl Sysopt { .unwrap_or(Config::clash().data().get_mixed_port()); let pac_port = IVerge::get_singleton_port(); - let (sys_enable, pac_enable) = { + let (sys_enable, pac_enable, proxy_host) = { let verge = Config::verge(); let verge = verge.latest(); ( verge.enable_system_proxy.unwrap_or(false), verge.proxy_auto_config.unwrap_or(false), + verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")), ) }; @@ -89,13 +90,13 @@ impl Sysopt { { let mut sys = Sysproxy { enable: false, - host: String::from("127.0.0.1"), + host: proxy_host.clone(), port, bypass: get_bypass(), }; let mut auto = Autoproxy { enable: false, - url: format!("http://127.0.0.1:{pac_port}/commands/pac"), + url: format!("http://{}:{}/commands/pac", proxy_host, pac_port), }; if !sys_enable { @@ -139,7 +140,7 @@ impl Sysopt { let shell = app_handle.shell(); let output = if pac_enable { - let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port); + let address = format!("http://{}:{}/commands/pac", proxy_host, pac_port); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) .args(["pac", address.as_str()]) @@ -148,7 +149,7 @@ impl Sysopt { .unwrap(); output } else { - let address = format!("{}:{}", "127.0.0.1", port); + let address = format!("{}:{}", proxy_host, port); let bypass = get_bypass(); let output = shell .command(sysproxy_exe.as_path().to_str().unwrap()) @@ -256,7 +257,7 @@ impl Sysopt { loop { sleep(Duration::from_secs(wait_secs)).await; - let (enable, guard, guard_duration, pac) = { + let (enable, guard, guard_duration, pac, proxy_host) = { let verge = Config::verge(); let verge = verge.latest(); ( @@ -264,6 +265,7 @@ impl Sysopt { verge.enable_proxy_guard.unwrap_or(false), verge.proxy_guard_duration.unwrap_or(10), verge.proxy_auto_config.unwrap_or(false), + verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")), ) }; @@ -303,13 +305,13 @@ impl Sysopt { if pac { let autoproxy = Autoproxy { enable: true, - url: format!("http://127.0.0.1:{pac_port}/commands/pac"), + url: format!("http://{}:{}/commands/pac", proxy_host, pac_port), }; logging_error!(Type::System, true, autoproxy.set_auto_proxy()); } else { let sysproxy = Sysproxy { enable: true, - host: "127.0.0.1".into(), + host: proxy_host.into(), port, bypass: get_bypass(), }; @@ -333,7 +335,7 @@ impl Sysopt { let shell = app_handle.shell(); let output = if pac { - let address = format!("http://{}:{}/commands/pac", "127.0.0.1", pac_port); + let address = format!("http://{}:{}/commands/pac", proxy_host, pac_port); shell .command(sysproxy_exe.as_path().to_str().unwrap()) @@ -342,7 +344,7 @@ impl Sysopt { .await .unwrap() } else { - let address = format!("{}:{}", "127.0.0.1", port); + let address = format!("{}:{}", proxy_host, port); let bypass = get_bypass(); shell diff --git a/clash-verge-rev/src-tauri/src/feat/proxy.rs b/clash-verge-rev/src-tauri/src/feat/proxy.rs index 5e24c3a071..a7f22f092a 100644 --- a/clash-verge-rev/src-tauri/src/feat/proxy.rs +++ b/clash-verge-rev/src-tauri/src/feat/proxy.rs @@ -63,9 +63,14 @@ pub fn toggle_tun_mode(not_save_file: Option) { /// Copy proxy environment variables to clipboard pub fn copy_clash_env() { - // 从环境变量获取IP地址,默认127.0.0.1 - let clash_verge_rev_ip = - env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| "127.0.0.1".to_string()); + // 从环境变量获取IP地址,如果没有则从配置中获取 proxy_host,默认为 127.0.0.1 + let clash_verge_rev_ip = env::var("CLASH_VERGE_REV_IP").unwrap_or_else(|_| { + Config::verge() + .latest() + .proxy_host + .clone() + .unwrap_or_else(|| "127.0.0.1".to_string()) + }); let app_handle = handle::Handle::global().app_handle().unwrap(); let port = { Config::verge().latest().verge_mixed_port.unwrap_or(7897) }; diff --git a/clash-verge-rev/src-tauri/src/lib.rs b/clash-verge-rev/src-tauri/src/lib.rs index 33acf0a4ac..e30a7aa510 100644 --- a/clash-verge-rev/src-tauri/src/lib.rs +++ b/clash-verge-rev/src-tauri/src/lib.rs @@ -156,6 +156,7 @@ pub fn run() { cmd::open_core_dir, cmd::get_portable_flag, cmd::get_network_interfaces, + cmd::get_system_hostname, cmd::restart_core, cmd::restart_app, // 添加新的命令 diff --git a/clash-verge-rev/src-tauri/src/module/mihomo.rs b/clash-verge-rev/src-tauri/src/module/mihomo.rs index f7e9060c2d..ea029f717b 100644 --- a/clash-verge-rev/src-tauri/src/module/mihomo.rs +++ b/clash-verge-rev/src-tauri/src/module/mihomo.rs @@ -1,8 +1,12 @@ -use crate::{config::Config, utils::dirs::app_socket_path}; +use crate::config::Config; +#[cfg(unix)] +use crate::utils::dirs::app_socket_path; use mihomo_api; use once_cell::sync::{Lazy, OnceCell}; use std::sync::Mutex; -use tauri::http::{HeaderMap, HeaderValue}; +use tauri::http::HeaderMap; +#[cfg(target_os = "macos")] +use tauri::http::HeaderValue; #[cfg(target_os = "macos")] use tokio_tungstenite::tungstenite::http; @@ -70,7 +74,7 @@ impl MihomoManager { #[cfg(unix)] let socket_path = app_socket_path().unwrap(); #[cfg(windows)] - let socket_path = r"\\.\pipe\mihomo"; + let socket_path = r"\\.\pipe\mihomo".to_string(); socket_path } } diff --git a/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs b/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs index fecdbebbad..dbccca0994 100644 --- a/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs +++ b/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/unix.rs @@ -36,21 +36,28 @@ impl UnixClient { ) -> Result { let uri = self.generate_unix_path(socket_path.as_str(), path).await; - let mut request_builder = Request::builder().method(method).uri(uri); + let mut request_builder = Request::builder().method(method.clone()).uri(uri); let body_bytes = if let Some(body) = body { - request_builder = request_builder.header( - HeaderName::from_static("Content-Type"), - HeaderValue::from_static("application/json"), - ); + if method != Method::PUT { + request_builder = request_builder.header( + HeaderName::from_static("Content-Type"), + HeaderValue::from_static("application/json"), + ); + } Bytes::from(serde_json::to_vec(&body)?) } else { Bytes::new() }; let request = request_builder.body(Full::new(body_bytes))?; - let response = self.client.lock().await.request(request).await?; + + if method == Method::PUT { + let json_value = serde_json::Value::Null; + return Ok(json_value); + } + let body_bytes = response.into_body().collect().await?.to_bytes(); let json_value = serde_json::from_slice(&body_bytes)?; diff --git a/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs b/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs index 956d5df3f9..7c820fbc34 100644 --- a/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs +++ b/clash-verge-rev/src-tauri/src_crates/crate_mihomo_api/src/platform/windows.rs @@ -1,23 +1,18 @@ -use crate::{model::E, sock}; +use crate::model::E; use hyper::Method; use serde_json::Value; use tokio_util::codec::{Framed, LinesCodec}; -use std::{sync::Arc, time::Duration}; -use tokio::{ - time::timeout, - sync::Mutex, -}; +use std::time::Duration; +use tokio::time::timeout; use futures::{SinkExt, StreamExt}; use tokio::net::windows::named_pipe::ClientOptions; pub struct WindowsClient { - lock: Arc>, } impl WindowsClient { pub fn new() -> Self { Self { - lock: Arc::new(Mutex::new(())), } } diff --git a/clash-verge-rev/src/components/setting/mods/controller-viewer.tsx b/clash-verge-rev/src/components/setting/mods/controller-viewer.tsx index dfb5067930..dea639bab8 100644 --- a/clash-verge-rev/src/components/setting/mods/controller-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/controller-viewer.tsx @@ -3,8 +3,9 @@ import { useLockFn } from "ahooks"; import { useTranslation } from "react-i18next"; import { List, ListItem, ListItemText, TextField, Typography, Box } from "@mui/material"; import { useClashInfo } from "@/hooks/use-clash"; -import { BaseDialog, DialogRef, Notice, Switch } from "@/components/base"; +import { BaseDialog, DialogRef, Notice } from "@/components/base"; import { useVerge } from "@/hooks/use-verge"; +import { useClash } from "@/hooks/use-clash"; export const ControllerViewer = forwardRef((props, ref) => { const { t } = useTranslation(); @@ -12,43 +13,39 @@ export const ControllerViewer = forwardRef((props, ref) => { const { clashInfo, patchInfo } = useClashInfo(); const { verge, patchVerge } = useVerge(); + const { clash } = useClash(); - const [controller, setController] = useState(clashInfo?.server || ""); - const [secret, setSecret] = useState(clashInfo?.secret || ""); + const [controller, setController] = useState(""); + const [secret, setSecret] = useState(""); - // 获取外部控制器开关状态 - const [enableController, setEnableController] = useState(() => { - const savedState = localStorage.getItem("enable_external_controller"); - if (savedState !== null) { - return savedState === "true"; - } - return verge?.enable_external_controller ?? true; - }); + const enableController = Boolean(clash?.["external-controller"] && clash?.["external-controller"] !== ""); useImperativeHandle(ref, () => ({ open: () => { setOpen(true); - setController(clashInfo?.server || ""); - setSecret(clashInfo?.secret || ""); - // 从localStorage更新开关状态 - const savedState = localStorage.getItem("enable_external_controller"); - if (savedState !== null) { - setEnableController(savedState === "true"); - } else { - setEnableController(verge?.enable_external_controller ?? true); - } + setController(clash?.["external-controller"] || ""); + setSecret(clash?.secret || ""); }, close: () => setOpen(false), })); const onSave = useLockFn(async () => { try { - // 只有在启用外部控制器时才更新配置 - if (enableController) { - await patchInfo({ "external-controller": controller, secret }); - } - Notice.success(t("External Controller Settings Saved"), 1000); setOpen(false); + const promises = []; + promises.push( + patchInfo({ + "external-controller": controller || "127.0.0.1:9097", + secret + }) + ); + + // 同步verge配置 + if (controller && controller !== "") { + promises.push(patchVerge({ enable_external_controller: true })); + } + await Promise.all(promises); + Notice.success(t("External Controller Settings Saved"), 1000); } catch (err: any) { Notice.error(err.message || err.toString(), 4000); } @@ -61,6 +58,7 @@ export const ControllerViewer = forwardRef((props, ref) => { contentSx={{ width: 400 }} okBtn={t("Save")} cancelBtn={t("Cancel")} + disableOk={!enableController} onClose={() => setOpen(false)} onCancel={() => setOpen(false)} onOk={onSave} @@ -81,7 +79,7 @@ export const ControllerViewer = forwardRef((props, ref) => { size="small" sx={{ width: 175 }} value={controller} - placeholder="Required" + placeholder="127.0.0.1:9097" onChange={(e) => setController(e.target.value)} disabled={!enableController} /> diff --git a/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx b/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx index b4063cf61e..b8c44514e3 100644 --- a/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx +++ b/clash-verge-rev/src/components/setting/mods/sysproxy-viewer.tsx @@ -3,10 +3,17 @@ import { BaseFieldset } from "@/components/base/base-fieldset"; import { TooltipIcon } from "@/components/base/base-tooltip-icon"; import { EditorViewer } from "@/components/profile/editor-viewer"; import { useVerge } from "@/hooks/use-verge"; -import { getAutotemProxy, getSystemProxy } from "@/services/cmds"; +import { + getAutotemProxy, + getNetworkInterfaces, + getNetworkInterfacesInfo, + getSystemHostname, + getSystemProxy, +} from "@/services/cmds"; import getSystem from "@/utils/get-system"; import { EditRounded } from "@mui/icons-material"; import { + Autocomplete, Button, InputAdornment, List, @@ -17,10 +24,16 @@ import { Typography, } from "@mui/material"; import { useLockFn } from "ahooks"; -import { forwardRef, useImperativeHandle, useMemo, useState } from "react"; +import { + forwardRef, + useImperativeHandle, + useEffect, + useMemo, + useState, +} from "react"; import { useTranslation } from "react-i18next"; const DEFAULT_PAC = `function FindProxyForURL(url, host) { - return "PROXY 127.0.0.1:%mixed-port%; SOCKS5 127.0.0.1:%mixed-port%; DIRECT;"; + return "PROXY %proxy_host%:%mixed-port%; SOCKS5 %proxy_host%:%mixed-port%; DIRECT;"; }`; /** NO_PROXY validation */ @@ -64,6 +77,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { const [open, setOpen] = useState(false); const [editorOpen, setEditorOpen] = useState(false); const { verge, patchVerge } = useVerge(); + const [hostOptions, setHostOptions] = useState([]); type SysProxy = Awaited>; const [sysproxy, setSysproxy] = useState(); @@ -79,6 +93,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default_bypass, system_proxy_bypass, proxy_guard_duration, + proxy_host, } = verge ?? {}; const [value, setValue] = useState({ @@ -88,6 +103,7 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default: use_default_bypass ?? true, pac: proxy_auto_config, pac_content: pac_file_content ?? DEFAULT_PAC, + proxy_host: proxy_host ?? "127.0.0.1", }); const defaultBypass = () => { @@ -110,13 +126,74 @@ export const SysproxyViewer = forwardRef((props, ref) => { use_default: use_default_bypass ?? true, pac: proxy_auto_config, pac_content: pac_file_content ?? DEFAULT_PAC, + proxy_host: proxy_host ?? "127.0.0.1", }); getSystemProxy().then((p) => setSysproxy(p)); getAutotemProxy().then((p) => setAutoproxy(p)); + fetchNetworkInterfaces(); }, close: () => setOpen(false), })); + // 获取网络接口和主机名 + const fetchNetworkInterfaces = async () => { + try { + // 获取系统网络接口信息 + const interfaces = await getNetworkInterfacesInfo(); + const ipAddresses: string[] = []; + + // 从interfaces中提取IPv4和IPv6地址 + interfaces.forEach((iface) => { + iface.addr.forEach((address) => { + if (address.V4 && address.V4.ip) { + ipAddresses.push(address.V4.ip); + } + if (address.V6 && address.V6.ip) { + ipAddresses.push(address.V6.ip); + } + }); + }); + + // 获取当前系统的主机名 + let hostname = ""; + try { + hostname = await getSystemHostname(); + console.log("获取到主机名:", hostname); + } catch (err) { + console.error("获取主机名失败:", err); + } + + // 构建选项列表 + const options = ["127.0.0.1", "localhost"]; + + // 确保主机名添加到列表,即使它是空字符串也记录下来 + if (hostname) { + // 如果主机名不是localhost或127.0.0.1,则添加它 + if (hostname !== "localhost" && hostname !== "127.0.0.1") { + hostname = hostname + ".local"; + options.push(hostname); + console.log("主机名已添加到选项中:", hostname); + } else { + console.log("主机名与已有选项重复:", hostname); + } + } else { + console.log("主机名为空"); + } + + // 添加IP地址 + options.push(...ipAddresses); + + // 去重 + const uniqueOptions = Array.from(new Set(options)); + console.log("最终选项列表:", uniqueOptions); + setHostOptions(uniqueOptions); + } catch (error) { + console.error("获取网络接口失败:", error); + // 失败时至少提供基本选项 + setHostOptions(["127.0.0.1", "localhost"]); + } + }; + const onSave = useLockFn(async () => { if (value.duration < 1) { Notice.error(t("Proxy Daemon Duration Cannot be Less than 1 Second")); @@ -127,6 +204,23 @@ export const SysproxyViewer = forwardRef((props, ref) => { return; } + // 修改验证规则,允许IP和主机名 + const ipv4Regex = + /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + const hostnameRegex = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + if ( + !ipv4Regex.test(value.proxy_host) && + !ipv6Regex.test(value.proxy_host) && + !hostnameRegex.test(value.proxy_host) + ) { + Notice.error(t("Invalid Proxy Host Format")); + return; + } + const patch: Partial = {}; if (value.guard !== enable_proxy_guard) { @@ -138,15 +232,34 @@ export const SysproxyViewer = forwardRef((props, ref) => { if (value.bypass !== system_proxy_bypass) { patch.system_proxy_bypass = value.bypass; } - if (value.pac !== proxy_auto_config) { patch.proxy_auto_config = value.pac; } if (value.use_default !== use_default_bypass) { patch.use_default_bypass = value.use_default; } - if (value.pac_content !== pac_file_content) { - patch.pac_file_content = value.pac_content; + + let pacContent = value.pac_content; + if (pacContent) { + pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host); + } + + if (pacContent !== pac_file_content) { + patch.pac_file_content = pacContent; + } + + // 处理IPv6地址,如果是IPv6地址但没有被方括号包围,则添加方括号 + let proxyHost = value.proxy_host; + if ( + ipv6Regex.test(proxyHost) && + !proxyHost.startsWith("[") && + !proxyHost.endsWith("]") + ) { + proxyHost = `[${proxyHost}]`; + } + + if (proxyHost !== proxy_host) { + patch.proxy_host = proxyHost; } try { @@ -199,6 +312,31 @@ export const SysproxyViewer = forwardRef((props, ref) => { )} + + + ( + + )} + onChange={(_, newValue) => { + setValue((v) => ({ + ...v, + proxy_host: newValue || "127.0.0.1", + })); + }} + onInputChange={(_, newInputValue) => { + setValue((v) => ({ + ...v, + proxy_host: newInputValue || "127.0.0.1", + })); + }} + /> + { "log-level": logLevel, "unified-delay": unifiedDelay, dns, + "external-controller": externalController } = clash ?? {}; const { enable_random_port = false, verge_mixed_port } = verge ?? {}; @@ -59,15 +60,6 @@ const SettingClash = ({ onError }: Props) => { return verge?.enable_dns_settings ?? false; }); - // 添加外部控制器开关状态 - const [enableController, setEnableController] = useState(() => { - const savedState = localStorage.getItem("enable_external_controller"); - if (savedState !== null) { - return savedState === "true"; - } - return verge?.enable_external_controller ?? true; - }); - const { addListener } = useListen(); const webRef = useRef(null); @@ -118,25 +110,14 @@ const SettingClash = ({ onError }: Props) => { } }); - // 处理外部控制器开关状态变化 - const handleControllerToggle = useLockFn(async (enable: boolean) => { - try { - setEnableController(enable); - localStorage.setItem("enable_external_controller", String(enable)); - await patchVerge({ enable_external_controller: enable }); - if (!enable) { - await patchInfo({ "external-controller": "", secret: "" }); - } else { - // 如果开启,恢复默认值或之前的值 - const server = clashInfo?.server || "127.0.0.1:9097"; - await patchInfo({ "external-controller": server, secret: clashInfo?.secret || "" }); - } - } catch (err: any) { - setEnableController(!enable); - localStorage.setItem("enable_external_controller", String(!enable)); - Notice.error(err.message || err.toString()); + // 同步外部控制器配置和开关状态 + useEffect(() => { + const hasController = Boolean(externalController && externalController !== ""); + const isEnabled = Boolean(verge?.enable_external_controller); + if (hasController !== isEnabled) { + patchVerge({ enable_external_controller: hasController }); } - }); + }, [externalController, verge?.enable_external_controller]); return ( @@ -286,26 +267,49 @@ const SettingClash = ({ onError }: Props) => { /> } > - handleControllerToggle(checked)} - /> + { + onChangeVerge({ enable_external_controller: e }); + onChangeData({ + "external-controller": e ? (externalController || "127.0.0.1:9097") : "" + }); + }} + onGuard={async (e) => { + const promises = [ + patchVerge({ enable_external_controller: e }) + ]; + + if (!e) { + // 如果禁用,清空配置 + promises.push(patchClash({ "external-controller": "" })); + } else if (!externalController || externalController === "") { + promises.push(patchClash({ "external-controller": "127.0.0.1:9097" })); + } + await Promise.all(promises); + }} + > + + webRef.current?.open() : undefined} + onClick={(externalController && externalController !== "") ? () => webRef.current?.open() : undefined} label={ {t("Web UI")} } extra={ - !enableController && ( + (!externalController || externalController === "") && ( ("get_network_interfaces"); } +export async function getSystemHostname() { + return invoke("get_system_hostname"); +} + export async function getNetworkInterfacesInfo() { return invoke("get_network_interfaces_info"); } diff --git a/clash-verge-rev/src/services/types.d.ts b/clash-verge-rev/src/services/types.d.ts index d38a173721..16219e4622 100644 --- a/clash-verge-rev/src/services/types.d.ts +++ b/clash-verge-rev/src/services/types.d.ts @@ -750,6 +750,7 @@ interface IVergeConfig { enable_external_controller?: boolean; proxy_auto_config?: boolean; pac_file_content?: string; + proxy_host?: string; enable_random_port?: boolean; verge_mixed_port?: number; verge_socks_port?: number; diff --git a/lede/package/kernel/linux/modules/netdevices.mk b/lede/package/kernel/linux/modules/netdevices.mk index 47192410ff..3c4f586c1a 100644 --- a/lede/package/kernel/linux/modules/netdevices.mk +++ b/lede/package/kernel/linux/modules/netdevices.mk @@ -588,7 +588,7 @@ $(eval $(call KernelPackage,dsa-realtek)) define KernelPackage/dsa-rtl8366rb SUBMENU:=$(NETWORK_DEVICES_MENU) - TITLE:=Realtek RTL8365MB switch DSA support + TITLE:=Realtek RTL8366RB switch DSA support DEPENDS:=+kmod-dsa-realtek @!TARGET_x86 @!TARGET_bcm47xx @!TARGET_uml KCONFIG:= \ CONFIG_NET_DSA_REALTEK_RTL8366RB \ diff --git a/lede/package/lean/default-settings/files/zzz-default-settings b/lede/package/lean/default-settings/files/zzz-default-settings index e613760fe8..ab42154566 100755 --- a/lede/package/lean/default-settings/files/zzz-default-settings +++ b/lede/package/lean/default-settings/files/zzz-default-settings @@ -51,7 +51,7 @@ sed -i '/option disabled/d' /etc/config/wireless sed -i '/set wireless.radio${devidx}.disabled/d' /lib/wifi/mac80211.sh sed -i '/DISTRIB_REVISION/d' /etc/openwrt_release -echo "DISTRIB_REVISION='R25.3.15'" >> /etc/openwrt_release +echo "DISTRIB_REVISION='R25.4.145'" >> /etc/openwrt_release sed -i '/DISTRIB_DESCRIPTION/d' /etc/openwrt_release echo "DISTRIB_DESCRIPTION='LEDE '" >> /etc/openwrt_release diff --git a/mihomo/adapter/outbound/vless.go b/mihomo/adapter/outbound/vless.go index d4444cb0c5..cfb39e99d6 100644 --- a/mihomo/adapter/outbound/vless.go +++ b/mihomo/adapter/outbound/vless.go @@ -600,10 +600,13 @@ func NewVless(option VlessOption) (*Vless, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, - }) + }, v.option.Fingerprint) + if err != nil { + return nil, err + } if option.ServerName == "" { host, _, _ := net.SplitHostPort(v.addr) tlsConfig.ServerName = host diff --git a/mihomo/adapter/outbound/vmess.go b/mihomo/adapter/outbound/vmess.go index 285fdc8e47..35aacb7860 100644 --- a/mihomo/adapter/outbound/vmess.go +++ b/mihomo/adapter/outbound/vmess.go @@ -511,10 +511,13 @@ func NewVmess(option VmessOption) (*Vmess, error) { } var tlsConfig *tls.Config if option.TLS { - tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ + tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{ InsecureSkipVerify: v.option.SkipCertVerify, ServerName: v.option.ServerName, - }) + }, v.option.Fingerprint) + if err != nil { + return nil, err + } if option.ServerName == "" { host, _, _ := net.SplitHostPort(v.addr) tlsConfig.ServerName = host diff --git a/mihomo/common/net/tls.go b/mihomo/common/net/tls.go index b28655031e..77c0d7ca39 100644 --- a/mihomo/common/net/tls.go +++ b/mihomo/common/net/tls.go @@ -3,8 +3,10 @@ package net import ( "crypto/rand" "crypto/rsa" + "crypto/sha256" "crypto/tls" "crypto/x509" + "encoding/hex" "encoding/pem" "fmt" "math/big" @@ -16,7 +18,11 @@ type Path interface { func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) { if certificate == "" && privateKey == "" { - return newRandomTLSKeyPair() + var err error + certificate, privateKey, _, err = NewRandomTLSKeyPair() + if err != nil { + return tls.Certificate{}, err + } } cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) if painTextErr == nil { @@ -32,10 +38,10 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro return cert, nil } -func newRandomTLSKeyPair() (tls.Certificate, error) { +func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) { key, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { - return tls.Certificate{}, err + return } template := x509.Certificate{SerialNumber: big.NewInt(1)} certDER, err := x509.CreateCertificate( @@ -45,14 +51,15 @@ func newRandomTLSKeyPair() (tls.Certificate, error) { &key.PublicKey, key) if err != nil { - return tls.Certificate{}, err + return } - keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) - certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) - - tlsCert, err := tls.X509KeyPair(certPEM, keyPEM) + cert, err := x509.ParseCertificate(certDER) if err != nil { - return tls.Certificate{}, err + return } - return tlsCert, nil + hash := sha256.Sum256(cert.Raw) + fingerprint = hex.EncodeToString(hash[:]) + privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})) + certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})) + return } diff --git a/mihomo/common/observable/observable.go b/mihomo/common/observable/observable.go index 62b2e1537e..1e45cc66e1 100644 --- a/mihomo/common/observable/observable.go +++ b/mihomo/common/observable/observable.go @@ -10,6 +10,7 @@ type Observable[T any] struct { listener map[Subscription[T]]*Subscriber[T] mux sync.Mutex done bool + stopCh chan struct{} } func (o *Observable[T]) process() { @@ -31,6 +32,7 @@ func (o *Observable[T]) close() { for _, sub := range o.listener { sub.Close() } + close(o.stopCh) } func (o *Observable[T]) Subscribe() (Subscription[T], error) { @@ -59,6 +61,7 @@ func NewObservable[T any](iter Iterable[T]) *Observable[T] { observable := &Observable[T]{ iterable: iter, listener: map[Subscription[T]]*Subscriber[T]{}, + stopCh: make(chan struct{}), } go observable.process() return observable diff --git a/mihomo/common/observable/observable_test.go b/mihomo/common/observable/observable_test.go index d263cb9403..6be7f3aacc 100644 --- a/mihomo/common/observable/observable_test.go +++ b/mihomo/common/observable/observable_test.go @@ -70,9 +70,11 @@ func TestObservable_SubscribeClosedSource(t *testing.T) { src := NewObservable[int](iter) data, _ := src.Subscribe() <-data - - _, closed := src.Subscribe() - assert.NotNil(t, closed) + select { + case <-src.stopCh: + case <-time.After(time.Second): + assert.Fail(t, "timeout not stop") + } } func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { diff --git a/mihomo/common/singledo/singledo_test.go b/mihomo/common/singledo/singledo_test.go index 2f92f0ae0f..f27650fb35 100644 --- a/mihomo/common/singledo/singledo_test.go +++ b/mihomo/common/singledo/singledo_test.go @@ -11,12 +11,13 @@ import ( ) func TestBasic(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 shardCount := atomic.NewInt32(0) call := func() (int, error) { foo++ - time.Sleep(time.Millisecond * 5) + time.Sleep(time.Millisecond * 20) return 0, nil } @@ -39,7 +40,8 @@ func TestBasic(t *testing.T) { } func TestTimer(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 callM := func() (int, error) { foo++ @@ -47,7 +49,7 @@ func TestTimer(t *testing.T) { } _, _, _ = single.Do(callM) - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) _, _, shard := single.Do(callM) assert.Equal(t, 1, foo) @@ -55,7 +57,8 @@ func TestTimer(t *testing.T) { } func TestReset(t *testing.T) { - single := NewSingle[int](time.Millisecond * 30) + t.Parallel() + single := NewSingle[int](time.Millisecond * 200) foo := 0 callM := func() (int, error) { foo++ diff --git a/mihomo/component/http/http.go b/mihomo/component/http/http.go index 63ea5be7bc..a2c44d85c3 100644 --- a/mihomo/component/http/http.go +++ b/mihomo/component/http/http.go @@ -69,7 +69,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { - if conn, err := inner.HandleTcp(address, specialProxy); err == nil { + if conn, err := inner.HandleTcp(inner.GetTunnel(), address, specialProxy); err == nil { return conn, nil } else { return dialer.DialContext(ctx, network, address) diff --git a/mihomo/go.mod b/mihomo/go.mod index 2a64051961..f40e0b89d8 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -20,7 +20,7 @@ require ( github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab github.com/metacubex/bart v0.19.0 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 - github.com/metacubex/chacha v0.1.1 + github.com/metacubex/chacha v0.1.2 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 github.com/metacubex/randv2 v0.2.0 diff --git a/mihomo/go.sum b/mihomo/go.sum index 8c2810266b..c7e852b004 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= -github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c= -github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= +github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc= +github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI= diff --git a/mihomo/listener/http/server.go b/mihomo/listener/http/server.go index e32b55dde3..3c1eacdd51 100644 --- a/mihomo/listener/http/server.go +++ b/mihomo/listener/http/server.go @@ -78,7 +78,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/listener/inbound/common_test.go b/mihomo/listener/inbound/common_test.go new file mode 100644 index 0000000000..29d0456687 --- /dev/null +++ b/mihomo/listener/inbound/common_test.go @@ -0,0 +1,232 @@ +package inbound_test + +import ( + "context" + "crypto/rand" + "crypto/tls" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/netip" + "sync" + "testing" + "time" + + N "github.com/metacubex/mihomo/common/net" + "github.com/metacubex/mihomo/component/ca" + "github.com/metacubex/mihomo/component/generater" + C "github.com/metacubex/mihomo/constant" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/stretchr/testify/assert" +) + +var httpPath = "/inbound_test" +var httpData = make([]byte, 10240) +var remoteAddr = netip.MustParseAddr("1.2.3.4") +var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair() +var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey)) +var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}} +var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "") +var realityPrivateKey, realityPublickey string +var realityDest = "itunes.apple.com" +var realityShortid = "10f897e26c4b9478" + +func init() { + rand.Read(httpData) + privateKey, err := generater.GeneratePrivateKey() + if err != nil { + panic(err) + } + publicKey := privateKey.PublicKey() + realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:]) + realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:]) +} + +type TestTunnel struct { + HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata) + HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata) + NatTableFn func() C.NatTable + CloseFn func() error + DoTestFn func(t *testing.T, proxy C.ProxyAdapter) +} + +func (tt *TestTunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) { + tt.HandleTCPConnFn(conn, metadata) +} + +func (tt *TestTunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) { + tt.HandleUDPPacketFn(packet, metadata) +} + +func (tt *TestTunnel) NatTable() C.NatTable { + return tt.NatTableFn() +} + +func (tt *TestTunnel) Close() error { + return tt.CloseFn() +} + +func (tt *TestTunnel) DoTest(t *testing.T, proxy C.ProxyAdapter) { + tt.DoTestFn(t, proxy) +} + +type TestTunnelListener struct { + ch chan net.Conn + ctx context.Context + cancel context.CancelFunc + addr net.Addr +} + +func (t *TestTunnelListener) Accept() (net.Conn, error) { + select { + case conn, ok := <-t.ch: + if !ok { + return nil, net.ErrClosed + } + return conn, nil + case <-t.ctx.Done(): + return nil, t.ctx.Err() + } +} + +func (t *TestTunnelListener) Close() error { + t.cancel() + return nil +} + +func (t *TestTunnelListener) Addr() net.Addr { + return t.addr +} + +type WaitCloseConn struct { + net.Conn + ch chan struct{} + once sync.Once +} + +func (c *WaitCloseConn) Close() error { + err := c.Conn.Close() + c.once.Do(func() { + close(c.ch) + }) + return err +} + +var _ C.Tunnel = (*TestTunnel)(nil) +var _ net.Listener = (*TestTunnelListener)(nil) + +func NewHttpTestTunnel() *TestTunnel { + ctx, cancel := context.WithCancel(context.Background()) + ln := &TestTunnelListener{ch: make(chan net.Conn), ctx: ctx, cancel: cancel, addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(remoteAddr, 0))} + + r := chi.NewRouter() + r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) { + render.Data(w, r, httpData) + }) + go http.Serve(ln, r) + testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil) + assert.NoError(t, err) + req = req.WithContext(ctx) + + var dstPort uint16 = 80 + if proto == "https" { + dstPort = 443 + } + metadata := &C.Metadata{ + NetWork: C.TCP, + DstIP: remoteAddr, + DstPort: dstPort, + } + instance, err := proxy.DialContext(ctx, metadata) + assert.NoError(t, err) + defer instance.Close() + + transport := &http.Transport{ + DialContext: func(context.Context, string, string) (net.Conn, error) { + return instance, nil + }, + // from http.DefaultTransport + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + // for our self-signed cert + TLSClientConfig: tlsClientConfig.Clone(), + // open http2 + ForceAttemptHTTP2: true, + } + + client := http.Client{ + Timeout: 30 * time.Second, + Transport: transport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + + defer client.CloseIdleConnections() + + resp, err := client.Do(req) + assert.NoError(t, err) + + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + data, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + assert.Equal(t, httpData, data) + } + tunnel := &TestTunnel{ + HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) { + defer conn.Close() + if metadata.DstIP != remoteAddr && metadata.Host != realityDest { + return // not match, just return + } + c := &WaitCloseConn{ + Conn: conn, + ch: make(chan struct{}), + } + if metadata.DstPort == 443 { + tlsConn := tls.Server(c, tlsConfig.Clone()) + if metadata.Host == realityDest { // ignore the tls handshake error for realityDest + ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout) + defer cancel() + if err := tlsConn.HandshakeContext(ctx); err != nil { + return + } + } + ln.ch <- tlsConn + } else { + ln.ch <- c + } + <-c.ch + }, + CloseFn: ln.Close, + DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) { + wg := sync.WaitGroup{} + num := 50 + for i := 0; i < num; i++ { + wg.Add(1) + go func() { + testFn(t, proxy, "https") + defer wg.Done() + }() + } + for i := 0; i < num; i++ { + wg.Add(1) + go func() { + testFn(t, proxy, "http") + defer wg.Done() + }() + } + wg.Wait() + }, + } + return tunnel +} diff --git a/mihomo/listener/inbound/shadowsocks_test.go b/mihomo/listener/inbound/shadowsocks_test.go new file mode 100644 index 0000000000..044898a346 --- /dev/null +++ b/mihomo/listener/inbound/shadowsocks_test.go @@ -0,0 +1,85 @@ +package inbound_test + +import ( + "crypto/rand" + "encoding/base64" + "net/netip" + "strings" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/listener/inbound" + + shadowsocks "github.com/metacubex/sing-shadowsocks" + "github.com/metacubex/sing-shadowsocks/shadowaead" + "github.com/metacubex/sing-shadowsocks/shadowaead_2022" + "github.com/metacubex/sing-shadowsocks/shadowstream" + "github.com/stretchr/testify/assert" +) + +var shadowsocksCipherList = []string{shadowsocks.MethodNone} +var shadowsocksPassword32 string +var shadowsocksPassword16 string + +func init() { + shadowsocksCipherList = append(shadowsocksCipherList, shadowaead.List...) + shadowsocksCipherList = append(shadowsocksCipherList, shadowaead_2022.List...) + shadowsocksCipherList = append(shadowsocksCipherList, shadowstream.List...) + passwordBytes := make([]byte, 32) + rand.Read(passwordBytes) + shadowsocksPassword32 = base64.StdEncoding.EncodeToString(passwordBytes) + shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16]) +} + +func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { + for _, cipher := range shadowsocksCipherList { + t.Run(cipher, func(t *testing.T) { + t.Parallel() + inboundOptions.Cipher = cipher + outboundOptions.Cipher = cipher + testInboundShadowSocks0(t, inboundOptions, outboundOptions) + }) + } +} + +func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) { + password := shadowsocksPassword32 + if strings.Contains(inboundOptions.Cipher, "-128-") { + password = shadowsocksPassword16 + } + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "shadowsocks_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Password = password + in, err := inbound.NewShadowSocks(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "shadowsocks_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.Password = password + + out, err := outbound.NewShadowSocks(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundShadowSocks_Basic(t *testing.T) { + inboundOptions := inbound.ShadowSocksOption{} + outboundOptions := outbound.ShadowSocksOption{} + testInboundShadowSocks(t, inboundOptions, outboundOptions) +} diff --git a/mihomo/listener/inbound/trojan_test.go b/mihomo/listener/inbound/trojan_test.go new file mode 100644 index 0000000000..ad35b88198 --- /dev/null +++ b/mihomo/listener/inbound/trojan_test.go @@ -0,0 +1,210 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "trojan_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.TrojanUser{ + {Username: "test", Password: userUUID}, + } + in, err := inbound.NewTrojan(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "trojan_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.Password = userUUID + + out, err := outbound.NewTrojan(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundTrojan_Tls(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss1(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss2(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Grpc1(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Grpc2(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Reality(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.TrojanOption{ + SNI: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.TrojanOption{ + SNI: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Tls_TrojanSS(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + SSOption: inbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + SSOpts: outbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} + +func TestInboundTrojan_Wss_TrojanSS(t *testing.T) { + inboundOptions := inbound.TrojanOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + SSOption: inbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + WsPath: "/ws", + } + outboundOptions := outbound.TrojanOption{ + Fingerprint: tlsFingerprint, + SSOpts: outbound.TrojanSSOption{ + Enabled: true, + Method: "", + Password: "password", + }, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundTrojan(t, inboundOptions, outboundOptions) +} diff --git a/mihomo/listener/inbound/vless_test.go b/mihomo/listener/inbound/vless_test.go new file mode 100644 index 0000000000..ed7a507347 --- /dev/null +++ b/mihomo/listener/inbound/vless_test.go @@ -0,0 +1,186 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outboundOptions outbound.VlessOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "vless_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.VlessUser{ + {Username: "test", UUID: userUUID, Flow: "xtls-rprx-vision"}, + } + in, err := inbound.NewVless(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "vless_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.UUID = userUUID + + out, err := outbound.NewVless(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundVless_Tls(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Wss1(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Wss2(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Grpc1(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} + +func TestInboundVless_Grpc2(t *testing.T) { + inboundOptions := inbound.VlessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} + +func TestInboundVless_Reality(t *testing.T) { + inboundOptions := inbound.VlessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.VlessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundVless(t, inboundOptions, outboundOptions) + t.Run("xtls-rprx-vision", func(t *testing.T) { + outboundOptions.Flow = "xtls-rprx-vision" + testInboundVless(t, inboundOptions, outboundOptions) + }) +} + +func TestInboundVless_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.VlessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VlessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVless(t, inboundOptions, outboundOptions) +} diff --git a/mihomo/listener/inbound/vmess_test.go b/mihomo/listener/inbound/vmess_test.go new file mode 100644 index 0000000000..4fd63c411a --- /dev/null +++ b/mihomo/listener/inbound/vmess_test.go @@ -0,0 +1,248 @@ +package inbound_test + +import ( + "net" + "net/netip" + "testing" + + "github.com/metacubex/mihomo/adapter/outbound" + "github.com/metacubex/mihomo/common/utils" + "github.com/metacubex/mihomo/listener/inbound" + "github.com/stretchr/testify/assert" +) + +func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) { + userUUID := utils.NewUUIDV4().String() + inboundOptions.BaseOption = inbound.BaseOption{ + NameStr: "vmess_inbound", + Listen: "127.0.0.1", + Port: "0", + } + inboundOptions.Users = []inbound.VmessUser{ + {Username: "test", UUID: userUUID, AlterID: 0}, + } + in, err := inbound.NewVmess(&inboundOptions) + assert.NoError(t, err) + + tunnel := NewHttpTestTunnel() + defer tunnel.Close() + + err = in.Listen(tunnel) + assert.NoError(t, err) + defer in.Close() + + addrPort, err := netip.ParseAddrPort(in.Address()) + assert.NoError(t, err) + + outboundOptions.Name = "vmess_outbound" + outboundOptions.Server = addrPort.Addr().String() + outboundOptions.Port = int(addrPort.Port()) + outboundOptions.UUID = userUUID + outboundOptions.AlterID = 0 + outboundOptions.Cipher = "auto" + + out, err := outbound.NewVmess(outboundOptions) + assert.NoError(t, err) + defer out.Close() + + tunnel.DoTest(t, out) +} + +func TestInboundVMess_Basic(t *testing.T) { + inboundOptions := inbound.VmessOption{} + outboundOptions := outbound.VmessOption{} + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Tls(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_ed1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws?ed=2048", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_ed2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + MaxEarlyData: 2048, + EarlyDataHeaderName: "Sec-WebSocket-Protocol", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_Upgrade1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + V2rayHttpUpgrade: true, + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Ws_Upgrade2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + V2rayHttpUpgrade: true, + V2rayHttpUpgradeFastOpen: true, + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Wss1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Wss2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "ws", + WSOpts: outbound.WSOptions{ + Path: "/ws", + }, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Grpc1(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Grpc2(t *testing.T) { + inboundOptions := inbound.VmessOption{ + Certificate: tlsCertificate, + PrivateKey: tlsPrivateKey, + WsPath: "/ws", + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + Fingerprint: tlsFingerprint, + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Reality(t *testing.T) { + inboundOptions := inbound.VmessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + } + outboundOptions := outbound.VmessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + } + testInboundVMess(t, inboundOptions, outboundOptions) +} + +func TestInboundVMess_Reality_Grpc(t *testing.T) { + inboundOptions := inbound.VmessOption{ + RealityConfig: inbound.RealityConfig{ + Dest: net.JoinHostPort(realityDest, "443"), + PrivateKey: realityPrivateKey, + ShortID: []string{realityShortid}, + ServerNames: []string{realityDest}, + }, + GrpcServiceName: "GunService", + } + outboundOptions := outbound.VmessOption{ + TLS: true, + ServerName: realityDest, + RealityOpts: outbound.RealityOptions{ + PublicKey: realityPublickey, + ShortID: realityShortid, + }, + ClientFingerprint: "chrome", + Network: "grpc", + GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"}, + } + testInboundVMess(t, inboundOptions, outboundOptions) +} diff --git a/mihomo/listener/inner/tcp.go b/mihomo/listener/inner/tcp.go index ee34ada661..95753fe9c4 100644 --- a/mihomo/listener/inner/tcp.go +++ b/mihomo/listener/inner/tcp.go @@ -3,8 +3,6 @@ package inner import ( "errors" "net" - "net/netip" - "strconv" N "github.com/metacubex/mihomo/common/net" C "github.com/metacubex/mihomo/constant" @@ -16,9 +14,13 @@ func New(t C.Tunnel) { tunnel = t } -func HandleTcp(address string, proxy string) (conn net.Conn, err error) { +func GetTunnel() C.Tunnel { + return tunnel +} + +func HandleTcp(tunnel C.Tunnel, address string, proxy string) (conn net.Conn, err error) { if tunnel == nil { - return nil, errors.New("tcp uninitialized") + return nil, errors.New("tunnel uninitialized") } // executor Parsed conn1, conn2 := N.Pipe() @@ -31,15 +33,8 @@ func HandleTcp(address string, proxy string) (conn net.Conn, err error) { if proxy != "" { metadata.SpecialProxy = proxy } - if h, port, err := net.SplitHostPort(address); err == nil { - if port, err := strconv.ParseUint(port, 10, 16); err == nil { - metadata.DstPort = uint16(port) - } - if ip, err := netip.ParseAddr(h); err == nil { - metadata.DstIP = ip - } else { - metadata.Host = h - } + if err = metadata.SetRemoteAddress(address); err != nil { + return nil, err } go tunnel.HandleTCPConn(conn2, metadata) diff --git a/mihomo/listener/mixed/mixed.go b/mihomo/listener/mixed/mixed.go index 6785b7fe5b..0ffdb02a3d 100644 --- a/mihomo/listener/mixed/mixed.go +++ b/mihomo/listener/mixed/mixed.go @@ -73,7 +73,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/listener/reality/reality.go b/mihomo/listener/reality/reality.go index 222b036c67..16ccc01c10 100644 --- a/mihomo/listener/reality/reality.go +++ b/mihomo/listener/reality/reality.go @@ -9,6 +9,7 @@ import ( "net" "time" + C "github.com/metacubex/mihomo/constant" "github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/ntp" @@ -27,7 +28,7 @@ type Config struct { Proxy string } -func (c Config) Build() (*Builder, error) { +func (c Config) Build(tunnel C.Tunnel) (*Builder, error) { realityConfig := &utls.RealityConfig{} realityConfig.SessionTicketsDisabled = true realityConfig.Type = "tcp" @@ -67,7 +68,7 @@ func (c Config) Build() (*Builder, error) { } realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { - return inner.HandleTcp(address, c.Proxy) + return inner.HandleTcp(tunnel, address, c.Proxy) } return &Builder{realityConfig}, nil diff --git a/mihomo/listener/sing_vless/server.go b/mihomo/listener/sing_vless/server.go index 0614e4e622..137c8beb95 100644 --- a/mihomo/listener/sing_vless/server.go +++ b/mihomo/listener/sing_vless/server.go @@ -106,7 +106,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/listener/sing_vmess/server.go b/mihomo/listener/sing_vmess/server.go index dd8f32af94..dc80e8cb8f 100644 --- a/mihomo/listener/sing_vmess/server.go +++ b/mihomo/listener/sing_vmess/server.go @@ -90,7 +90,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/listener/socks/tcp.go b/mihomo/listener/socks/tcp.go index 5515360772..9e42a62505 100644 --- a/mihomo/listener/socks/tcp.go +++ b/mihomo/listener/socks/tcp.go @@ -72,7 +72,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/listener/trojan/server.go b/mihomo/listener/trojan/server.go index 3141ab0b91..299575a562 100644 --- a/mihomo/listener/trojan/server.go +++ b/mihomo/listener/trojan/server.go @@ -84,7 +84,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) if tlsConfig.Certificates != nil { return nil, errors.New("certificate is unavailable in reality") } - realityBuilder, err = config.RealityConfig.Build() + realityBuilder, err = config.RealityConfig.Build(tunnel) if err != nil { return nil, err } diff --git a/mihomo/transport/shadowsocks/shadowstream/packet.go b/mihomo/transport/shadowsocks/shadowstream/packet.go index 39d09a70e5..f8cd7e616f 100644 --- a/mihomo/transport/shadowsocks/shadowstream/packet.go +++ b/mihomo/transport/shadowsocks/shadowstream/packet.go @@ -29,6 +29,18 @@ func Pack(dst, plaintext []byte, s Cipher) ([]byte, error) { return dst[:len(iv)+len(plaintext)], nil } +// UnpackInplace decrypts pkt using stream cipher s. +// Returns a slice of pkt containing decrypted plaintext. +// Note: The data in the input dst will be changed +func UnpackInplace(pkt []byte, s Cipher) ([]byte, error) { + if len(pkt) < s.IVSize() { + return nil, ErrShortPacket + } + iv, dst := pkt[:s.IVSize()], pkt[s.IVSize():] + s.Decrypter(iv).XORKeyStream(dst, dst) + return dst, nil +} + // Unpack decrypts pkt using stream cipher s. // Returns a slice of dst containing decrypted plaintext. func Unpack(dst, pkt []byte, s Cipher) ([]byte, error) { @@ -71,7 +83,7 @@ func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) { if err != nil { return n, addr, err } - bb, err := Unpack(b[c.IVSize():], b[:n], c.Cipher) + bb, err := UnpackInplace(b[:n], c.Cipher) if err != nil { return n, addr, err } @@ -84,7 +96,7 @@ func (c *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, err if err != nil { return } - data, err = Unpack(data[c.IVSize():], data, c) + data, err = UnpackInplace(data, c.Cipher) if err != nil { if put != nil { put() diff --git a/mihomo/transport/vless/vision/conn.go b/mihomo/transport/vless/vision/conn.go index 69ccfba0c3..556626594c 100644 --- a/mihomo/transport/vless/vision/conn.go +++ b/mihomo/transport/vless/vision/conn.go @@ -5,6 +5,7 @@ import ( "crypto/subtle" gotls "crypto/tls" "encoding/binary" + "errors" "fmt" "io" "net" @@ -117,9 +118,11 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { case commandPaddingDirect: needReturn := false if vc.input != nil { - _, err := buffer.ReadFrom(vc.input) + _, err := buffer.ReadOnceFrom(vc.input) if err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } } if vc.input.Len() == 0 { needReturn = true @@ -129,9 +132,11 @@ func (vc *Conn) ReadBuffer(buffer *buf.Buffer) error { } } if vc.rawInput != nil { - _, err := buffer.ReadFrom(vc.rawInput) + _, err := buffer.ReadOnceFrom(vc.rawInput) if err != nil { - return err + if !errors.Is(err, io.EOF) { + return err + } } needReturn = true if vc.rawInput.Len() == 0 { diff --git a/mihomo/transport/vmess/websocket.go b/mihomo/transport/vmess/websocket.go index 772c6fefb4..8ada88ecc1 100644 --- a/mihomo/transport/vmess/websocket.go +++ b/mihomo/transport/vmess/websocket.go @@ -467,7 +467,7 @@ func streamWebsocketConn(ctx context.Context, conn net.Conn, c *WebsocketConfig, } } - conn = newWebsocketConn(conn, ws.StateClientSide) + conn = newWebsocketConn(bufferedConn, ws.StateClientSide) // websocketConn can't correct handle ReadDeadline // so call N.NewDeadlineConn to add a safe wrapper return N.NewDeadlineConn(conn), nil @@ -555,7 +555,7 @@ func StreamUpgradedWebsocketConn(w http.ResponseWriter, r *http.Request) (net.Co w.Header().Set("Sec-Websocket-Accept", getSecAccept(r.Header.Get("Sec-WebSocket-Key"))) } w.WriteHeader(http.StatusSwitchingProtocols) - if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher { + if flusher, isFlusher := w.(interface{ FlushError() error }); isFlusher && writeHeaderShouldFlush { err = flusher.FlushError() if err != nil { return nil, fmt.Errorf("flush response: %w", err) diff --git a/mihomo/transport/vmess/websocket_go120.go b/mihomo/transport/vmess/websocket_go120.go new file mode 100644 index 0000000000..0b9ff5e3d6 --- /dev/null +++ b/mihomo/transport/vmess/websocket_go120.go @@ -0,0 +1,7 @@ +//go:build !go1.21 + +package vmess + +// Golang1.20's net.http Flush will mistakenly call w.WriteHeader(StatusOK) internally after w.WriteHeader(http.StatusSwitchingProtocols) +// https://github.com/golang/go/issues/59564 +const writeHeaderShouldFlush = false diff --git a/mihomo/transport/vmess/websocket_go121.go b/mihomo/transport/vmess/websocket_go121.go new file mode 100644 index 0000000000..9419cd50a3 --- /dev/null +++ b/mihomo/transport/vmess/websocket_go121.go @@ -0,0 +1,5 @@ +//go:build go1.21 + +package vmess + +const writeHeaderShouldFlush = true diff --git a/nekobox-android/README.md b/nekobox-android/README.md index b09c60a4e0..b1322451e6 100644 --- a/nekobox-android/README.md +++ b/nekobox-android/README.md @@ -16,7 +16,8 @@ sing-box / universal proxy toolchain for Android. **Google Play 版本自 2024 年 5 月起已被第三方控制,为非开源版本,请不要下载。** -**The Google Play version has been controlled by a third party since May 2024 and is a non-open source version. Please do not download it.** +**The Google Play version has been controlled by a third party since May 2024 and is a non-open +source version. Please do not download it.** ## 更新日志 & Telegram 发布频道 / Changelog & Telegram Channel @@ -33,23 +34,33 @@ https://matsuridayo.github.io * SSH * Shadowsocks * VMess -* VLESS -* WireGuard * Trojan +* VLESS +* AnyTLS +* ShadowTLS +* TUIC +* Hysteria 1/2 +* WireGuard * Trojan-Go (trojan-go-plugin) * NaïveProxy (naive-plugin) -* Hysteria (hysteria-plugin) * Mieru (mieru-plugin) -* TUIC -请到[这里](https://matsuridayo.github.io/m-plugin/)下载插件以获得完整的代理支持. +请到[这里](https://matsuridayo.github.io/nb4a-plugin/)下载插件以获得完整的代理支持. -Please visit [here](https://matsuridayo.github.io/m-plugin/) to download plugins for full proxy supports. +Please visit [here](https://matsuridayo.github.io/nb4a-plugin/) to download plugins for full proxy +supports. ## 支持的订阅格式 / Supported Subscription Format -* 原始格式: 一些广泛使用的格式 (如 Shadowsocks, Clash 和 v2rayN) -* Raw: some widely used formats (like Shadowsocks, Clash and v2rayN) +* 一些广泛使用的格式 (如 Shadowsocks, ClashMeta 和 v2rayN) +* sing-box 出站 + +仅支持解析出站,即节点。分流规则等信息会被忽略。 + +* Some widely used formats (like Shadowsocks, ClashMeta and v2rayN) +* sing-box outbound + +Only resolving outbound, i.e. nodes, is supported. Information such as diversion rules are ignored. ## 捐助 / Donate @@ -57,9 +68,12 @@ Please visit [here](https://matsuridayo.github.io/m-plugin/) to download plugins 如果这个项目对您有帮助, 可以通过捐赠的方式帮助我们维持这个项目. -捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里, 欢迎联系我们补充. +捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里, +欢迎联系我们补充. -Donations of 50 USD or more can display your avatar on the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please contact us to add it. +Donations of 50 USD or more can display your avatar on +the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please +contact us to add it. USDT TRC20 @@ -74,13 +88,14 @@ XMR ## Credits Core: + - [SagerNet/sing-box](https://github.com/SagerNet/sing-box) -- [Matsuridayo/sing-box-extra](https://github.com/MatsuriDayo/sing-box-extra) Android GUI: + - [shadowsocks/shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android) - [SagerNet/SagerNet](https://github.com/SagerNet/SagerNet) -- [Matsuridayo/Matsuri](https://github.com/MatsuriDayo/Matsuri) Web Dashboard: + - [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta) diff --git a/nekobox-android/app/src/main/AndroidManifest.xml b/nekobox-android/app/src/main/AndroidManifest.xml index c0ed87d7f2..f969dea481 100644 --- a/nekobox-android/app/src/main/AndroidManifest.xml +++ b/nekobox-android/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ - + @@ -191,9 +191,6 @@ - @@ -258,36 +255,33 @@ - - + android:foregroundServiceType="systemExempted" + android:process=":bg" + tools:ignore="ForegroundServicePermission" /> + android:process=":bg" + tools:ignore="ForegroundServicePermission"> - diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/Constants.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/Constants.kt index a2fece3668..d44a3b601c 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/Constants.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/Constants.kt @@ -147,7 +147,6 @@ object Key { // - const val NEKO_PLUGIN_MANAGED = "nekoPlugins" const val APP_TLS_VERSION = "appTLSVersion" const val ENABLE_CLASH_API = "enableClashAPI" } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt index 3efd3f4c41..0ac84ac6a3 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt @@ -6,9 +6,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED import android.os.Build import android.text.format.Formatter +import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import io.nekohasekai.sagernet.Action @@ -185,14 +186,22 @@ class ServiceNotification( private suspend fun show() = useBuilder { - if (Build.VERSION.SDK_INT >= 34) { - (service as Service).startForeground( - notificationId, - it.build(), - FOREGROUND_SERVICE_TYPE_SPECIAL_USE - ) - } else { - (service as Service).startForeground(notificationId, it.build()) + try { + if (Build.VERSION.SDK_INT >= 34) { + (service as Service).startForeground( + notificationId, + it.build(), + FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED + ) + } else { + (service as Service).startForeground(notificationId, it.build()) + } + } catch (e: Exception) { + Toast.makeText( + SagerNet.application, + "startForeground: $e", + Toast.LENGTH_LONG + ).show() } } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt index c43b250ebd..751c14965f 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt @@ -16,9 +16,6 @@ import io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.ui.VpnRequestActivity import io.nekohasekai.sagernet.utils.Subnet -import libcore.* -import moe.matsuri.nb4a.net.LocalResolverImpl -import moe.matsuri.nb4a.proxy.neko.needBypassRootUid import android.net.VpnService as BaseVpnService class VpnService : BaseVpnService(), @@ -135,7 +132,7 @@ class VpnService : BaseVpnService(), var bypass = DataStore.bypass val workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */ val needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any { - it[0].nekoBean?.needBypassRootUid() == true || it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP + it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP } if (proxyApps || needBypassRootUid) { diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt index bca90d09ee..84586bc2c3 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt @@ -21,11 +21,6 @@ import io.nekohasekai.sagernet.plugin.PluginManager import kotlinx.coroutines.* import libcore.BoxInstance import libcore.Libcore -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.proxy.neko.NekoBean -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.updateAllConfig -import org.json.JSONObject import java.io.File abstract class BoxInstance( @@ -53,7 +48,6 @@ abstract class BoxInstance( } protected open suspend fun loadConfig() { - NekoJSInterface.Default.destroyAllJsi() box = Libcore.newSingBoxInstance(config.config) } @@ -88,17 +82,6 @@ abstract class BoxInstance( } } } - - is NekoBean -> { - // check if plugin binary can be loaded - initPlugin(bean.plgId) - - // build config and check if succeed - bean.updateAllConfig(port) - if (bean.allConfig == null) { - throw NekoPluginManager.PluginInternalException(bean.protocolId) - } - } } } } @@ -211,44 +194,6 @@ abstract class BoxInstance( processes.start(commands) } - - bean is NekoBean -> { - // config built from JS - val nekoRunConfigs = bean.allConfig.optJSONArray("nekoRunConfigs") - val configs = mutableMapOf() - - nekoRunConfigs?.forEach { _, any -> - any as JSONObject - - val name = any.getString("name") - val configFile = File(cacheDir, name) - configFile.parentFile?.mkdirs() - val content = any.getString("content") - configFile.writeText(content) - - cacheFiles.add(configFile) - configs[name] = configFile.absolutePath - - Logs.d(name + "\n\n" + content) - } - - val nekoCommands = bean.allConfig.getJSONArray("nekoCommands") - val commands = mutableListOf() - - nekoCommands.forEach { _, any -> - if (any is String) { - if (configs.containsKey(any)) { - commands.add(configs[any]!!) - } else if (any == "%exe%") { - commands.add(initPlugin(bean.plgId).path) - } else { - commands.add(any) - } - } - } - - processes.start(commands) - } } } } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt index 8c9591153d..df6f06ab4a 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt @@ -81,7 +81,6 @@ object DataStore : OnPreferenceDataStoreChangeListener { return groups.find { it.type == GroupType.BASIC }!!.id } - var nekoPlugins by configurationStore.string(Key.NEKO_PLUGIN_MANAGED) var appTLSVersion by configurationStore.string(Key.APP_TLS_VERSION) var enableClashAPI by configurationStore.boolean(Key.ENABLE_CLASH_API) var showBottomBar by configurationStore.boolean(Key.SHOW_BOTTOM_BAR) diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt index b53343dba9..b975695cb2 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt @@ -239,7 +239,7 @@ data class ProxyEntity( is SSHBean -> false is WireGuardBean -> false is ShadowTLSBean -> false - is NekoBean -> nekoBean!!.haveStandardLink() + is NekoBean -> false is ConfigBean -> false else -> true } @@ -257,7 +257,7 @@ data class ProxyEntity( is HysteriaBean -> toUri() is TuicBean -> toUri() is AnyTLSBean -> toUri() - is NekoBean -> shareLink() + is NekoBean -> "" else -> toUniversalLink() } } @@ -470,7 +470,6 @@ data class ProxyEntity( TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java TYPE_CHAIN -> ChainSettingsActivity::class.java - TYPE_NEKO -> NekoSettingActivity::class.java TYPE_CONFIG -> ConfigSettingActivity::class.java else -> throw IllegalArgumentException() } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java index b3cbcc4d5b..61a2a7323c 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java @@ -54,11 +54,6 @@ public abstract class StandardV2RayBean extends AbstractBean { public String echConfig; - // sing-box 不再使用 - public Boolean enablePqSignature; - - public Boolean disabledDRS; - // --------------------------------------- Mux public Boolean enableMux; @@ -86,7 +81,7 @@ public abstract class StandardV2RayBean extends AbstractBean { if (JavaUtil.isNullOrBlank(path)) path = ""; if (JavaUtil.isNullOrBlank(security)) { - if (this instanceof TrojanBean || isVLESS()) { + if (this instanceof TrojanBean) { security = "tls"; } else { security = "none"; @@ -108,8 +103,6 @@ public abstract class StandardV2RayBean extends AbstractBean { if (enableECH == null) enableECH = false; if (JavaUtil.isNullOrBlank(echConfig)) echConfig = ""; - if (enablePqSignature == null) enablePqSignature = false; - if (disabledDRS == null) disabledDRS = false; if (enableMux == null) enableMux = false; if (muxPadding == null) muxPadding = false; @@ -119,7 +112,7 @@ public abstract class StandardV2RayBean extends AbstractBean { @Override public void serialize(ByteBufferOutput output) { - output.writeInt(2); + output.writeInt(3); super.serialize(output); output.writeString(uuid); output.writeString(encryption); @@ -167,11 +160,7 @@ public abstract class StandardV2RayBean extends AbstractBean { } output.writeBoolean(enableECH); - if (enableECH) { - output.writeBoolean(enablePqSignature); - output.writeBoolean(disabledDRS); - output.writeString(echConfig); - } + output.writeString(echConfig); output.writeInt(packetEncoding); @@ -229,16 +218,18 @@ public abstract class StandardV2RayBean extends AbstractBean { realityShortId = input.readString(); } - if (version >= 1) { // 从老版本升级上来 + if (version >= 1) { enableECH = input.readBoolean(); - if (enableECH) { - enablePqSignature = input.readBoolean(); - disabledDRS = input.readBoolean(); + if (version >= 3) { echConfig = input.readString(); + } else { + if (enableECH) { + input.readBoolean(); + input.readBoolean(); + echConfig = input.readString(); + } } - } - - if (version == 0) { + } else if (version == 0) { // 从老版本升级上来但是 version == 0, 可能有 enableECH 也可能没有,需要做判断 int position = input.getByteBuffer().position(); // 当前位置 @@ -250,8 +241,8 @@ public abstract class StandardV2RayBean extends AbstractBean { if (tmpPacketEncoding != 1 && tmpPacketEncoding != 2) { enableECH = tmpEnableECH; if (enableECH) { - enablePqSignature = input.readBoolean(); - disabledDRS = input.readBoolean(); + input.readBoolean(); + input.readBoolean(); echConfig = input.readString(); } } // 否则后一位就是 packetEncoding @@ -275,7 +266,6 @@ public abstract class StandardV2RayBean extends AbstractBean { bean.utlsFingerprint = utlsFingerprint; bean.packetEncoding = packetEncoding; bean.enableECH = enableECH; - bean.disabledDRS = disabledDRS; bean.echConfig = echConfig; bean.enableMux = enableMux; bean.muxPadding = muxPadding; diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt index 79f80bc495..93684a5b3f 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt @@ -153,7 +153,7 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { } type = url.queryParameter("type") ?: "tcp" - if (type == "h2") type = "http" + if (type == "h2" || url.queryParameter("headerType") == "http") type = "http" security = url.queryParameter("security") if (security.isNullOrBlank()) { @@ -163,6 +163,9 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { when (security) { "tls", "reality" -> { security = "tls" + url.queryParameter("allowInsecure")?.let { + allowInsecure = it == "1" || it == "true" + } url.queryParameter("sni")?.let { sni = it } @@ -185,16 +188,6 @@ fun StandardV2RayBean.parseDuckSoft(url: HttpUrl) { } when (type) { - "tcp" -> { - // v2rayNG - if (url.queryParameter("headerType") == "http") { - url.queryParameter("host")?.let { - type = "http" - host = it - } - } - } - "http" -> { url.queryParameter("host")?.let { host = it diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java index 84044da1b1..2b7e8ae9ed 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java @@ -16,7 +16,12 @@ public class VMessBean extends StandardV2RayBean { super.initializeDefaultValues(); alterId = alterId != null ? alterId : 0; - encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto"; + + if (alterId == -1) { + encryption = JavaUtil.isNotBlank(encryption) ? encryption : ""; + } else { + encryption = JavaUtil.isNotBlank(encryption) ? encryption : "auto"; + } } @NotNull diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt index 59adcb735a..943674bd89 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt @@ -1,7 +1,6 @@ package io.nekohasekai.sagernet.group import android.annotation.SuppressLint -import android.net.Uri import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.database.* import io.nekohasekai.sagernet.fmt.AbstractBean @@ -33,6 +32,7 @@ import org.yaml.snakeyaml.TypeDescription import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.error.YAMLException import java.io.StringReader +import androidx.core.net.toUri @Suppress("EXPERIMENTAL_API_USAGE") object RawUpdater : GroupUpdater() { @@ -48,7 +48,7 @@ object RawUpdater : GroupUpdater() { val link = subscription.link var proxies: List if (link.startsWith("content://")) { - val contentText = app.contentResolver.openInputStream(Uri.parse(link)) + val contentText = app.contentResolver.openInputStream(link.toUri()) ?.bufferedReader() ?.readText() @@ -174,11 +174,12 @@ object RawUpdater : GroupUpdater() { } } else { changed++ - SagerDatabase.proxyDao.addProxy(ProxyEntity( - groupId = proxyGroup.id, userOrder = userOrder - ).apply { - putBean(bean) - }) + SagerDatabase.proxyDao.addProxy( + ProxyEntity( + groupId = proxyGroup.id, userOrder = userOrder + ).apply { + putBean(bean) + }) added.add(name) Logs.d("Inserted profile: $name") } @@ -250,7 +251,7 @@ object RawUpdater : GroupUpdater() { setTLS(proxy["tls"]?.toString() == "true") sni = proxy["sni"]?.toString() name = proxy["name"]?.toString() - allowInsecure = proxy["name"]?.toString() == "true" + allowInsecure = proxy["skip-cert-verify"]?.toString() == "true" }) } @@ -289,226 +290,181 @@ object RawUpdater : GroupUpdater() { }) } - "vmess", "vless" -> { - var isHttpUpgrade = false - val isVLESS = proxy["type"].toString() == "vless" - val bean = VMessBean().apply { - if (isVLESS) { + "vmess", "vless", "trojan" -> { + val bean = when (proxy["type"] as String) { + "vmess" -> VMessBean() + "vless" -> VMessBean().apply { alterId = -1 // make it VLESS packetEncoding = 2 // clash meta default XUDP } + + "trojan" -> TrojanBean().apply { + security = "tls" + } + + else -> error("impossible") } + + bean.serverAddress = proxy["server"]?.toString() ?: continue + bean.serverPort = proxy["port"]?.toString()?.toIntOrNull() ?: continue + for (opt in proxy) { - if (opt.value == null) continue - when (opt.key.replace("_", "-")) { - "name" -> bean.name = opt.value.toString() - "server" -> bean.serverAddress = opt.value as String - "port" -> bean.serverPort = opt.value.toString().toInt() - "uuid" -> bean.uuid = opt.value as String + when (opt.key) { + "name" -> bean.name = opt.value?.toString() + "password" -> if (bean is TrojanBean) bean.password = + opt.value?.toString() - "alterId" -> if (!isVLESS) bean.alterId = - opt.value.toString().toInt() + "uuid" -> if (bean is VMessBean) bean.uuid = + opt.value?.toString() - "cipher" -> if (!isVLESS) bean.encryption = opt.value as String + "alterId" -> if (bean is VMessBean && !bean.isVLESS) bean.alterId = + opt.value?.toString()?.toIntOrNull() - "flow" -> if (isVLESS) bean.encryption = opt.value as String + "cipher" -> if (bean is VMessBean && !bean.isVLESS) bean.encryption = + (opt.value as? String) - "packet-addr" -> if (opt.value.toString() == "true") { - bean.packetEncoding = 1 + "flow" -> if (bean is VMessBean && bean.isVLESS) { + (opt.value as? String)?.let { + if (it.contains("xtls-rprx-vision")) { + bean.encryption = "xtls-rprx-vision" + } + } } - "xudp" -> if (opt.value.toString() == "true") { - bean.packetEncoding = 2 + "packet-encoding" -> if (bean is VMessBean) { + bean.packetEncoding = when ((opt.value as? String)) { + "packetaddr" -> 1 + "xudp" -> 2 + else -> 0 + } + } + + "tls" -> if (bean is VMessBean) { + bean.security = + if (opt.value as? Boolean == true) "tls" else "" + } + + "servername", "sni" -> bean.sni = opt.value?.toString() + + "alpn" -> bean.alpn = + (opt.value as? List)?.joinToString("\n") + + "skip-cert-verify" -> bean.allowInsecure = + opt.value as? Boolean == true + + "client-fingerprint" -> bean.utlsFingerprint = + opt.value as String + + "reality-opts" -> (opt.value as? Map)?.also { + for (realityOpt in it) { + bean.security = "tls" + + when (realityOpt.key) { + "public-key" -> bean.realityPubKey = + realityOpt.value?.toString() + + "short-id" -> bean.realityShortId = + realityOpt.value?.toString() + } + } } "network" -> { - bean.type = opt.value as String - // Clash "network" fix - when (bean.type) { - "h2" -> bean.type = "http" + when (opt.value) { + "h2", "http" -> bean.type = "http" + "ws", "grpc" -> bean.type = opt.value as String } } - "client-fingerprint" -> bean.utlsFingerprint = - opt.value as String - - "tls" -> bean.security = - if (opt.value.toString() == "true") "tls" else "" - - "servername" -> bean.sni = opt.value.toString() - - "skip-cert-verify" -> bean.allowInsecure = - opt.value.toString() == "true" - - "alpn" -> { - val alpn = (opt.value as? (List)) - bean.alpn = alpn?.joinToString("\n") - } - - "ws-path" -> bean.path = opt.value.toString() - "ws-headers" -> for (wsHeader in (opt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() - } - } - - "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { - when (wsOpt.key.lowercase()) { - "headers" -> for (wsHeader in (wsOpt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() + "ws-opts" -> (opt.value as? Map)?.also { + for (wsOpt in it) { + when (wsOpt.key) { + "headers" -> (wsOpt.value as? Map)?.forEach { (key, value) -> + when (key.toString().lowercase()) { + "host" -> { + bean.host = value?.toString() + } + } } - } - "path" -> { - bean.path = wsOpt.value.toString() - } + "path" -> { + bean.path = wsOpt.value?.toString() + } - "max-early-data" -> { - bean.wsMaxEarlyData = wsOpt.value.toString().toInt() - } + "max-early-data" -> { + bean.wsMaxEarlyData = + wsOpt.value?.toString()?.toIntOrNull() + } - "early-data-header-name" -> { - bean.earlyDataHeaderName = wsOpt.value.toString() - } + "early-data-header-name" -> { + bean.earlyDataHeaderName = + wsOpt.value?.toString() + } - "v2ray-http-upgrade" -> { - isHttpUpgrade = true - } - } - } - - // The format of the VMessBean is wrong, so the `host` `path` has some strange transformations here. - "h2-opts", "h2-opt" -> for (h2Opt in (opt.value as Map)) { - when (h2Opt.key.lowercase()) { - "host" -> bean.host = - (h2Opt.value as List).first() - - "path" -> bean.path = h2Opt.value.toString() - } - } - - "http-opts", "http-opt" -> for (httpOpt in (opt.value as Map)) { - when (httpOpt.key.lowercase()) { - "path" -> bean.path = - (httpOpt.value as List).first() - - "headers" -> for (hdr in (httpOpt.value as Map)) { - when (hdr.key.lowercase()) { - "host" -> bean.host = - (hdr.value as List).first() + "v2ray-http-upgrade" -> { + if (wsOpt.value as? Boolean == true) { + bean.type = "httpupgrade" + } } } } } - "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { - when (grpcOpt.key.lowercase()) { - "grpc-service-name" -> bean.path = - grpcOpt.value.toString() + "h2-opts" -> (opt.value as? Map)?.also { + for (h2Opt in it) { + when (h2Opt.key) { + "host" -> bean.host = + (h2Opt.value as? List)?.joinToString("\n") + + "path" -> bean.path = h2Opt.value?.toString() + } } } - "reality-opts" -> for (realityOpt in (opt.value as Map)) { - when (realityOpt.key.lowercase()) { - "public-key" -> bean.realityPubKey = - realityOpt.value.toString() + "http-opts" -> (opt.value as? Map)?.also { + for (httpOpt in it) { + when (httpOpt.key) { + "path" -> bean.path = + (httpOpt.value as? List)?.joinToString("\n") - "short-id" -> bean.realityShortId = - realityOpt.value.toString() - } - } - - "smux" -> for (smuxOpt in (opt.value as Map)) { - when (smuxOpt.key.lowercase()) { - "enabled" -> bean.enableMux = - smuxOpt.value.toString() == "true" - - "max-streams" -> bean.muxConcurrency = - smuxOpt.value.toString().toInt() - - "padding" -> bean.muxPadding = - smuxOpt.value.toString() == "true" - } - } - - } - } - if (isHttpUpgrade) { - bean.type = "httpupgrade" - } - proxies.add(bean) - } - - "trojan" -> { - var isHttpUpgrade = false - val bean = TrojanBean() - bean.security = "tls" - for (opt in proxy) { - if (opt.value == null) continue - when (opt.key.replace("_", "-")) { - "name" -> bean.name = opt.value.toString() - "server" -> bean.serverAddress = opt.value as String - "port" -> bean.serverPort = opt.value.toString().toInt() - "password" -> bean.password = opt.value.toString() - "client-fingerprint" -> bean.utlsFingerprint = - opt.value as String - - "sni" -> bean.sni = opt.value.toString() - "skip-cert-verify" -> bean.allowInsecure = - opt.value.toString() == "true" - - "alpn" -> { - val alpn = (opt.value as? (List)) - bean.alpn = alpn?.joinToString("\n") - } - - "network" -> when (opt.value) { - "ws", "grpc" -> bean.type = opt.value.toString() - } - - "ws-opts", "ws-opt" -> for (wsOpt in (opt.value as Map)) { - when (wsOpt.key.lowercase()) { - "headers" -> for (wsHeader in (wsOpt.value as Map)) { - when (wsHeader.key.lowercase()) { - "host" -> bean.host = wsHeader.value.toString() + "headers" -> { + (httpOpt.value as? Map>)?.forEach { (key, value) -> + when (key.toString().lowercase()) { + "host" -> { + bean.host = value.joinToString("\n") + } + } + } } } + } + } - "path" -> { - bean.path = wsOpt.value.toString() - } - - "v2ray-http-upgrade" -> { - isHttpUpgrade = true + "grpc-opts" -> (opt.value as? Map)?.also { + for (grpcOpt in it) { + when (grpcOpt.key) { + "grpc-service-name" -> bean.path = + grpcOpt.value?.toString() } } } - "grpc-opts", "grpc-opt" -> for (grpcOpt in (opt.value as Map)) { - when (grpcOpt.key.lowercase()) { - "grpc-service-name" -> bean.path = - grpcOpt.value.toString() - } - } + "smux" -> (opt.value as? Map)?.also { + for (smuxOpt in it) { + when (smuxOpt.key) { + "enabled" -> bean.enableMux = + smuxOpt.value.toString() == "true" - "smux" -> for (smuxOpt in (opt.value as Map)) { - when (smuxOpt.key.lowercase()) { - "enabled" -> bean.enableMux = - smuxOpt.value.toString() == "true" + "max-streams" -> bean.muxConcurrency = + smuxOpt.value.toString().toInt() - "max-streams" -> bean.muxConcurrency = - smuxOpt.value.toString().toInt() - - "padding" -> bean.muxPadding = - smuxOpt.value.toString() == "true" + "padding" -> bean.muxPadding = + smuxOpt.value.toString() == "true" + } } } } } - if (isHttpUpgrade) { - bean.type = "httpupgrade" - } proxies.add(bean) } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt index 3fc857397c..37747191e2 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt @@ -14,10 +14,7 @@ import io.nekohasekai.sagernet.fmt.trojan.parseTrojan import io.nekohasekai.sagernet.fmt.tuic.parseTuic import io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo import io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray -import moe.matsuri.nb4a.plugin.NekoPluginManager import moe.matsuri.nb4a.proxy.anytls.parseAnytls -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.parseShareLink import moe.matsuri.nb4a.utils.JavaUtil.gson import moe.matsuri.nb4a.utils.Util import org.json.JSONArray @@ -112,7 +109,7 @@ suspend fun parseProxies(text: String): List { val entities = ArrayList() val entitiesByLine = ArrayList() - suspend fun String.parseLink(entities: ArrayList) { + fun String.parseLink(entities: ArrayList) { if (startsWith("clash://install-config?") || startsWith("sn://subscription?")) { throw SubscriptionFoundException(this) } @@ -211,22 +208,6 @@ suspend fun parseProxies(text: String): List { }.onFailure { Logs.w(it) } - } else { // Neko Plugins - NekoPluginManager.getProtocols().forEach { obj -> - obj.protocolConfig.optJSONArray("links")?.forEach { _, any -> - if (any is String && startsWith(any)) { - runCatching { - entities.add( - parseShareLink( - obj.plgId, obj.protocolId, this@parseLink - ) - ) - }.onFailure { - Logs.w(it) - } - } - } - } } } @@ -246,7 +227,6 @@ suspend fun parseProxies(text: String): List { } } } - NekoJSInterface.Default.destroyAllJsi() return if (entities.size > entitiesByLine.size) entities else entitiesByLine } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt index ba70fc3306..54ed72a2e5 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt @@ -75,9 +75,11 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { } return MaterialAboutList.Builder() - .addCard(MaterialAboutCard.Builder() + .addCard( + MaterialAboutCard.Builder() .outline(false) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_update_24) .text(R.string.app_version) .subText(versionName) @@ -87,13 +89,15 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { ) } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_layers_24) .text(getString(R.string.version_x, "sing-box")) .subText(Libcore.versionBox()) .setOnClickAction { } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_card_giftcard_24) .text(R.string.donate) .subText(R.string.donate_info) @@ -107,9 +111,11 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { PackageCache.awaitLoadSync() for ((_, pkg) in PackageCache.installedPluginPackages) { try { - val pluginId = pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID) - if (pluginId.isNullOrBlank() || pluginId.startsWith(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN)) continue - addItem(MaterialAboutActionItem.Builder() + val pluginId = + pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID) + if (pluginId.isNullOrBlank()) continue + addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_nfc_24) .text( getString( @@ -137,7 +143,8 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager if (!pm.isIgnoringBatteryOptimizations(app.packageName)) { - addItem(MaterialAboutActionItem.Builder() + addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_running_with_errors_24) .text(R.string.ignore_battery_optimizations) .subText(R.string.ignore_battery_optimizations_sum) @@ -154,10 +161,12 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { } } .build()) - .addCard(MaterialAboutCard.Builder() + .addCard( + MaterialAboutCard.Builder() .outline(false) .title(R.string.project) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_baseline_sanitizer_24) .text(R.string.github) .setOnClickAction { @@ -167,7 +176,8 @@ class AboutFragment : ToolbarFragment(R.layout.layout_about) { ) } .build()) - .addItem(MaterialAboutActionItem.Builder() + .addItem( + MaterialAboutActionItem.Builder() .icon(R.drawable.ic_qu_shadowsocks_foreground) .text(R.string.telegram) .setOnClickAction { diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt index 6c96bc1225..436151aae0 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt @@ -15,12 +15,9 @@ import android.view.ViewGroup import android.widget.Filter import android.widget.Filterable import androidx.annotation.UiThread -import androidx.appcompat.content.res.AppCompatResources import androidx.core.util.contains import androidx.core.util.set import androidx.core.view.ViewCompat -import androidx.core.view.isGone -import androidx.core.view.isVisible import androidx.core.widget.addTextChangedListener import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.DefaultItemAnimator @@ -29,27 +26,20 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.snackbar.Snackbar import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView import io.nekohasekai.sagernet.BuildConfig -import io.nekohasekai.sagernet.Key import io.nekohasekai.sagernet.R import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.databinding.LayoutAppListBinding import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding import io.nekohasekai.sagernet.ktx.crossFadeFrom -import io.nekohasekai.sagernet.ktx.launchCustomTab import io.nekohasekai.sagernet.ktx.onMainDispatcher import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher import io.nekohasekai.sagernet.utils.PackageCache import io.nekohasekai.sagernet.widget.ListListener import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.plugin.Plugins -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.ui.Dialogs import kotlin.coroutines.coroutineContext class AppListActivity : ThemedActivity() { @@ -81,35 +71,7 @@ class AppListActivity : ThemedActivity() { item = app binding.itemicon.setImageDrawable(app.icon) binding.title.text = app.name - if (forNeko) { - val packageName = app.packageName - val ver = getCachedApps()[packageName]?.versionName ?: "" - binding.desc.text = "$packageName ($ver)" - // - binding.button.isVisible = true - binding.button.setImageDrawable( - AppCompatResources.getDrawable( - this@AppListActivity, - R.drawable.ic_baseline_info_24 - ) - ) - binding.button.setOnClickListener { - runOnIoDispatcher { - val jsi = NekoJSInterface(packageName) - jsi.init() - val about = jsi.getAbout() - jsi.destorySuspend() - Dialogs.message( - this@AppListActivity, app.name as String, - "PackageName: ${packageName}\n" + - "Version: ${ver}\n" + - "--------\n" + about - ) - } - } - } else { - binding.desc.text = "${app.packageName} (${app.uid})" - } + binding.desc.text = "${app.packageName} (${app.uid})" handlePayload(listOf(SWITCH)) } @@ -117,7 +79,6 @@ class AppListActivity : ThemedActivity() { if (payloads.contains(SWITCH)) { val selected = isProxiedApp(item) binding.itemcheck.isChecked = selected - binding.button.isVisible = forNeko && selected } } @@ -125,23 +86,6 @@ class AppListActivity : ThemedActivity() { if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true DataStore.routePackages = apps.filter { isProxiedApp(it) } .joinToString("\n") { it.packageName } - - if (forNeko) { - if (isProxiedApp(item)) { - runOnIoDispatcher { - try { - NekoPluginManager.installPlugin(item.packageName) - } catch (e: Exception) { - // failed UI - runOnUiThread { onClick(v) } - Dialogs.logExceptionAndShow(this@AppListActivity, e) { } - } - } - } else { - NekoPluginManager.removeManagedPlugin(item.packageName) - } - } - appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) } } @@ -234,11 +178,8 @@ class AppListActivity : ThemedActivity() { } } - private var forNeko = false - fun getCachedApps(): MutableMap { - val packages = - if (forNeko) PackageCache.installedPluginPackages else PackageCache.installedPackages + val packages = PackageCache.installedPackages return packages.toMutableMap().apply { remove(BuildConfig.APPLICATION_ID) } @@ -246,7 +187,6 @@ class AppListActivity : ThemedActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - forNeko = intent?.hasExtra(Key.NEKO_PLUGIN_MANAGED) == true binding = LayoutAppListBinding.inflate(layoutInflater) setContentView(binding.root) @@ -275,28 +215,13 @@ class AppListActivity : ThemedActivity() { appsAdapter.filter.filter(binding.search.text?.toString() ?: "") } - if (forNeko) { - DataStore.routePackages = DataStore.nekoPlugins - binding.search.setText(Plugins.AUTHORITIES_PREFIX_NEKO_PLUGIN) - } - - binding.searchLayout.isGone = forNeko - binding.hintNekoPlugin.isGone = !forNeko - binding.actionLearnMore.setOnClickListener { - launchCustomTab("https://matsuridayo.github.io/m-plugin/") - } - loadApps() } private var sysApps = false override fun onCreateOptionsMenu(menu: Menu): Boolean { - if (forNeko) { - menuInflater.inflate(R.menu.app_list_neko_menu, menu) - } else { - menuInflater.inflate(R.menu.app_list_menu, menu) - } + menuInflater.inflate(R.menu.app_list_menu, menu) return true } @@ -368,9 +293,6 @@ class AppListActivity : ThemedActivity() { proxiedUids.clear() DataStore.routePackages = "" apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() })) - NekoPluginManager.plugins.forEach { - NekoPluginManager.removeManagedPlugin(it) - } onMainDispatcher { appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH) } @@ -394,7 +316,6 @@ class AppListActivity : ThemedActivity() { override fun onDestroy() { loader?.cancel() - if (forNeko) DataStore.nekoPlugins = DataStore.routePackages super.onDestroy() } } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt index f82edad9da..d915244272 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt @@ -10,12 +10,15 @@ import android.text.SpannableStringBuilder import android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE import android.text.format.Formatter import android.text.style.ForegroundColorSpan -import android.view.* +import android.view.KeyEvent +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.Toolbar @@ -32,43 +35,82 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator -import io.nekohasekai.sagernet.* +import io.nekohasekai.sagernet.GroupOrder +import io.nekohasekai.sagernet.GroupType +import io.nekohasekai.sagernet.Key +import io.nekohasekai.sagernet.R +import io.nekohasekai.sagernet.SagerNet import io.nekohasekai.sagernet.aidl.TrafficData import io.nekohasekai.sagernet.bg.BaseService import io.nekohasekai.sagernet.bg.proto.UrlTest -import io.nekohasekai.sagernet.database.* +import io.nekohasekai.sagernet.database.DataStore +import io.nekohasekai.sagernet.database.GroupManager +import io.nekohasekai.sagernet.database.ProfileManager +import io.nekohasekai.sagernet.database.ProxyEntity +import io.nekohasekai.sagernet.database.ProxyGroup +import io.nekohasekai.sagernet.database.SagerDatabase import io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener -import io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding import io.nekohasekai.sagernet.databinding.LayoutProfileListBinding import io.nekohasekai.sagernet.databinding.LayoutProgressListBinding import io.nekohasekai.sagernet.fmt.AbstractBean import io.nekohasekai.sagernet.fmt.toUniversalLink import io.nekohasekai.sagernet.group.GroupUpdater import io.nekohasekai.sagernet.group.RawUpdater -import io.nekohasekai.sagernet.ktx.* +import io.nekohasekai.sagernet.ktx.FixedLinearLayoutManager +import io.nekohasekai.sagernet.ktx.Logs +import io.nekohasekai.sagernet.ktx.SubscriptionFoundException +import io.nekohasekai.sagernet.ktx.alert +import io.nekohasekai.sagernet.ktx.app +import io.nekohasekai.sagernet.ktx.dp2px +import io.nekohasekai.sagernet.ktx.getColorAttr +import io.nekohasekai.sagernet.ktx.getColour +import io.nekohasekai.sagernet.ktx.isIpAddress +import io.nekohasekai.sagernet.ktx.onMainDispatcher +import io.nekohasekai.sagernet.ktx.readableMessage +import io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher +import io.nekohasekai.sagernet.ktx.runOnLifecycleDispatcher +import io.nekohasekai.sagernet.ktx.runOnMainDispatcher +import io.nekohasekai.sagernet.ktx.scrollTo +import io.nekohasekai.sagernet.ktx.showAllowingStateLoss +import io.nekohasekai.sagernet.ktx.snackbar +import io.nekohasekai.sagernet.ktx.startFilesForResult +import io.nekohasekai.sagernet.ktx.tryToShow import io.nekohasekai.sagernet.plugin.PluginManager -import io.nekohasekai.sagernet.ui.profile.* -import io.nekohasekai.sagernet.utils.PackageCache +import io.nekohasekai.sagernet.ui.profile.ChainSettingsActivity +import io.nekohasekai.sagernet.ui.profile.HttpSettingsActivity +import io.nekohasekai.sagernet.ui.profile.HysteriaSettingsActivity +import io.nekohasekai.sagernet.ui.profile.MieruSettingsActivity +import io.nekohasekai.sagernet.ui.profile.NaiveSettingsActivity +import io.nekohasekai.sagernet.ui.profile.SSHSettingsActivity +import io.nekohasekai.sagernet.ui.profile.ShadowsocksSettingsActivity +import io.nekohasekai.sagernet.ui.profile.SocksSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TrojanGoSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TrojanSettingsActivity +import io.nekohasekai.sagernet.ui.profile.TuicSettingsActivity +import io.nekohasekai.sagernet.ui.profile.VMessSettingsActivity +import io.nekohasekai.sagernet.ui.profile.WireGuardSettingsActivity import io.nekohasekai.sagernet.widget.QRCodeDialog import io.nekohasekai.sagernet.widget.UndoSnackbarManager -import kotlinx.coroutines.* +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.launch +import kotlinx.coroutines.newFixedThreadPoolContext import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import moe.matsuri.nb4a.Protocols import moe.matsuri.nb4a.Protocols.getProtocolColor -import moe.matsuri.nb4a.plugin.NekoPluginManager import moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity import moe.matsuri.nb4a.proxy.config.ConfigSettingActivity -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import moe.matsuri.nb4a.proxy.neko.NekoSettingActivity -import moe.matsuri.nb4a.proxy.neko.canShare import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity import okhttp3.internal.closeQuietly import java.net.InetAddress import java.net.InetSocketAddress import java.net.Socket import java.net.UnknownHostException -import java.util.* +import java.util.Collections import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.atomic.AtomicInteger import java.util.zip.ZipInputStream @@ -403,38 +445,6 @@ class ConfigurationFragment @JvmOverloads constructor( startActivity(Intent(requireActivity(), ChainSettingsActivity::class.java)) } - R.id.action_new_neko -> { - val context = requireContext() - lateinit var dialog: AlertDialog - val linearLayout = LinearLayout(context).apply { - orientation = LinearLayout.VERTICAL - - NekoPluginManager.getProtocols().forEach { obj -> - LayoutAppsItemBinding.inflate(layoutInflater, this, true).apply { - itemcheck.isGone = true - button.isGone = false - itemicon.setImageDrawable( - PackageCache.installedApps[obj.plgId]?.loadIcon( - context.packageManager - ) - ) - title.text = obj.protocolId - desc.text = obj.plgId - button.setOnClickListener { - dialog.dismiss() - val intent = Intent(context, NekoSettingActivity::class.java) - intent.putExtra("plgId", obj.plgId) - intent.putExtra("protocolId", obj.protocolId) - startActivity(intent) - } - } - } - } - dialog = MaterialAlertDialogBuilder(context).setTitle(R.string.neko_plugin) - .setView(linearLayout) - .show() - } - R.id.action_update_subscription -> { val group = DataStore.currentGroup() if (group.type != GroupType.SUBSCRIPTION) { @@ -870,7 +880,6 @@ class ConfigurationFragment @JvmOverloads constructor( } } GroupManager.postReload(DataStore.currentGroupId()) - NekoJSInterface.Default.destroyAllJsi() mainJob.cancel() testJobs.forEach { it.cancel() } } @@ -1591,7 +1600,7 @@ class ConfigurationFragment @JvmOverloads constructor( removeButton.isGone = select proxyEntity.nekoBean?.apply { - shareLayout.isGone = !canShare() + shareLayout.isGone = true } runOnDefaultDispatcher { diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt index ed5dd6e094..e8a17fc50b 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt @@ -260,7 +260,7 @@ class MainActivity : ThemedActivity(), } .setNeutralButton(android.R.string.cancel, null) .setNeutralButton(R.string.action_learn_more) { _, _ -> - launchCustomTab("https://matsuridayo.github.io/m-plugin/") + launchCustomTab("https://matsuridayo.github.io/nb4a-plugin/") } .show() } diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt index 4de4242a08..5c91718c97 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt @@ -16,13 +16,11 @@ import io.nekohasekai.sagernet.database.DataStore import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers import io.nekohasekai.sagernet.ktx.* import io.nekohasekai.sagernet.utils.Theme -import io.nekohasekai.sagernet.widget.AppListPreference import moe.matsuri.nb4a.ui.* class SettingsPreferenceFragment : PreferenceFragmentCompat() { private lateinit var isProxyApps: SwitchPreference - private lateinit var nekoPlugins: AppListPreference override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -40,16 +38,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { DataStore.initGlobal() addPreferencesFromResource(R.xml.global_preferences) - DataStore.routePackages = DataStore.nekoPlugins - nekoPlugins = findPreference(Key.NEKO_PLUGIN_MANAGED)!! - nekoPlugins.setOnPreferenceClickListener { - // borrow from route app settings - startActivity(Intent( - context, AppListActivity::class.java - ).apply { putExtra(Key.NEKO_PLUGIN_MANAGED, true) }) - true - } - val appTheme = findPreference(Key.APP_THEME)!! appTheme.setOnPreferenceChangeListener { _, newTheme -> if (DataStore.serviceState.started) { @@ -183,9 +171,6 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() { if (::isProxyApps.isInitialized) { isProxyApps.isChecked = DataStore.proxyApps } - if (::nekoPlugins.isInitialized) { - nekoPlugins.postUpdate() - } } } \ No newline at end of file diff --git a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt index 2ffaef8b72..a2e09ef589 100644 --- a/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt +++ b/nekobox-android/app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt @@ -51,7 +51,7 @@ object PackageCache { }.associateBy { it.packageName } installedPluginPackages = rawPackageInfo.filter { - Plugins.isExeOrPlugin(it) + Plugins.isExe(it) }.associateBy { it.packageName } val installed = app.packageManager.getInstalledApplications(PackageManager.GET_META_DATA) diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt deleted file mode 100644 index d7ae50e102..0000000000 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/NekoPluginManager.kt +++ /dev/null @@ -1,153 +0,0 @@ -package moe.matsuri.nb4a.plugin - -import io.nekohasekai.sagernet.R -import io.nekohasekai.sagernet.SagerNet -import io.nekohasekai.sagernet.bg.BaseService -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.forEach -import io.nekohasekai.sagernet.utils.PackageCache -import moe.matsuri.nb4a.proxy.neko.NekoJSInterface -import okhttp3.internal.closeQuietly -import org.json.JSONObject -import java.io.File -import java.util.zip.CRC32 -import java.util.zip.ZipFile - -object NekoPluginManager { - const val managerVersion = 2 - - val plugins get() = DataStore.nekoPlugins.split("\n").filter { it.isNotBlank() } - - // plgID to plgConfig object - fun getManagedPlugins(): Map { - val ret = mutableMapOf() - plugins.forEach { - tryGetPlgConfig(it)?.apply { - ret[it] = this - } - } - return ret - } - - class Protocol( - val protocolId: String, val plgId: String, val protocolConfig: JSONObject - ) - - fun getProtocols(): List { - val ret = mutableListOf() - getManagedPlugins().forEach { (t, u) -> - u.optJSONArray("protocols")?.forEach { _, any -> - if (any is JSONObject) { - val name = any.optString("protocolId") - ret.add(Protocol(name, t, any)) - } - } - } - return ret - } - - fun findProtocol(protocolId: String): Protocol? { - getManagedPlugins().forEach { (t, u) -> - u.optJSONArray("protocols")?.forEach { _, any -> - if (any is JSONObject) { - if (protocolId == any.optString("protocolId")) { - return Protocol(protocolId, t, any) - } - } - } - } - return null - } - - fun removeManagedPlugin(plgId: String) { - DataStore.configurationStore.remove(plgId) - val dir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - if (dir.exists()) { - dir.deleteRecursively() - } - } - - fun extractPlugin(plgId: String, install: Boolean) { - val app = PackageCache.installedApps[plgId] ?: return - val apk = File(app.publicSourceDir) - if (!apk.exists()) { - return - } - if (!install && !plugins.contains(plgId)) { - return - } - - val zipFile = ZipFile(apk) - val unzipDir = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - unzipDir.mkdirs() - for (entry in zipFile.entries()) { - if (entry.name.startsWith("assets/")) { - val relativePath = entry.name.removePrefix("assets/") - val outFile = File(unzipDir, relativePath) - if (entry.isDirectory) { - outFile.mkdirs() - continue - } - - if (outFile.isDirectory) { - outFile.delete() - } else if (outFile.exists()) { - val checksum = CRC32() - checksum.update(outFile.readBytes()) - if (checksum.value == entry.crc) { - continue - } - } - - val input = zipFile.getInputStream(entry) - outFile.outputStream().use { - input.copyTo(it) - } - } - } - zipFile.closeQuietly() - } - - suspend fun installPlugin(plgId: String) { - if (plgId == "moe.matsuri.plugin.singbox" || plgId == "moe.matsuri.plugin.xray") { - throw Exception("This plugin is deprecated") - } - extractPlugin(plgId, true) - NekoJSInterface.Default.destroyJsi(plgId) - NekoJSInterface.Default.requireJsi(plgId).init() - NekoJSInterface.Default.destroyJsi(plgId) - } - - const val PLUGIN_APP_VERSION = "_v_vc" - const val PLUGIN_APP_VERSION_NAME = "_v_vn" - - // Return null if not managed - fun tryGetPlgConfig(plgId: String): JSONObject? { - return try { - JSONObject(DataStore.configurationStore.getString(plgId)!!) - } catch (e: Exception) { - null - } - } - - fun updatePlgConfig(plgId: String, plgConfig: JSONObject) { - PackageCache.installedPluginPackages[plgId]?.apply { - // longVersionCode requires API 28 -// plgConfig.put(PLUGIN_APP_VERSION, versionCode) - plgConfig.put(PLUGIN_APP_VERSION_NAME, versionName) - } - DataStore.configurationStore.putString(plgId, plgConfig.toString()) - } - - fun htmlPath(plgId: String): String { - val htmlFile = File(SagerNet.application.filesDir.absolutePath + "/plugins/" + plgId) - return htmlFile.absolutePath - } - - class PluginInternalException(val protocolId: String) : Exception(), - BaseService.ExpectedException { - override fun getLocalizedMessage() = - SagerNet.application.getString(R.string.neko_plugin_internal_error, protocolId) - } - -} \ No newline at end of file diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt index 43b80bbc49..a584cadfa0 100644 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt +++ b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt @@ -14,20 +14,18 @@ import io.nekohasekai.sagernet.utils.PackageCache object Plugins { const val AUTHORITIES_PREFIX_SEKAI_EXE = "io.nekohasekai.sagernet.plugin." const val AUTHORITIES_PREFIX_NEKO_EXE = "moe.matsuri.exe." - const val AUTHORITIES_PREFIX_NEKO_PLUGIN = "moe.matsuri.plugin." const val ACTION_NATIVE_PLUGIN = "io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN" const val METADATA_KEY_ID = "io.nekohasekai.sagernet.plugin.id" const val METADATA_KEY_EXECUTABLE_PATH = "io.nekohasekai.sagernet.plugin.executable_path" - fun isExeOrPlugin(pkg: PackageInfo): Boolean { + fun isExe(pkg: PackageInfo): Boolean { if (pkg.providers?.isEmpty() == true) return false val provider = pkg.providers?.get(0) ?: return false val auth = provider.authority ?: return false return auth.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE) || auth.startsWith(AUTHORITIES_PREFIX_NEKO_EXE) - || auth.startsWith(AUTHORITIES_PREFIX_NEKO_PLUGIN) } fun preferExePrefix(): String { diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java index 3a86b7fcec..434d549235 100644 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java +++ b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java @@ -8,18 +8,12 @@ import com.esotericsoftware.kryo.io.ByteBufferOutput; import org.jetbrains.annotations.NotNull; import org.json.JSONObject; -import io.nekohasekai.sagernet.R; -import io.nekohasekai.sagernet.SagerNet; import io.nekohasekai.sagernet.fmt.AbstractBean; import io.nekohasekai.sagernet.fmt.KryoConverters; import io.nekohasekai.sagernet.ktx.Logs; -import moe.matsuri.nb4a.plugin.NekoPluginManager; public class NekoBean extends AbstractBean { - // BoxInstance use this - public JSONObject allConfig = null; - public String plgId; public String protocolId; public JSONObject sharedStorage = new JSONObject(); @@ -62,31 +56,22 @@ public class NekoBean extends AbstractBean { } public String displayType() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - String neko = SagerNet.application.getResources().getString(R.string.neko_plugin); - if (p == null) return neko; - return p.getProtocolId(); + return "invalid"; } @Override public boolean canMapping() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canMapping"); + return false; } @Override public boolean canICMPing() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canICMPing"); + return false; } @Override public boolean canTCPing() { - NekoPluginManager.Protocol p = NekoPluginManager.INSTANCE.findProtocol(protocolId); - if (p == null) return false; - return p.getProtocolConfig().optBoolean("canTCPing"); + return false; } @NotNull diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt deleted file mode 100644 index eb29e538a6..0000000000 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoFmt.kt +++ /dev/null @@ -1,123 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.Logs -import io.nekohasekai.sagernet.ktx.getStr -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher -import libcore.Libcore -import moe.matsuri.nb4a.Protocols -import moe.matsuri.nb4a.plugin.NekoPluginManager -import org.json.JSONObject -import kotlin.coroutines.resume -import kotlin.coroutines.suspendCoroutine - -suspend fun parseShareLink(plgId: String, protocolId: String, link: String): NekoBean = - suspendCoroutine { - runOnIoDispatcher { - val jsi = NekoJSInterface.Default.requireJsi(plgId) - jsi.lock() - - try { - jsi.init() - - val jsip = jsi.switchProtocol(protocolId) - val sharedStorage = jsip.parseShareLink(link) - - // NekoBean from link - val bean = NekoBean() - bean.plgId = plgId - bean.protocolId = protocolId - bean.sharedStorage = NekoBean.tryParseJSON(sharedStorage) - bean.onSharedStorageSet() - - it.resume(bean) - } catch (e: Exception) { - Logs.e(e) - it.resume(NekoBean().apply { - this.plgId = plgId - this.protocolId = protocolId - }) - } - - jsi.unlock() - // destroy when all link parsed - } - } - -fun NekoBean.shareLink(): String { - return sharedStorage.optString("shareLink") -} - -// Only run in bg process -// seems no concurrent -suspend fun NekoBean.updateAllConfig(port: Int) = suspendCoroutine { - allConfig = null - - runOnIoDispatcher { - val jsi = NekoJSInterface.Default.requireJsi(plgId) - jsi.lock() - - try { - jsi.init() - val jsip = jsi.switchProtocol(protocolId) - - // runtime arguments - val otherArgs = mutableMapOf() - otherArgs["finalAddress"] = finalAddress - otherArgs["finalPort"] = finalPort -// otherArgs["muxEnabled"] = Protocols.shouldEnableMux(protocolId) -// otherArgs["muxConcurrency"] = DataStore.muxConcurrency - - val ret = jsip.buildAllConfig(port, this@updateAllConfig, otherArgs) - - // result - allConfig = JSONObject(ret) - } catch (e: Exception) { - Logs.e(e) - } - - jsi.unlock() - it.resume(Unit) - // destroy when config generated / all tests finished - } -} - -fun NekoBean.cacheGet(id: String): String? { - return DataStore.profileCacheStore.getString("neko_${hash()}_$id") -} - -fun NekoBean.cacheSet(id: String, value: String) { - DataStore.profileCacheStore.putString("neko_${hash()}_$id", value) -} - -fun NekoBean.hash(): String { - var a = plgId - a += protocolId - a += sharedStorage.toString() - return Libcore.sha256Hex(a.toByteArray()) -} - -// must call it to update something like serverAddress -fun NekoBean.onSharedStorageSet() { - serverAddress = sharedStorage.getStr("serverAddress") - serverPort = sharedStorage.getStr("serverPort")?.toInt() ?: 1080 - if (serverAddress == null || serverAddress.isBlank()) { - serverAddress = "127.0.0.1" - } - name = sharedStorage.optString("name") -} - -fun NekoBean.needBypassRootUid(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("needBypassRootUid") -} - -fun NekoBean.haveStandardLink(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("haveStandardLink") -} - -fun NekoBean.canShare(): Boolean { - val p = NekoPluginManager.findProtocol(protocolId) ?: return false - return p.protocolConfig.optBoolean("canShare") -} diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt deleted file mode 100644 index df165c75f2..0000000000 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoJSInterface.kt +++ /dev/null @@ -1,388 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import android.annotation.SuppressLint -import android.webkit.* -import android.widget.Toast -import androidx.preference.Preference -import androidx.preference.PreferenceScreen -import io.nekohasekai.sagernet.BuildConfig -import io.nekohasekai.sagernet.SagerNet -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.plugin.NekoPluginManager -import moe.matsuri.nb4a.ui.SimpleMenuPreference -import moe.matsuri.nb4a.utils.JavaUtil -import moe.matsuri.nb4a.utils.Util -import moe.matsuri.nb4a.utils.WebViewUtil -import org.json.JSONObject -import java.io.File -import java.io.FileInputStream -import java.util.* -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine - -class NekoJSInterface(val plgId: String) { - - private val mutex = Mutex() - private var webView: WebView? = null - val jsObject = JsObject() - var plgConfig: JSONObject? = null - var plgConfigException: Exception? = null - val protocols = mutableMapOf() - val loaded = AtomicBoolean() - - suspend fun lock() { - mutex.lock(null) - } - - fun unlock() { - mutex.unlock(null) - } - - // load webview and js - // Return immediately when already loaded - // Return plgConfig or throw exception - suspend fun init() = withContext(Dispatchers.Main) { - initInternal() - } - - @SuppressLint("SetJavaScriptEnabled") - private suspend fun initInternal() = suspendCoroutine { - if (loaded.get()) { - plgConfig?.apply { - it.resume(this) - return@suspendCoroutine - } - plgConfigException?.apply { - it.resumeWithException(this) - return@suspendCoroutine - } - it.resumeWithException(Exception("wtf")) - return@suspendCoroutine - } - - WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG) - NekoPluginManager.extractPlugin(plgId, false) - - webView = WebView(SagerNet.application.applicationContext) - webView!!.settings.javaScriptEnabled = true - webView!!.addJavascriptInterface(jsObject, "neko") - webView!!.webViewClient = object : WebViewClient() { - // provide files - override fun shouldInterceptRequest( - view: WebView?, request: WebResourceRequest? - ): WebResourceResponse { - return WebViewUtil.interceptRequest( - { res -> - val f = File(NekoPluginManager.htmlPath(plgId), res) - if (f.exists()) { - FileInputStream(f) - } else { - null - } - }, - view, - request - ) - } - - override fun onReceivedError( - view: WebView?, request: WebResourceRequest?, error: WebResourceError? - ) { - WebViewUtil.onReceivedError(view, request, error) - } - - override fun onPageFinished(view: WebView?, url: String?) { - super.onPageFinished(view, url) - if (loaded.getAndSet(true)) return - - runOnIoDispatcher { - // Process nekoInit - var ret = "" - try { - ret = nekoInit() - val obj = JSONObject(ret) - if (!obj.getBoolean("ok")) { - throw Exception("plugin refuse to run: ${obj.optString("reason")}") - } - val min = obj.getInt("minVersion") - if (min > NekoPluginManager.managerVersion) { - throw Exception("manager version ${NekoPluginManager.managerVersion} too old, this plugin requires >= $min") - } - plgConfig = obj - NekoPluginManager.updatePlgConfig(plgId, obj) - it.resume(obj) - } catch (e: Exception) { - val e2 = Exception("nekoInit: " + e.readableMessage + "\n\n" + ret) - plgConfigException = e2 - it.resumeWithException(e2) - } - } - } - } - webView!!.loadUrl("http://$plgId/plugin.html") - } - - // Android call JS - - private suspend fun callJS(script: String): String = suspendCoroutine { - val jsLatch = CountDownLatch(1) - var jsReceivedValue = "" - - runOnMainDispatcher { - if (webView != null) { - webView!!.evaluateJavascript(script) { value -> - jsReceivedValue = value - jsLatch.countDown() - } - } else { - jsReceivedValue = "webView is null" - jsLatch.countDown() - } - } - - jsLatch.await(5, TimeUnit.SECONDS) - - // evaluateJavascript escapes Javascript's String - jsReceivedValue = JavaUtil.unescapeString(jsReceivedValue.removeSurrounding("\"")) - if (BuildConfig.DEBUG) Logs.d("$script: $jsReceivedValue") - it.resume(jsReceivedValue) - } - - // call once - private suspend fun nekoInit(): String { - val sendData = JSONObject() - sendData.put("lang", Locale.getDefault().toLanguageTag()) - sendData.put("plgId", plgId) - sendData.put("managerVersion", NekoPluginManager.managerVersion) - - return callJS( - "nekoInit(\"${ - Util.b64EncodeUrlSafe( - sendData.toString().toByteArray() - ) - }\")" - ) - } - - fun switchProtocol(id: String): NekoProtocol { - lateinit var p: NekoProtocol - if (protocols.containsKey(id)) { - p = protocols[id]!! - } else { - p = NekoProtocol(id) { callJS(it) } - protocols[id] = p - } - jsObject.protocol = p - return p - } - - suspend fun getAbout(): String { - return callJS("nekoAbout()") - } - - inner class NekoProtocol(val protocolId: String, val callJS: suspend (String) -> String) { - private suspend fun callProtocol(method: String, b64Str: String?): String { - var arg = "" - if (b64Str != null) { - arg = "\"" + b64Str + "\"" - } - return callJS("nekoProtocol(\"$protocolId\").$method($arg)") - } - - suspend fun buildAllConfig( - port: Int, bean: NekoBean, otherArgs: Map? - ): String { - val sendData = JSONObject() - sendData.put("port", port) - sendData.put( - "sharedStorage", - Util.b64EncodeUrlSafe(bean.sharedStorage.toString().toByteArray()) - ) - otherArgs?.forEach { (t, u) -> sendData.put(t, u) } - - return callProtocol( - "buildAllConfig", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - suspend fun parseShareLink(shareLink: String): String { - val sendData = JSONObject() - sendData.put("shareLink", shareLink) - - return callProtocol( - "parseShareLink", Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - // UI Interface - - suspend fun setSharedStorage(sharedStorage: String) { - callProtocol( - "setSharedStorage", - Util.b64EncodeUrlSafe(sharedStorage.toByteArray()) - ) - } - - suspend fun requireSetProfileCache() { - callProtocol("requireSetProfileCache", null) - } - - suspend fun requirePreferenceScreenConfig(): String { - return callProtocol("requirePreferenceScreenConfig", null) - } - - suspend fun sharedStorageFromProfileCache(): String { - return callProtocol("sharedStorageFromProfileCache", null) - } - - suspend fun onPreferenceCreated() { - callProtocol("onPreferenceCreated", null) - } - - suspend fun onPreferenceChanged(key: String, v: Any) { - val sendData = JSONObject() - sendData.put("key", key) - sendData.put("newValue", v) - - callProtocol( - "onPreferenceChanged", - Util.b64EncodeUrlSafe(sendData.toString().toByteArray()) - ) - } - - } - - inner class JsObject { - var preferenceScreen: PreferenceScreen? = null - var protocol: NekoProtocol? = null - - // JS call Android - - @JavascriptInterface - fun toast(s: String) { - Toast.makeText(SagerNet.application.applicationContext, s, Toast.LENGTH_SHORT).show() - } - - @JavascriptInterface - fun logError(s: String) { - Logs.e("logError: $s") - } - - @JavascriptInterface - fun setPreferenceVisibility(key: String, isVisible: Boolean) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.isVisible = isVisible - } - } - - @JavascriptInterface - fun setPreferenceTitle(key: String, title: String) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.title = title - } - } - - @JavascriptInterface - fun setMenu(key: String, entries: String) { - runBlockingOnMainDispatcher { - preferenceScreen?.findPreference(key)?.apply { - NekoPreferenceInflater.setMenu(this, JSONObject(entries)) - } - } - } - - @JavascriptInterface - fun listenOnPreferenceChanged(key: String) { - preferenceScreen?.findPreference(key) - ?.setOnPreferenceChangeListener { preference, newValue -> - runOnIoDispatcher { - protocol?.onPreferenceChanged(preference.key, newValue) - } - true - } - } - - @JavascriptInterface - fun setKV(type: Int, key: String, jsonStr: String) { - try { - val v = JSONObject(jsonStr) - when (type) { - 0 -> DataStore.profileCacheStore.putBoolean(key, v.getBoolean("v")) - 1 -> DataStore.profileCacheStore.putFloat(key, v.getDouble("v").toFloat()) - 2 -> DataStore.profileCacheStore.putInt(key, v.getInt("v")) - 3 -> DataStore.profileCacheStore.putLong(key, v.getLong("v")) - 4 -> DataStore.profileCacheStore.putString(key, v.getString("v")) - } - } catch (e: Exception) { - Logs.e("setKV: $e") - } - } - - @JavascriptInterface - fun getKV(type: Int, key: String): String { - val v = JSONObject() - try { - when (type) { - 0 -> v.put("v", DataStore.profileCacheStore.getBoolean(key)) - 1 -> v.put("v", DataStore.profileCacheStore.getFloat(key)) - 2 -> v.put("v", DataStore.profileCacheStore.getInt(key)) - 3 -> v.put("v", DataStore.profileCacheStore.getLong(key)) - 4 -> v.put("v", DataStore.profileCacheStore.getString(key)) - } - } catch (e: Exception) { - Logs.e("getKV: $e") - } - return v.toString() - } - - } - - fun destroy() { - webView?.onPause() - webView?.removeAllViews() - webView?.destroy() - webView = null - } - - suspend fun destorySuspend() = withContext(Dispatchers.Main) { - destroy() - } - - object Default { - val map = mutableMapOf() - - suspend fun destroyJsi(plgId: String) = withContext(Dispatchers.Main) { - if (map.containsKey(plgId)) { - map[plgId]!!.destroy() - map.remove(plgId) - } - } - - // now it's manually managed - suspend fun destroyAllJsi() = withContext(Dispatchers.Main) { - map.forEach { (t, u) -> - u.destroy() - map.remove(t) - } - } - - suspend fun requireJsi(plgId: String): NekoJSInterface = withContext(Dispatchers.Main) { - lateinit var jsi: NekoJSInterface - if (map.containsKey(plgId)) { - jsi = map[plgId]!! - } else { - jsi = NekoJSInterface(plgId) - map[plgId] = jsi - } - return@withContext jsi - } - } -} diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt deleted file mode 100644 index cd2fdff2e5..0000000000 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoPreferenceInflater.kt +++ /dev/null @@ -1,97 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import androidx.preference.* -import io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers -import io.nekohasekai.sagernet.ktx.forEach -import io.nekohasekai.sagernet.ktx.getStr -import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import moe.matsuri.nb4a.ui.SimpleMenuPreference -import moe.matsuri.nb4a.utils.getDrawableByName -import org.json.JSONArray -import org.json.JSONObject - -object NekoPreferenceInflater { - suspend fun inflate(pref: JSONArray, preferenceScreen: PreferenceScreen) = - withContext(Dispatchers.Main) { - val context = preferenceScreen.context - pref.forEach { _, category -> - category as JSONObject - - val preferenceCategory = PreferenceCategory(context) - preferenceScreen.addPreference(preferenceCategory) - - category.getStr("key")?.apply { preferenceCategory.key = this } - category.getStr("title")?.apply { preferenceCategory.title = this } - - category.optJSONArray("preferences")?.forEach { _, any -> - if (any is JSONObject) { - lateinit var p: Preference - // Create Preference - when (any.getStr("type")) { - "EditTextPreference" -> { - p = EditTextPreference(context).apply { - when (any.getStr("summaryProvider")) { - null -> summaryProvider = - EditTextPreference.SimpleSummaryProvider.getInstance() - "PasswordSummaryProvider" -> summaryProvider = - ProfileSettingsActivity.PasswordSummaryProvider - } - when (any.getStr("EditTextPreferenceModifiers")) { - "Monospace" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Monospace - ) - "Hosts" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Hosts - ) - "Port" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Port - ) - "Number" -> setOnBindEditTextListener( - EditTextPreferenceModifiers.Number - ) - } - } - } - "SwitchPreference" -> { - p = SwitchPreference(context) - } - "SimpleMenuPreference" -> { - p = SimpleMenuPreference(context).apply { - val entries = any.optJSONObject("entries") - if (entries != null) setMenu(this, entries) - } - } - } - // Set key & title - p.key = any.getString("key") - any.getStr("title")?.apply { p.title = this } - // Set icon - any.getStr("icon")?.apply { - p.icon = context.getDrawableByName(this) - } - // Set summary - any.getStr("summary")?.apply { - p.summary = this - } - // Add to category - preferenceCategory.addPreference(p) - } - } - } - } - - fun setMenu(p: SimpleMenuPreference, entries: JSONObject) { - val menuEntries = mutableListOf() - val menuEntryValues = mutableListOf() - entries.forEach { s, b -> - menuEntryValues.add(s) - menuEntries.add(b as String) - } - entries.apply { - p.entries = menuEntries.toTypedArray() - p.entryValues = menuEntryValues.toTypedArray() - } - } -} \ No newline at end of file diff --git a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt b/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt deleted file mode 100644 index e167fd6e47..0000000000 --- a/nekobox-android/app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoSettingActivity.kt +++ /dev/null @@ -1,102 +0,0 @@ -package moe.matsuri.nb4a.proxy.neko - -import android.os.Bundle -import android.view.View -import androidx.core.view.isVisible -import androidx.preference.PreferenceDataStore -import androidx.preference.PreferenceFragmentCompat -import io.nekohasekai.sagernet.Key -import io.nekohasekai.sagernet.R -import io.nekohasekai.sagernet.database.DataStore -import io.nekohasekai.sagernet.ktx.runOnIoDispatcher -import io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity -import moe.matsuri.nb4a.ui.Dialogs -import org.json.JSONArray - -class NekoSettingActivity : ProfileSettingsActivity() { - - lateinit var jsi: NekoJSInterface - lateinit var jsip: NekoJSInterface.NekoProtocol - lateinit var plgId: String - lateinit var protocolId: String - var loaded = false - - override fun createEntity() = NekoBean() - - override fun NekoBean.init() { - if (!this@NekoSettingActivity::plgId.isInitialized) this@NekoSettingActivity.plgId = plgId - if (!this@NekoSettingActivity::protocolId.isInitialized) this@NekoSettingActivity.protocolId = protocolId - DataStore.profileCacheStore.putString("name", name) - DataStore.sharedStorage = sharedStorage.toString() - } - - override fun NekoBean.serialize() { - // NekoBean from input - plgId = this@NekoSettingActivity.plgId - protocolId = this@NekoSettingActivity.protocolId - - sharedStorage = NekoBean.tryParseJSON(DataStore.sharedStorage) - onSharedStorageSet() - } - - override fun onCreate(savedInstanceState: Bundle?) { - intent?.getStringExtra("plgId")?.apply { plgId = this } - intent?.getStringExtra("protocolId")?.apply { protocolId = this } - super.onCreate(savedInstanceState) - } - - override fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) { - listView.isVisible = false - } - - override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { - if (loaded && key != Key.PROFILE_DIRTY) { - DataStore.dirty = true - } - } - - override fun PreferenceFragmentCompat.createPreferences( - savedInstanceState: Bundle?, - rootKey: String?, - ) { - addPreferencesFromResource(R.xml.neko_preferences) - - // Create a jsi - jsi = NekoJSInterface(plgId) - runOnIoDispatcher { - try { - jsi.init() - jsip = jsi.switchProtocol(protocolId) - jsi.jsObject.preferenceScreen = preferenceScreen - - // Because of the Preference problem, first require the KV and then inflate the UI - jsip.setSharedStorage(DataStore.sharedStorage) - jsip.requireSetProfileCache() - - val config = jsip.requirePreferenceScreenConfig() - val pref = JSONArray(config) - - NekoPreferenceInflater.inflate(pref, preferenceScreen) - jsip.onPreferenceCreated() - - runOnUiThread { - loaded = true - listView.isVisible = true - } - } catch (e: Exception) { - Dialogs.logExceptionAndShow(this@NekoSettingActivity, e) { finish() } - } - } - } - - override suspend fun saveAndExit() { - DataStore.sharedStorage = jsip.sharedStorageFromProfileCache() - super.saveAndExit() // serialize & finish - } - - override fun onDestroy() { - jsi.destroy() - super.onDestroy() - } - -} \ No newline at end of file diff --git a/nekobox-android/app/src/main/res/layout/layout_app_list.xml b/nekobox-android/app/src/main/res/layout/layout_app_list.xml index f013d4dbaa..45432fa452 100644 --- a/nekobox-android/app/src/main/res/layout/layout_app_list.xml +++ b/nekobox-android/app/src/main/res/layout/layout_app_list.xml @@ -82,61 +82,6 @@ - - - - - - - - - - - - - - -