diff --git a/.github/update.log b/.github/update.log index 832eab3bb8..158372f5c8 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1213,3 +1213,4 @@ Update On Fri Dec 12 19:42:07 CET 2025 Update On Sat Dec 13 19:37:32 CET 2025 Update On Sun Dec 14 19:39:26 CET 2025 Update On Mon Dec 15 19:43:13 CET 2025 +Update On Tue Dec 16 19:42:39 CET 2025 diff --git a/clash-meta/adapter/outbound/sudoku.go b/clash-meta/adapter/outbound/sudoku.go index f9313ca39f..bd393ec616 100644 --- a/clash-meta/adapter/outbound/sudoku.go +++ b/clash-meta/adapter/outbound/sudoku.go @@ -20,17 +20,19 @@ type Sudoku struct { type SudokuOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Key string `proxy:"key"` - AEADMethod string `proxy:"aead-method,omitempty"` - PaddingMin *int `proxy:"padding-min,omitempty"` - PaddingMax *int `proxy:"padding-max,omitempty"` - TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" - EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"` - HTTPMask bool `proxy:"http-mask,omitempty"` - CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Key string `proxy:"key"` + AEADMethod string `proxy:"aead-method,omitempty"` + PaddingMin *int `proxy:"padding-min,omitempty"` + PaddingMax *int `proxy:"padding-max,omitempty"` + TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"` + HTTPMask bool `proxy:"http-mask,omitempty"` + HTTPMaskStrategy string `proxy:"http-mask-strategy,omitempty"` // "random" (default), "post", "websocket" + CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + CustomTables []string `proxy:"custom-tables,omitempty"` // optional table rotation patterns, overrides custom-table when non-empty } // DialContext implements C.ProxyAdapter @@ -54,7 +56,9 @@ func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con defer done(&err) } - c, err = sudoku.ClientHandshake(c, cfg) + c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{ + HTTPMaskStrategy: s.option.HTTPMaskStrategy, + }) if err != nil { return nil, err } @@ -97,7 +101,9 @@ func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata) defer done(&err) } - c, err = sudoku.ClientHandshake(c, cfg) + c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{ + HTTPMaskStrategy: s.option.HTTPMaskStrategy, + }) if err != nil { return nil, err } @@ -185,11 +191,15 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, DisableHTTPMask: !option.HTTPMask, } - table, err := sudoku.NewTableWithCustom(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable) + tables, err := sudoku.NewTablesWithCustomPatterns(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable, option.CustomTables) if err != nil { - return nil, fmt.Errorf("build table failed: %w", err) + return nil, fmt.Errorf("build table(s) failed: %w", err) + } + if len(tables) == 1 { + baseConf.Table = tables[0] + } else { + baseConf.Tables = tables } - baseConf.Table = table if option.AEADMethod != "" { baseConf.AEADMethod = option.AEADMethod } diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index 26b1af2c90..04d15bd202 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -1049,7 +1049,9 @@ proxies: # socks5 padding-max: 7 # 最大填充字节数 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy` + # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table http-mask: true # 是否启用http掩码 + # http-mask-strategy: random # 可选:random(默认)、post、websocket;仅在 http-mask=true 时生效 enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与服务端端保持相同(如果此处为false,则要求aead不可为none) # anytls @@ -1591,6 +1593,7 @@ listeners: padding-max: 15 # 填充最大长度,均不建议过大 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy` + # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table handshake-timeout: 5 # optional enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与客户端保持相同(如果此处为false,则要求aead不可为none) diff --git a/clash-meta/go.mod b/clash-meta/go.mod index 8fb7e51f1f..c0160c2630 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -43,7 +43,7 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 - github.com/saba-futai/sudoku v0.0.2-c + github.com/saba-futai/sudoku v0.0.2-d github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 08d1d002a8..c6fba8e505 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/saba-futai/sudoku v0.0.2-c h1:0CaoCKx4Br8UL97fnIxn8Y7rnQpflBza7kfaIrdg2rI= -github.com/saba-futai/sudoku v0.0.2-c/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= +github.com/saba-futai/sudoku v0.0.2-d h1:HW/gIyNUFcDchpMN+ZhluM86U/HGkWkkRV+9Km6WZM8= +github.com/saba-futai/sudoku v0.0.2-d/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/clash-meta/listener/config/sudoku.go b/clash-meta/listener/config/sudoku.go index b581f3f5ce..848db875d7 100644 --- a/clash-meta/listener/config/sudoku.go +++ b/clash-meta/listener/config/sudoku.go @@ -9,16 +9,17 @@ import ( // SudokuServer describes a Sudoku inbound server configuration. // It is internal to the listener layer and mainly used for logging and wiring. type SudokuServer struct { - Enable bool `json:"enable"` - Listen string `json:"listen"` - Key string `json:"key"` - AEADMethod string `json:"aead-method,omitempty"` - PaddingMin *int `json:"padding-min,omitempty"` - PaddingMax *int `json:"padding-max,omitempty"` - TableType string `json:"table-type,omitempty"` - HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` - EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"` - CustomTable string `json:"custom-table,omitempty"` + Enable bool `json:"enable"` + Listen string `json:"listen"` + Key string `json:"key"` + AEADMethod string `json:"aead-method,omitempty"` + PaddingMin *int `json:"padding-min,omitempty"` + PaddingMax *int `json:"padding-max,omitempty"` + TableType string `json:"table-type,omitempty"` + HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` + EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"` + CustomTable string `json:"custom-table,omitempty"` + CustomTables []string `json:"custom-tables,omitempty"` // mihomo private extension (not the part of standard Sudoku protocol) MuxOption sing.MuxOption `json:"mux-option,omitempty"` diff --git a/clash-meta/listener/inbound/sudoku.go b/clash-meta/listener/inbound/sudoku.go index 6c4092473c..433976026d 100644 --- a/clash-meta/listener/inbound/sudoku.go +++ b/clash-meta/listener/inbound/sudoku.go @@ -13,14 +13,15 @@ import ( type SudokuOption struct { BaseOption - Key string `inbound:"key"` - AEADMethod string `inbound:"aead-method,omitempty"` - PaddingMin *int `inbound:"padding-min,omitempty"` - PaddingMax *int `inbound:"padding-max,omitempty"` - TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" - HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` - EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"` - CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + Key string `inbound:"key"` + AEADMethod string `inbound:"aead-method,omitempty"` + PaddingMin *int `inbound:"padding-min,omitempty"` + PaddingMax *int `inbound:"padding-max,omitempty"` + TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` + EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"` + CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + CustomTables []string `inbound:"custom-tables,omitempty"` // mihomo private extension (not the part of standard Sudoku protocol) MuxOption MuxOption `inbound:"mux-option,omitempty"` @@ -57,6 +58,7 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) { HandshakeTimeoutSecond: options.HandshakeTimeoutSecond, EnablePureDownlink: options.EnablePureDownlink, CustomTable: options.CustomTable, + CustomTables: options.CustomTables, } serverConf.MuxOption = options.MuxOption.Build() diff --git a/clash-meta/listener/sudoku/server.go b/clash-meta/listener/sudoku/server.go index 8351e36597..e90e231c1d 100644 --- a/clash-meta/listener/sudoku/server.go +++ b/clash-meta/listener/sudoku/server.go @@ -166,7 +166,7 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) enablePureDownlink = *config.EnablePureDownlink } - table, err := sudoku.NewTableWithCustom(config.Key, tableType, config.CustomTable) + tables, err := sudoku.NewTablesWithCustomPatterns(config.Key, tableType, config.CustomTable, config.CustomTables) if err != nil { _ = l.Close() return nil, err @@ -180,12 +180,16 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) protoConf := sudoku.ProtocolConfig{ Key: config.Key, AEADMethod: defaultConf.AEADMethod, - Table: table, PaddingMin: paddingMin, PaddingMax: paddingMax, EnablePureDownlink: enablePureDownlink, HandshakeTimeoutSeconds: handshakeTimeout, } + if len(tables) == 1 { + protoConf.Table = tables[0] + } else { + protoConf.Tables = tables + } if config.AEADMethod != "" { protoConf.AEADMethod = config.AEADMethod } diff --git a/clash-meta/transport/sudoku/features_test.go b/clash-meta/transport/sudoku/features_test.go new file mode 100644 index 0000000000..8eb3aedd25 --- /dev/null +++ b/clash-meta/transport/sudoku/features_test.go @@ -0,0 +1,192 @@ +package sudoku + +import ( + "bytes" + "io" + "net" + "testing" + "time" + + sudokuobfs "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +type discardConn struct{} + +func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } +func (discardConn) Write(p []byte) (int, error) { return len(p), nil } +func (discardConn) Close() error { return nil } +func (discardConn) LocalAddr() net.Addr { return nil } +func (discardConn) RemoteAddr() net.Addr { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(time.Time) error { return nil } + +func TestSudokuObfsWriter_ReducesWriteAllocs(t *testing.T) { + table := sudokuobfs.NewTable("alloc-seed", "prefer_ascii") + w := newSudokuObfsWriter(discardConn{}, table, 0, 0) + + payload := bytes.Repeat([]byte{0x42}, 2048) + if _, err := w.Write(payload); err != nil { + t.Fatalf("warmup write: %v", err) + } + + allocs := testing.AllocsPerRun(100, func() { + if _, err := w.Write(payload); err != nil { + t.Fatalf("write: %v", err) + } + }) + if allocs != 0 { + t.Fatalf("expected 0 allocs/run, got %.2f", allocs) + } +} + +func TestHTTPMaskStrategy_WebSocketAndPost(t *testing.T) { + key := "mask-test-key" + target := "1.1.1.1:80" + table := sudokuobfs.NewTable("mask-seed", "prefer_ascii") + + base := DefaultConfig() + base.Key = key + base.AEADMethod = "chacha20-poly1305" + base.Table = table + base.PaddingMin = 0 + base.PaddingMax = 0 + base.EnablePureDownlink = true + base.HandshakeTimeoutSeconds = 5 + base.DisableHTTPMask = false + base.ServerAddress = "example.com:443" + + cases := []string{"post", "websocket"} + for _, strategy := range cases { + t.Run(strategy, func(t *testing.T) { + serverConn, clientConn := net.Pipe() + defer serverConn.Close() + defer clientConn.Close() + + errCh := make(chan error, 1) + go func() { + defer close(errCh) + session, err := ServerHandshake(serverConn, base) + if err != nil { + errCh <- err + return + } + defer session.Conn.Close() + if session.Type != SessionTypeTCP { + errCh <- io.ErrUnexpectedEOF + return + } + if session.Target != target { + errCh <- io.ErrClosedPipe + return + } + _, _ = session.Conn.Write([]byte("ok")) + }() + + cConn, err := ClientHandshakeWithOptions(clientConn, base, ClientHandshakeOptions{HTTPMaskStrategy: strategy}) + if err != nil { + t.Fatalf("client handshake: %v", err) + } + defer cConn.Close() + + addrBuf, err := EncodeAddress(target) + if err != nil { + t.Fatalf("encode addr: %v", err) + } + if _, err := cConn.Write(addrBuf); err != nil { + t.Fatalf("write addr: %v", err) + } + + buf := make([]byte, 2) + if _, err := io.ReadFull(cConn, buf); err != nil { + t.Fatalf("read: %v", err) + } + if string(buf) != "ok" { + t.Fatalf("unexpected payload: %q", buf) + } + + if err := <-errCh; err != nil { + t.Fatalf("server: %v", err) + } + }) + } +} + +func TestCustomTablesRotation_ProbedByServer(t *testing.T) { + key := "rotate-test-key" + target := "8.8.8.8:53" + + t1, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "xpxvvpvv") + if err != nil { + t.Fatalf("t1: %v", err) + } + t2, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "vxpvxvvp") + if err != nil { + t.Fatalf("t2: %v", err) + } + + serverCfg := DefaultConfig() + serverCfg.Key = key + serverCfg.AEADMethod = "chacha20-poly1305" + serverCfg.Tables = []*sudokuobfs.Table{t1, t2} + serverCfg.PaddingMin = 0 + serverCfg.PaddingMax = 0 + serverCfg.EnablePureDownlink = true + serverCfg.HandshakeTimeoutSeconds = 5 + serverCfg.DisableHTTPMask = true + + clientCfg := DefaultConfig() + *clientCfg = *serverCfg + clientCfg.ServerAddress = "example.com:443" + + for i := 0; i < 10; i++ { + serverConn, clientConn := net.Pipe() + + errCh := make(chan error, 1) + go func() { + defer close(errCh) + defer serverConn.Close() + session, err := ServerHandshake(serverConn, serverCfg) + if err != nil { + errCh <- err + return + } + defer session.Conn.Close() + if session.Type != SessionTypeTCP { + errCh <- io.ErrUnexpectedEOF + return + } + if session.Target != target { + errCh <- io.ErrClosedPipe + return + } + _, _ = session.Conn.Write([]byte{0xaa, 0xbb, 0xcc}) + }() + + cConn, err := ClientHandshake(clientConn, clientCfg) + if err != nil { + t.Fatalf("client handshake: %v", err) + } + + addrBuf, err := EncodeAddress(target) + if err != nil { + t.Fatalf("encode addr: %v", err) + } + if _, err := cConn.Write(addrBuf); err != nil { + t.Fatalf("write addr: %v", err) + } + + buf := make([]byte, 3) + if _, err := io.ReadFull(cConn, buf); err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.Equal(buf, []byte{0xaa, 0xbb, 0xcc}) { + t.Fatalf("payload mismatch: %x", buf) + } + _ = cConn.Close() + + if err := <-errCh; err != nil { + t.Fatalf("server: %v", err) + } + } +} diff --git a/clash-meta/transport/sudoku/handshake.go b/clash-meta/transport/sudoku/handshake.go index d34fceb40e..989d281323 100644 --- a/clash-meta/transport/sudoku/handshake.go +++ b/clash-meta/transport/sudoku/handshake.go @@ -2,11 +2,13 @@ package sudoku import ( "bufio" + "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "io" "net" + "strings" "time" "github.com/saba-futai/sudoku/apis" @@ -110,25 +112,35 @@ func downlinkMode(cfg *apis.ProtocolConfig) byte { return downlinkModePacked } -func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig) net.Conn { - base := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false) +func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table) net.Conn { + baseReader := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, false) + baseWriter := newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax) if cfg.EnablePureDownlink { - return base + return &directionalConn{ + Conn: raw, + reader: baseReader, + writer: baseWriter, + } } - packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax) + packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax) return &directionalConn{ Conn: raw, reader: packed, - writer: base, + writer: baseWriter, } } -func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, record bool) (*sudoku.Conn, net.Conn) { - uplink := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, record) +func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table, record bool) (*sudoku.Conn, net.Conn) { + uplink := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, record) if cfg.EnablePureDownlink { - return uplink, uplink + downlink := &directionalConn{ + Conn: raw, + reader: uplink, + writer: newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax), + } + return uplink, downlink } - packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax) + packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax) return uplink, &directionalConn{ Conn: raw, reader: uplink, @@ -170,8 +182,19 @@ func ClientAEADSeed(key string) string { return key } +type ClientHandshakeOptions struct { + // HTTPMaskStrategy controls how the client generates the HTTP mask header when DisableHTTPMask=false. + // Supported: ""/"random" (default), "post", "websocket". + HTTPMaskStrategy string +} + // ClientHandshake performs the client-side Sudoku handshake (without sending target address). func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, error) { + return ClientHandshakeWithOptions(rawConn, cfg, ClientHandshakeOptions{}) +} + +// ClientHandshakeWithOptions performs the client-side Sudoku handshake (without sending target address). +func ClientHandshakeWithOptions(rawConn net.Conn, cfg *apis.ProtocolConfig, opt ClientHandshakeOptions) (net.Conn, error) { if cfg == nil { return nil, fmt.Errorf("config is required") } @@ -180,18 +203,26 @@ func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, erro } if !cfg.DisableHTTPMask { - if err := httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { + if err := WriteHTTPMaskHeader(rawConn, cfg.ServerAddress, opt.HTTPMaskStrategy); err != nil { return nil, fmt.Errorf("write http mask failed: %w", err) } } - obfsConn := buildClientObfsConn(rawConn, cfg) + table, tableID, err := pickClientTable(cfg) + if err != nil { + return nil, err + } + + obfsConn := buildClientObfsConn(rawConn, cfg, table) cConn, err := crypto.NewAEADConn(obfsConn, ClientAEADSeed(cfg.Key), cfg.AEADMethod) if err != nil { return nil, fmt.Errorf("setup crypto failed: %w", err) } handshake := buildHandshakePayload(cfg.Key) + if len(tableCandidates(cfg)) > 1 { + handshake[15] = tableID + } if _, err := cConn.Write(handshake[:]); err != nil { cConn.Close() return nil, fmt.Errorf("send handshake failed: %w", err) @@ -218,21 +249,25 @@ func ServerHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (*ServerSession handshakeTimeout = 5 * time.Second } + rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout)) + bufReader := bufio.NewReader(rawConn) if !cfg.DisableHTTPMask { - if peek, _ := bufReader.Peek(4); len(peek) == 4 && string(peek) == "POST" { + if peek, err := bufReader.Peek(4); err == nil && httpmask.LooksLikeHTTPRequestStart(peek) { if _, err := httpmask.ConsumeHeader(bufReader); err != nil { return nil, fmt.Errorf("invalid http header: %w", err) } } } - rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout)) - bConn := &bufferedConn{ - Conn: rawConn, - r: bufReader, + selectedTable, preRead, err := selectTableByProbe(bufReader, cfg, tableCandidates(cfg)) + if err != nil { + return nil, err } - sConn, obfsConn := buildServerObfsConn(bConn, cfg, true) + + baseConn := &preBufferedConn{Conn: rawConn, buf: preRead} + bConn := &bufferedConn{Conn: baseConn, r: bufio.NewReader(baseConn)} + sConn, obfsConn := buildServerObfsConn(bConn, cfg, selectedTable, true) cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod) if err != nil { return nil, fmt.Errorf("crypto setup failed: %w", err) @@ -313,3 +348,24 @@ func GenKeyPair() (privateKey, publicKey string, err error) { publicKey = crypto.EncodePoint(pair.Public) // Master Public Key for server return } + +func normalizeHTTPMaskStrategy(strategy string) string { + s := strings.TrimSpace(strings.ToLower(strategy)) + switch s { + case "", "random": + return "random" + case "ws": + return "websocket" + default: + return s + } +} + +// randomByte returns a cryptographically random byte (with a math/rand fallback). +func randomByte() byte { + var b [1]byte + if _, err := rand.Read(b[:]); err == nil { + return b[0] + } + return byte(time.Now().UnixNano()) +} diff --git a/clash-meta/transport/sudoku/httpmask_strategy.go b/clash-meta/transport/sudoku/httpmask_strategy.go new file mode 100644 index 0000000000..dc90991d13 --- /dev/null +++ b/clash-meta/transport/sudoku/httpmask_strategy.go @@ -0,0 +1,179 @@ +package sudoku + +import ( + "encoding/base64" + "fmt" + "io" + "math/rand" + "net" + "strconv" + "sync" + "time" + + "github.com/saba-futai/sudoku/pkg/obfs/httpmask" +) + +var ( + httpMaskUserAgents = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + } + httpMaskAccepts = []string{ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "application/json, text/plain, */*", + "application/octet-stream", + "*/*", + } + httpMaskAcceptLanguages = []string{ + "en-US,en;q=0.9", + "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", + } + httpMaskAcceptEncodings = []string{ + "gzip, deflate, br", + "gzip, deflate", + } + httpMaskPaths = []string{ + "/api/v1/upload", + "/data/sync", + "/v1/telemetry", + "/session", + "/ws", + } + httpMaskContentTypes = []string{ + "application/octet-stream", + "application/json", + } +) + +var ( + httpMaskRngPool = sync.Pool{ + New: func() any { return rand.New(rand.NewSource(time.Now().UnixNano())) }, + } + httpMaskBufPool = sync.Pool{ + New: func() any { + b := make([]byte, 0, 1024) + return &b + }, + } +) + +func trimPortForHost(host string) string { + if host == "" { + return host + } + h, _, err := net.SplitHostPort(host) + if err == nil && h != "" { + return h + } + return host +} + +func appendCommonHeaders(buf []byte, host string, r *rand.Rand) []byte { + ua := httpMaskUserAgents[r.Intn(len(httpMaskUserAgents))] + accept := httpMaskAccepts[r.Intn(len(httpMaskAccepts))] + lang := httpMaskAcceptLanguages[r.Intn(len(httpMaskAcceptLanguages))] + enc := httpMaskAcceptEncodings[r.Intn(len(httpMaskAcceptEncodings))] + + buf = append(buf, "Host: "...) + buf = append(buf, host...) + buf = append(buf, "\r\nUser-Agent: "...) + buf = append(buf, ua...) + buf = append(buf, "\r\nAccept: "...) + buf = append(buf, accept...) + buf = append(buf, "\r\nAccept-Language: "...) + buf = append(buf, lang...) + buf = append(buf, "\r\nAccept-Encoding: "...) + buf = append(buf, enc...) + buf = append(buf, "\r\nConnection: keep-alive\r\n"...) + buf = append(buf, "Cache-Control: no-cache\r\nPragma: no-cache\r\n"...) + return buf +} + +// WriteHTTPMaskHeader writes an HTTP/1.x request header as a mask, according to strategy. +// Supported strategies: ""/"random", "post", "websocket". +func WriteHTTPMaskHeader(w io.Writer, host string, strategy string) error { + switch normalizeHTTPMaskStrategy(strategy) { + case "random": + return httpmask.WriteRandomRequestHeader(w, host) + case "post": + return writeHTTPMaskPOST(w, host) + case "websocket": + return writeHTTPMaskWebSocket(w, host) + default: + return fmt.Errorf("unsupported http-mask-strategy: %s", strategy) + } +} + +func writeHTTPMaskPOST(w io.Writer, host string) error { + r := httpMaskRngPool.Get().(*rand.Rand) + defer httpMaskRngPool.Put(r) + + path := httpMaskPaths[r.Intn(len(httpMaskPaths))] + ctype := httpMaskContentTypes[r.Intn(len(httpMaskContentTypes))] + + bufPtr := httpMaskBufPool.Get().(*[]byte) + buf := *bufPtr + buf = buf[:0] + defer func() { + if cap(buf) <= 4096 { + *bufPtr = buf + httpMaskBufPool.Put(bufPtr) + } + }() + + const minCL = int64(4 * 1024) + const maxCL = int64(10 * 1024 * 1024) + contentLength := minCL + r.Int63n(maxCL-minCL+1) + + buf = append(buf, "POST "...) + buf = append(buf, path...) + buf = append(buf, " HTTP/1.1\r\n"...) + buf = appendCommonHeaders(buf, host, r) + buf = append(buf, "Content-Type: "...) + buf = append(buf, ctype...) + buf = append(buf, "\r\nContent-Length: "...) + buf = strconv.AppendInt(buf, contentLength, 10) + buf = append(buf, "\r\n\r\n"...) + + _, err := w.Write(buf) + return err +} + +func writeHTTPMaskWebSocket(w io.Writer, host string) error { + r := httpMaskRngPool.Get().(*rand.Rand) + defer httpMaskRngPool.Put(r) + + path := httpMaskPaths[r.Intn(len(httpMaskPaths))] + + bufPtr := httpMaskBufPool.Get().(*[]byte) + buf := *bufPtr + buf = buf[:0] + defer func() { + if cap(buf) <= 4096 { + *bufPtr = buf + httpMaskBufPool.Put(bufPtr) + } + }() + + hostNoPort := trimPortForHost(host) + var keyBytes [16]byte + for i := 0; i < len(keyBytes); i++ { + keyBytes[i] = byte(r.Intn(256)) + } + var wsKey [24]byte + base64.StdEncoding.Encode(wsKey[:], keyBytes[:]) + + buf = append(buf, "GET "...) + buf = append(buf, path...) + buf = append(buf, " HTTP/1.1\r\n"...) + buf = appendCommonHeaders(buf, host, r) + buf = append(buf, "Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...) + buf = append(buf, wsKey[:]...) + buf = append(buf, "\r\nOrigin: https://"...) + buf = append(buf, hostNoPort...) + buf = append(buf, "\r\n\r\n"...) + + _, err := w.Write(buf) + return err +} diff --git a/clash-meta/transport/sudoku/obfs_writer.go b/clash-meta/transport/sudoku/obfs_writer.go new file mode 100644 index 0000000000..f980359119 --- /dev/null +++ b/clash-meta/transport/sudoku/obfs_writer.go @@ -0,0 +1,113 @@ +package sudoku + +import ( + crypto_rand "crypto/rand" + "encoding/binary" + "math/rand" + "net" + + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +// perm4 matches github.com/saba-futai/sudoku/pkg/obfs/sudoku perm4. +var perm4 = [24][4]byte{ + {0, 1, 2, 3}, + {0, 1, 3, 2}, + {0, 2, 1, 3}, + {0, 2, 3, 1}, + {0, 3, 1, 2}, + {0, 3, 2, 1}, + {1, 0, 2, 3}, + {1, 0, 3, 2}, + {1, 2, 0, 3}, + {1, 2, 3, 0}, + {1, 3, 0, 2}, + {1, 3, 2, 0}, + {2, 0, 1, 3}, + {2, 0, 3, 1}, + {2, 1, 0, 3}, + {2, 1, 3, 0}, + {2, 3, 0, 1}, + {2, 3, 1, 0}, + {3, 0, 1, 2}, + {3, 0, 2, 1}, + {3, 1, 0, 2}, + {3, 1, 2, 0}, + {3, 2, 0, 1}, + {3, 2, 1, 0}, +} + +type sudokuObfsWriter struct { + conn net.Conn + table *sudoku.Table + rng *rand.Rand + paddingRate float32 + + outBuf []byte + pads []byte + padLen int +} + +func newSudokuObfsWriter(conn net.Conn, table *sudoku.Table, pMin, pMax int) *sudokuObfsWriter { + var seedBytes [8]byte + if _, err := crypto_rand.Read(seedBytes[:]); err != nil { + binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63())) + } + seed := int64(binary.BigEndian.Uint64(seedBytes[:])) + localRng := rand.New(rand.NewSource(seed)) + + min := float32(pMin) / 100.0 + span := float32(pMax-pMin) / 100.0 + rate := min + localRng.Float32()*span + + w := &sudokuObfsWriter{ + conn: conn, + table: table, + rng: localRng, + paddingRate: rate, + } + w.pads = table.PaddingPool + w.padLen = len(w.pads) + return w +} + +func (w *sudokuObfsWriter) Write(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + // Worst-case: 4 hints + up to 6 paddings per input byte. + needed := len(p)*10 + 1 + if cap(w.outBuf) < needed { + w.outBuf = make([]byte, 0, needed) + } + out := w.outBuf[:0] + + pads := w.pads + padLen := w.padLen + + for _, b := range p { + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + + puzzles := w.table.EncodeTable[b] + puzzle := puzzles[w.rng.Intn(len(puzzles))] + + perm := perm4[w.rng.Intn(len(perm4))] + for _, idx := range perm { + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + out = append(out, puzzle[idx]) + } + } + + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + + w.outBuf = out + _, err := w.conn.Write(out) + return len(p), err +} diff --git a/clash-meta/transport/sudoku/table_probe.go b/clash-meta/transport/sudoku/table_probe.go new file mode 100644 index 0000000000..f12c172226 --- /dev/null +++ b/clash-meta/transport/sudoku/table_probe.go @@ -0,0 +1,152 @@ +package sudoku + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "github.com/saba-futai/sudoku/apis" + "github.com/saba-futai/sudoku/pkg/crypto" + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +func tableCandidates(cfg *apis.ProtocolConfig) []*sudoku.Table { + if cfg == nil { + return nil + } + if len(cfg.Tables) > 0 { + return cfg.Tables + } + if cfg.Table != nil { + return []*sudoku.Table{cfg.Table} + } + return nil +} + +func pickClientTable(cfg *apis.ProtocolConfig) (*sudoku.Table, byte, error) { + candidates := tableCandidates(cfg) + if len(candidates) == 0 { + return nil, 0, fmt.Errorf("no table configured") + } + if len(candidates) == 1 { + return candidates[0], 0, nil + } + idx := int(randomByte()) % len(candidates) + return candidates[idx], byte(idx), nil +} + +type readOnlyConn struct { + *bytes.Reader +} + +func (c *readOnlyConn) Write([]byte) (int, error) { return 0, io.ErrClosedPipe } +func (c *readOnlyConn) Close() error { return nil } +func (c *readOnlyConn) LocalAddr() net.Addr { return nil } +func (c *readOnlyConn) RemoteAddr() net.Addr { return nil } +func (c *readOnlyConn) SetDeadline(time.Time) error { return nil } +func (c *readOnlyConn) SetReadDeadline(time.Time) error { return nil } +func (c *readOnlyConn) SetWriteDeadline(time.Time) error { return nil } + +func drainBuffered(r *bufio.Reader) ([]byte, error) { + n := r.Buffered() + if n <= 0 { + return nil, nil + } + out := make([]byte, n) + _, err := io.ReadFull(r, out) + return out, err +} + +func probeHandshakeBytes(probe []byte, cfg *apis.ProtocolConfig, table *sudoku.Table) error { + rc := &readOnlyConn{Reader: bytes.NewReader(probe)} + _, obfsConn := buildServerObfsConn(rc, cfg, table, false) + cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod) + if err != nil { + return err + } + + var handshakeBuf [16]byte + if _, err := io.ReadFull(cConn, handshakeBuf[:]); err != nil { + return err + } + ts := int64(binary.BigEndian.Uint64(handshakeBuf[:8])) + if absInt64(time.Now().Unix()-ts) > 60 { + return fmt.Errorf("timestamp skew/replay detected") + } + + modeBuf := []byte{0} + if _, err := io.ReadFull(cConn, modeBuf); err != nil { + return err + } + if modeBuf[0] != downlinkMode(cfg) { + return fmt.Errorf("downlink mode mismatch") + } + + return nil +} + +func selectTableByProbe(r *bufio.Reader, cfg *apis.ProtocolConfig, tables []*sudoku.Table) (*sudoku.Table, []byte, error) { + const ( + maxProbeBytes = 64 * 1024 + readChunk = 4 * 1024 + ) + if len(tables) == 0 { + return nil, nil, fmt.Errorf("no table candidates") + } + if len(tables) > 255 { + return nil, nil, fmt.Errorf("too many table candidates: %d", len(tables)) + } + + probe, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + + tmp := make([]byte, readChunk) + for { + if len(tables) == 1 { + tail, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + probe = append(probe, tail...) + return tables[0], probe, nil + } + + needMore := false + for _, table := range tables { + err := probeHandshakeBytes(probe, cfg, table) + if err == nil { + tail, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + probe = append(probe, tail...) + return table, probe, nil + } + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + needMore = true + } + } + + if !needMore { + return nil, probe, fmt.Errorf("handshake table selection failed") + } + if len(probe) >= maxProbeBytes { + return nil, probe, fmt.Errorf("handshake probe exceeded %d bytes", maxProbeBytes) + } + + n, err := r.Read(tmp) + if n > 0 { + probe = append(probe, tmp[:n]...) + } + if err != nil { + return nil, probe, fmt.Errorf("handshake probe read failed: %w", err) + } + } +} diff --git a/clash-meta/transport/sudoku/tables.go b/clash-meta/transport/sudoku/tables.go new file mode 100644 index 0000000000..429a4ab327 --- /dev/null +++ b/clash-meta/transport/sudoku/tables.go @@ -0,0 +1,30 @@ +package sudoku + +import ( + "strings" + + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +// NewTablesWithCustomPatterns builds one or more obfuscation tables from x/v/p custom patterns. +// When customTables is non-empty it overrides customTable (matching upstream Sudoku behavior). +func NewTablesWithCustomPatterns(key string, tableType string, customTable string, customTables []string) ([]*sudoku.Table, error) { + patterns := customTables + if len(patterns) == 0 && strings.TrimSpace(customTable) != "" { + patterns = []string{customTable} + } + if len(patterns) == 0 { + patterns = []string{""} + } + + tables := make([]*sudoku.Table, 0, len(patterns)) + for _, pattern := range patterns { + pattern = strings.TrimSpace(pattern) + t, err := NewTableWithCustom(key, tableType, pattern) + if err != nil { + return nil, err + } + tables = append(tables, t) + } + return tables, nil +} diff --git a/clash-nyanpasu/.github/workflows/ci.yml b/clash-nyanpasu/.github/workflows/ci.yml index f46d4f23db..8f644fac29 100644 --- a/clash-nyanpasu/.github/workflows/ci.yml +++ b/clash-nyanpasu/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: env: NODE_OPTIONS: '--max_old_space_size=4096' - name: Prepare sidecar and resources - run: pnpm check + run: pnpm prepare:check - name: Lint if: startsWith(matrix.targets.os, 'ubuntu-') run: pnpm lint # Lint @@ -177,7 +177,7 @@ jobs: run: pnpm install --no-frozen-lockfile - name: Prepare sidecar and resources - run: pnpm check + run: pnpm prepare:check - name: Prepare frontend run: pnpm -r build @@ -268,7 +268,7 @@ jobs: run: pnpm install --no-frozen-lockfile - name: Prepare sidecar and resources - run: pnpm check + run: pnpm prepare:check - name: Prepare frontend run: pnpm -r build diff --git a/clash-nyanpasu/.github/workflows/deps-build-linux.yaml b/clash-nyanpasu/.github/workflows/deps-build-linux.yaml index c0c4a5c142..1eb8d6ba6e 100644 --- a/clash-nyanpasu/.github/workflows/deps-build-linux.yaml +++ b/clash-nyanpasu/.github/workflows/deps-build-linux.yaml @@ -97,15 +97,15 @@ jobs: run: | case "${{ inputs.arch }}" in "x86_64") - pnpm check ;; + pnpm prepare:check ;; "i686") - pnpm check --arch ia32 --sidecar-host i686-unknown-linux-gnu ;; + pnpm prepare:check --arch ia32 --sidecar-host i686-unknown-linux-gnu ;; "aarch64") - pnpm check --arch arm64 --sidecar-host aarch64-unknown-linux-gnu ;; + pnpm prepare:check --arch arm64 --sidecar-host aarch64-unknown-linux-gnu ;; "armel") - pnpm check --arch armel --sidecar-host armv7-unknown-linux-gnueabi ;; + pnpm prepare:check --arch armel --sidecar-host armv7-unknown-linux-gnueabi ;; "armhf") - pnpm check --arch arm --sidecar-host armv7-unknown-linux-gnueabihf ;; + pnpm prepare:check --arch arm --sidecar-host armv7-unknown-linux-gnueabihf ;; esac - name: Nightly Prepare diff --git a/clash-nyanpasu/.github/workflows/deps-build-macos.yaml b/clash-nyanpasu/.github/workflows/deps-build-macos.yaml index 2d26d9c18f..0be70ff28f 100644 --- a/clash-nyanpasu/.github/workflows/deps-build-macos.yaml +++ b/clash-nyanpasu/.github/workflows/deps-build-macos.yaml @@ -84,10 +84,10 @@ jobs: pnpm i - name: Download Sidecars aarch64 if: ${{ inputs.aarch64 == true }} - run: pnpm check --arch arm64 --sidecar-host aarch64-apple-darwin + run: pnpm prepare:check --arch arm64 --sidecar-host aarch64-apple-darwin - name: Download Sidecars x64 if: ${{ inputs.aarch64 == false }} - run: pnpm check --arch x64 --sidecar-host x86_64-apple-darwin + run: pnpm prepare:check --arch x64 --sidecar-host x86_64-apple-darwin - name: Nightly Prepare if: ${{ inputs.nightly == true }} run: | diff --git a/clash-nyanpasu/.github/workflows/deps-build-windows-nsis.yaml b/clash-nyanpasu/.github/workflows/deps-build-windows-nsis.yaml index 1f81a8f1a9..e1ab01827f 100644 --- a/clash-nyanpasu/.github/workflows/deps-build-windows-nsis.yaml +++ b/clash-nyanpasu/.github/workflows/deps-build-windows-nsis.yaml @@ -103,13 +103,13 @@ jobs: $condition = '${{ inputs.arch }}' switch ($condition) { 'x86_64' { - pnpm check + pnpm prepare:check } 'i686' { - pnpm check --arch ia32 --sidecar-host i686-pc-windows-msvc + pnpm prepare:check --arch ia32 --sidecar-host i686-pc-windows-msvc } 'aarch64' { - pnpm check --arch arm64 --sidecar-host aarch64-pc-windows-msvc + pnpm prepare:check --arch arm64 --sidecar-host aarch64-pc-windows-msvc } } diff --git a/clash-nyanpasu/.github/workflows/macos-aarch64.yaml b/clash-nyanpasu/.github/workflows/macos-aarch64.yaml index c979bf32df..1b27b62e21 100644 --- a/clash-nyanpasu/.github/workflows/macos-aarch64.yaml +++ b/clash-nyanpasu/.github/workflows/macos-aarch64.yaml @@ -44,7 +44,7 @@ jobs: - name: Pnpm install and check run: | pnpm i - pnpm check --arch arm64 --sidecar-host aarch64-apple-darwin + pnpm prepare:check --arch arm64 --sidecar-host aarch64-apple-darwin - name: Tauri build with Upload (cmd) env: diff --git a/clash-nyanpasu/CONTRIBUTING.md b/clash-nyanpasu/CONTRIBUTING.md index d5d9f998cb..ff7f423350 100644 --- a/clash-nyanpasu/CONTRIBUTING.md +++ b/clash-nyanpasu/CONTRIBUTING.md @@ -78,7 +78,7 @@ pnpm i ### 2. Download Core & Resource Files ``` -pnpm check +pnpm prepare:check ``` > This command downloads binaries like `sidecar` and `resource` to ensure the project runs properly @@ -86,7 +86,7 @@ pnpm check If files are missing or you want to force update: ``` -pnpm check --force +pnpm prepare:check --force ``` 💡 **Tip**: Configure terminal proxy if network issues occur diff --git a/clash-nyanpasu/README.md b/clash-nyanpasu/README.md index c8cc1b092c..50ef9ebc79 100644 --- a/clash-nyanpasu/README.md +++ b/clash-nyanpasu/README.md @@ -54,9 +54,9 @@ pnpm i ```shell # force update to latest version -# pnpm check --force +# pnpm prepare:check --force -pnpm check +pnpm prepare:check ``` ### Run dev diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/app/app-drawer.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/app/app-drawer.tsx index f16175d390..e853b86e90 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/app/app-drawer.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/app/app-drawer.tsx @@ -3,6 +3,7 @@ import { useState } from 'react' import getSystem from '@/utils/get-system' import { MenuOpen } from '@mui/icons-material' import { Backdrop, IconButton } from '@mui/material' +import type { SxProps, Theme } from '@mui/material/styles' import { alpha, cn } from '@nyanpasu/ui' import AnimatedLogo from '../layout/animated-logo' import DrawerContent from './drawer-content' @@ -50,22 +51,18 @@ export const AppDrawer = () => { - OS === 'linux' - ? { - backgroundColor: null, - } - : { - backgroundColor: alpha(theme.vars.palette.primary.light, 0.1), - ...theme.applyStyles('dark', { - backgroundColor: alpha( - theme.vars.palette.primary.dark, - 0.1, - ), - }), - }, - ]} + sx={ + (OS === 'linux' + ? { + backgroundColor: 'transparent', + } + : (theme) => ({ + backgroundColor: alpha(theme.vars.palette.primary.light, 0.1), + ...theme.applyStyles('dark', { + backgroundColor: alpha(theme.vars.palette.primary.dark, 0.1), + }), + })) as SxProps + } open={open} onClick={() => setOpen(false)} > diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/app/modules/route-list-item.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/app/modules/route-list-item.tsx index 534d72f43c..8472559ec8 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/app/modules/route-list-item.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/app/modules/route-list-item.tsx @@ -5,7 +5,7 @@ import { SvgIconComponent } from '@mui/icons-material' import { Box, ListItemButton, ListItemIcon, Tooltip } from '@mui/material' import { useSetting } from '@nyanpasu/interface' import { alpha, cn } from '@nyanpasu/ui' -import { useMatch, useNavigate } from '@tanstack/react-router' +import { useLocation, useMatch, useNavigate } from '@tanstack/react-router' export const RouteListItem = ({ name, @@ -19,11 +19,10 @@ export const RouteListItem = ({ onlyIcon?: boolean }) => { const { t } = useTranslation() - const match = useMatch({ - strict: false, - shouldThrow: false, - from: path as never, - }) + + const location = useLocation() + + const match = location.pathname === path const navigate = useNavigate() diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/connections/connections-total.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/connections/connections-total.tsx index 6900f748f4..716dd68eea 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/connections/connections-total.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/connections/connections-total.tsx @@ -2,6 +2,7 @@ import { filesize } from 'filesize' import { useEffect, useRef, useState } from 'react' import { Download, Upload } from '@mui/icons-material' import { Paper, Skeleton } from '@mui/material' +import type { SxProps, Theme } from '@mui/material/styles' import { useClashConnections } from '@nyanpasu/interface' import { darken, lighten } from '@nyanpasu/ui' @@ -88,8 +89,8 @@ export default function ConnectionTotal() { > ({ + sx={ + ((theme) => ({ color: darken( theme.vars.palette.primary.main, downloadHighlight ? 0.9 : 0.3, @@ -100,8 +101,8 @@ export default function ConnectionTotal() { downloadHighlight ? 0.2 : 0.9, ), }), - }), - ]} + })) as SxProps + } />{' '} {filesize(latestClashConnections.downloadTotal, { pad: true })} @@ -117,8 +118,8 @@ export default function ConnectionTotal() { > ({ + sx={ + ((theme) => ({ color: darken( theme.vars.palette.primary.main, uploadHighlight ? 0.9 : 0.3, @@ -129,8 +130,8 @@ export default function ConnectionTotal() { downloadHighlight ? 0.2 : 0.9, ), }), - }), - ]} + })) as SxProps + } />{' '} {filesize(latestClashConnections.uploadTotal, { pad: true })} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/dashboard/service-shortcuts.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/dashboard/service-shortcuts.tsx index 01a5ee5591..6c00beeb3d 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/dashboard/service-shortcuts.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/dashboard/service-shortcuts.tsx @@ -5,15 +5,9 @@ import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import useSWR from 'swr' import { atomIsDrawer } from '@/store' -import { - Box, - CircularProgress, - Paper, - SxProps, - Theme, - Tooltip, -} from '@mui/material' +import { Box, CircularProgress, Paper, Tooltip } from '@mui/material' import Grid from '@mui/material/Grid' +import type { SxProps, Theme } from '@mui/material/styles' import { getCoreStatus, useSystemService } from '@nyanpasu/interface' import { alpha } from '@nyanpasu/ui' @@ -42,24 +36,24 @@ export const ServiceShortcuts = () => { case 'running': { return { label: t('running'), - sx: (theme) => ({ + sx: ((theme) => ({ backgroundColor: alpha(theme.vars.palette.success.light, 0.3), ...theme.applyStyles('dark', { backgroundColor: alpha(theme.vars.palette.success.dark, 0.3), }), - }), + })) as SxProps, } } case 'stopped': { return { label: t('stopped'), - sx: (theme) => ({ + sx: ((theme) => ({ backgroundColor: alpha(theme.vars.palette.error.light, 0.3), ...theme.applyStyles('dark', { backgroundColor: alpha(theme.vars.palette.error.dark, 0.3), }), - }), + })) as SxProps, } } @@ -67,12 +61,12 @@ export const ServiceShortcuts = () => { default: { return { label: t('not_installed'), - sx: (theme) => ({ + sx: ((theme) => ({ backgroundColor: theme.vars.palette.grey[100], ...theme.applyStyles('dark', { backgroundColor: theme.vars.palette.background.paper, }), - }), + })) as SxProps, } } } @@ -90,24 +84,24 @@ export const ServiceShortcuts = () => { !!Stopped && Stopped.trim() ? t('stopped_reason', { reason: Stopped }) : t('stopped'), - sx: (theme) => ({ + sx: ((theme) => ({ backgroundColor: alpha(theme.vars.palette.success.light, 0.3), ...theme.applyStyles('dark', { backgroundColor: alpha(theme.vars.palette.success.dark, 0.3), }), - }), + })) as SxProps, } } return { label: t('service_shortcuts.core_started_by', { by: t(status[2] === 'normal' ? 'UI' : 'service'), }), - sx: (theme) => ({ + sx: ((theme) => ({ backgroundColor: alpha(theme.vars.palette.success.light, 0.3), ...theme.applyStyles('dark', { backgroundColor: alpha(theme.vars.palette.success.dark, 0.3), }), - }), + })) as SxProps, } }, [coreStatusSWR.data, t]) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/proxies/node-card.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/proxies/node-card.tsx index 769c594ea7..0fc5bafe20 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/proxies/node-card.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/proxies/node-card.tsx @@ -1,6 +1,7 @@ import { useLockFn } from 'ahooks' import { CSSProperties, memo, useMemo } from 'react' import Box from '@mui/material/Box' +import type { SxProps, Theme } from '@mui/material/styles' import { ClashProxiesQueryProxyItem } from '@nyanpasu/interface' import { alpha, cn } from '@nyanpasu/ui' import { PaperSwitchButton } from '../setting/modules/system-proxy' @@ -41,16 +42,18 @@ export const NodeCard = memo(function NodeCard({ disabled={disabled} style={style} className={cn(styles.Card, delay === -1 && styles.NoDelay)} - sxPaper={(theme) => ({ - backgroundColor: checked - ? alpha(theme.vars.palette.primary.main, 0.3) - : theme.vars.palette.grey[100], - ...theme.applyStyles('dark', { + sxPaper={ + ((theme) => ({ backgroundColor: checked ? alpha(theme.vars.palette.primary.main, 0.3) - : theme.vars.palette.grey[900], - }), - })} + : theme.vars.palette.grey[100], + ...theme.applyStyles('dark', { + backgroundColor: checked + ? alpha(theme.vars.palette.primary.main, 0.3) + : theme.vars.palette.grey[900], + }), + })) as SxProps + } > diff --git a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/system-proxy.tsx b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/system-proxy.tsx index 64dfe3bb9f..1949f1b37b 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/system-proxy.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/components/setting/modules/system-proxy.tsx @@ -1,7 +1,8 @@ import { useControllableValue } from 'ahooks' import { memo, ReactNode } from 'react' import { mergeSxProps } from '@/utils/mui-theme' -import { CircularProgress, SxProps, Theme } from '@mui/material' +import { CircularProgress } from '@mui/material' +import type { SxProps, Theme } from '@mui/material/styles' import { alpha } from '@nyanpasu/ui' import { PaperButton, PaperButtonProps } from './nyanpasu-path' @@ -48,7 +49,7 @@ export const PaperSwitchButton = memo(function PaperSwitchButton({ ({ + ((theme) => ({ backgroundColor: checked ? alpha(theme.vars.palette.primary.main, 0.1) : theme.vars.palette.grey[100], @@ -57,7 +58,7 @@ export const PaperSwitchButton = memo(function PaperSwitchButton({ ? alpha(theme.vars.palette.primary.main, 0.1) : theme.vars.palette.common.black, }), - }), + })) as SxProps, sxPaper, )} sxButton={{ diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/connections.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/connections.tsx similarity index 97% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/connections.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/connections.tsx index c3391dd54c..a22cdcbda4 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/connections.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/connections.tsx @@ -18,7 +18,7 @@ const ConnectionTotal = lazy( () => import('@/components/connections/connections-total'), ) -export const Route = createFileRoute('/connections')({ +export const Route = createFileRoute('/(legacy)/connections')({ component: Connections, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/dashboard.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/dashboard.tsx similarity index 94% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/dashboard.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/dashboard.tsx index e4bd2bdfc3..c17eae64d0 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/dashboard.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/dashboard.tsx @@ -9,7 +9,7 @@ import { useClashWSContext } from '@nyanpasu/interface' import { BasePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/dashboard')({ +export const Route = createFileRoute('/(legacy)/dashboard')({ component: Dashboard, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/logs.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/logs.tsx similarity index 93% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/logs.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/logs.tsx index c3b2e66c5f..df3259c8fc 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/logs.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/logs.tsx @@ -5,7 +5,7 @@ import LogHeader from '@/components/logs/los-header' import { BasePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/logs')({ +export const Route = createFileRoute('/(legacy)/logs')({ component: LogPage, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/profiles.tsx similarity index 97% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/profiles.tsx index d54893ebb1..9fa93c853d 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/profiles.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/profiles.tsx @@ -26,6 +26,7 @@ import { message } from '@/utils/notification' import { Public, Update } from '@mui/icons-material' import { Badge, Button, CircularProgress, IconButton } from '@mui/material' import Grid from '@mui/material/Grid' +import type { SxProps, Theme } from '@mui/material/styles' import { RemoteProfileOptionsBuilder, useProfile, @@ -41,7 +42,7 @@ const profileSearchParams = z.object({ subscribeDesc: z.string().optional(), }) -export const Route = createFileRoute('/profiles')({ +export const Route = createFileRoute('/(legacy)/profiles')({ validateSearch: zodSearchValidator(profileSearchParams), component: ProfilePage, }) @@ -262,8 +263,8 @@ function ProfilePage() {
({ + sx={ + ((theme) => ({ backgroundColor: theme.vars.palette.grey[200], boxShadow: 4, '&:hover': { @@ -275,8 +276,8 @@ function ProfilePage() { backgroundColor: theme.vars.palette.grey[700], }, }), - }), - ]} + })) as SxProps + } onClick={handleGlobalProfileUpdate} > {globalUpdatePending ? : } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/providers.tsx similarity index 92% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/providers.tsx index f0d6a3fb70..ca8dd94937 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/providers.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/providers.tsx @@ -12,7 +12,7 @@ import { import { BasePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/providers')({ +export const Route = createFileRoute('/(legacy)/providers')({ component: ProvidersPage, }) @@ -29,7 +29,7 @@ function ProvidersPage() {
@@ -57,7 +57,7 @@ function ProvidersPage() {
diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx similarity index 98% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx index 3c0fada3a3..2de898b72e 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/proxies.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/proxies.tsx @@ -24,7 +24,7 @@ import { import { alpha, cn, SidePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/proxies')({ +export const Route = createFileRoute('/(legacy)/proxies')({ component: ProxyPage, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx new file mode 100644 index 0000000000..9c1e3bfb99 --- /dev/null +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/route.tsx @@ -0,0 +1,78 @@ +import AppContainer from '@/components/app/app-container' +import LocalesProvider from '@/components/app/locales-provider' +import MutationProvider from '@/components/layout/mutation-provider' +import NoticeProvider from '@/components/layout/notice-provider' +import PageTransition from '@/components/layout/page-transition' +import SchemeProvider from '@/components/layout/scheme-provider' +import UpdaterDialog from '@/components/updater/updater-dialog-wrapper' +import { UpdaterProvider } from '@/hooks/use-updater' +import { FileRouteTypes } from '@/route-tree.gen' +import { atomIsDrawer, memorizedRoutePathAtom } from '@/store' +import { useSettings } from '@nyanpasu/interface' +import { cn, useBreakpoint } from '@nyanpasu/ui' +import { createFileRoute, useLocation } from '@tanstack/react-router' +import 'dayjs/locale/ru' +import 'dayjs/locale/zh-cn' +import 'dayjs/locale/zh-tw' +import { useAtom, useSetAtom } from 'jotai' +import { PropsWithChildren, useEffect } from 'react' +import { SWRConfig } from 'swr' + +export const Route = createFileRoute('/(legacy)')({ + component: Layout, +}) + +const QueryLoaderProvider = ({ children }: PropsWithChildren) => { + const { + query: { isLoading }, + } = useSettings() + + return isLoading ? null : children +} + +function Layout() { + const breakpoint = useBreakpoint() + + const setMemorizedPath = useSetAtom(memorizedRoutePathAtom) + + const pathname = useLocation({ + select: (location) => location.pathname, + }) + + useEffect(() => { + if (pathname !== '/') { + setMemorizedPath(pathname as FileRouteTypes['fullPaths']) + } + }, [pathname, setMemorizedPath]) + + const [isDrawer, setIsDrawer] = useAtom(atomIsDrawer) + + useEffect(() => { + setIsDrawer(breakpoint === 'sm' || breakpoint === 'xs') + }, [breakpoint, setIsDrawer]) + + return ( + + + + + + + + + + + + + + ) +} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx similarity index 96% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx index ca0c69bca2..31d0c64b79 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/rules.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/rules.tsx @@ -8,7 +8,7 @@ import { useClashRules } from '@nyanpasu/interface' import { alpha, BasePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/rules')({ +export const Route = createFileRoute('/(legacy)/rules')({ component: RulesPage, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx similarity index 98% rename from clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx rename to clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx index 3b4e1b07d4..8de7f2859e 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/settings.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/(legacy)/settings.tsx @@ -11,7 +11,7 @@ import { collectEnvs, openThat } from '@nyanpasu/interface' import { BasePage } from '@nyanpasu/ui' import { createFileRoute } from '@tanstack/react-router' -export const Route = createFileRoute('/settings')({ +export const Route = createFileRoute('/(legacy)/settings')({ component: SettingPage, }) diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx index 940ace011c..f4e5b4f7c2 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/__root.tsx @@ -1,24 +1,14 @@ import { useMount } from 'ahooks' import dayjs from 'dayjs' -import AppContainer from '@/components/app/app-container' -import LocalesProvider from '@/components/app/locales-provider' -import MutationProvider from '@/components/layout/mutation-provider' -import NoticeProvider from '@/components/layout/notice-provider' -import PageTransition from '@/components/layout/page-transition' -import SchemeProvider from '@/components/layout/scheme-provider' import { ThemeModeProvider } from '@/components/layout/use-custom-theme' -import UpdaterDialog from '@/components/updater/updater-dialog-wrapper' import { useNyanpasuStorageSubscribers } from '@/hooks/use-store' -import { UpdaterProvider } from '@/hooks/use-updater' -import { FileRouteTypes } from '@/route-tree.gen' -import { atomIsDrawer, memorizedRoutePathAtom } from '@/store' import { CssBaseline } from '@mui/material' import { StyledEngineProvider, useColorScheme } from '@mui/material/styles' -import { cn, useBreakpoint } from '@nyanpasu/ui' +import { cn } from '@nyanpasu/ui' import { createRootRoute, ErrorComponentProps, - useLocation, + Outlet, } from '@tanstack/react-router' import { emit } from '@tauri-apps/api/event' import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' @@ -27,10 +17,8 @@ import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-tw' import customParseFormat from 'dayjs/plugin/customParseFormat' import relativeTime from 'dayjs/plugin/relativeTime' -import { useAtom, useSetAtom } from 'jotai' -import { lazy, PropsWithChildren, useEffect } from 'react' -import { SWRConfig } from 'swr' -import { NyanpasuProvider, useSettings } from '@nyanpasu/interface' +import { lazy } from 'react' +import { NyanpasuProvider } from '@nyanpasu/interface' import styles from './-__root.module.scss' dayjs.extend(relativeTime) @@ -69,36 +57,9 @@ export const Route = createRootRoute({ pendingComponent: Pending, }) -const QueryLoaderProvider = ({ children }: PropsWithChildren) => { - const { - query: { isLoading }, - } = useSettings() - - return isLoading ? null : children -} - export default function App() { - const breakpoint = useBreakpoint() - - const setMemorizedPath = useSetAtom(memorizedRoutePathAtom) - const pathname = useLocation({ - select: (location) => location.pathname, - }) - - useEffect(() => { - if (pathname !== '/') { - setMemorizedPath(pathname as FileRouteTypes['fullPaths']) - } - }, [pathname, setMemorizedPath]) - - const [isDrawer, setIsDrawer] = useAtom(atomIsDrawer) - useNyanpasuStorageSubscribers() - useEffect(() => { - setIsDrawer(breakpoint === 'sm' || breakpoint === 'xs') - }, [breakpoint, setIsDrawer]) - useMount(() => { const appWindow = getCurrentWebviewWindow() Promise.all([ @@ -110,38 +71,15 @@ export default function App() { return ( - - - - - - - - - - - + - - - - - - - - + + + + + + + ) } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx deleted file mode 100644 index e83f7084c4..0000000000 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/_layout.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { - createFileRoute, - ErrorComponentProps, - Outlet, -} from '@tanstack/react-router' - -const Catch = ({ error }: ErrorComponentProps) => { - return ( -
-

Oops!

-

Something went wrong... Caught at _layout error boundary.

-
{error.message}
-
- ) -} - -const Pending = () =>
Loading from _layout...
- -export const Route = createFileRoute('/_layout')({ - component: Layout, - errorComponent: Catch, - pendingComponent: Pending, -}) - -function Layout() { - return -} diff --git a/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx b/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx index 605885cdad..dcd96353d7 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx +++ b/clash-nyanpasu/frontend/nyanpasu/src/pages/index.tsx @@ -1,32 +1,13 @@ import { useAtomValue } from 'jotai' -import { useEffect, useRef } from 'react' import { memorizedRoutePathAtom } from '@/store' -import { createFileRoute, useNavigate } from '@tanstack/react-router' +import { createFileRoute, Navigate } from '@tanstack/react-router' export const Route = createFileRoute('/')({ - component: IndexPage, + component: RouteComponent, }) -function IndexPage() { - const navigate = useNavigate() +function RouteComponent() { const memorizedNavigate = useAtomValue(memorizedRoutePathAtom) - const lockRef = useRef(false) - useEffect(() => { - if (lockRef.current) { - return - } - const to = - memorizedNavigate && memorizedNavigate !== '/' - ? memorizedNavigate - : '/dashboard' - - lockRef.current = true - console.log('navigate to', to) - navigate({ - to: to, - }) - }, [memorizedNavigate, navigate]) - - return null + return } diff --git a/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts b/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts index ba36b35547..72b0c2d84f 100644 --- a/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts +++ b/clash-nyanpasu/frontend/nyanpasu/src/route-tree.gen.ts @@ -9,59 +9,19 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './pages/__root' -import { Route as SettingsRouteImport } from './pages/settings' -import { Route as RulesRouteImport } from './pages/rules' -import { Route as ProxiesRouteImport } from './pages/proxies' -import { Route as ProvidersRouteImport } from './pages/providers' -import { Route as ProfilesRouteImport } from './pages/profiles' -import { Route as LogsRouteImport } from './pages/logs' -import { Route as DashboardRouteImport } from './pages/dashboard' -import { Route as ConnectionsRouteImport } from './pages/connections' -import { Route as LayoutRouteImport } from './pages/_layout' +import { Route as legacyRouteRouteImport } from './pages/(legacy)/route' import { Route as IndexRouteImport } from './pages/index' +import { Route as legacySettingsRouteImport } from './pages/(legacy)/settings' +import { Route as legacyRulesRouteImport } from './pages/(legacy)/rules' +import { Route as legacyProxiesRouteImport } from './pages/(legacy)/proxies' +import { Route as legacyProvidersRouteImport } from './pages/(legacy)/providers' +import { Route as legacyProfilesRouteImport } from './pages/(legacy)/profiles' +import { Route as legacyLogsRouteImport } from './pages/(legacy)/logs' +import { Route as legacyDashboardRouteImport } from './pages/(legacy)/dashboard' +import { Route as legacyConnectionsRouteImport } from './pages/(legacy)/connections' -const SettingsRoute = SettingsRouteImport.update({ - id: '/settings', - path: '/settings', - getParentRoute: () => rootRouteImport, -} as any) -const RulesRoute = RulesRouteImport.update({ - id: '/rules', - path: '/rules', - getParentRoute: () => rootRouteImport, -} as any) -const ProxiesRoute = ProxiesRouteImport.update({ - id: '/proxies', - path: '/proxies', - getParentRoute: () => rootRouteImport, -} as any) -const ProvidersRoute = ProvidersRouteImport.update({ - id: '/providers', - path: '/providers', - getParentRoute: () => rootRouteImport, -} as any) -const ProfilesRoute = ProfilesRouteImport.update({ - id: '/profiles', - path: '/profiles', - getParentRoute: () => rootRouteImport, -} as any) -const LogsRoute = LogsRouteImport.update({ - id: '/logs', - path: '/logs', - getParentRoute: () => rootRouteImport, -} as any) -const DashboardRoute = DashboardRouteImport.update({ - id: '/dashboard', - path: '/dashboard', - getParentRoute: () => rootRouteImport, -} as any) -const ConnectionsRoute = ConnectionsRouteImport.update({ - id: '/connections', - path: '/connections', - getParentRoute: () => rootRouteImport, -} as any) -const LayoutRoute = LayoutRouteImport.update({ - id: '/_layout', +const legacyRouteRoute = legacyRouteRouteImport.update({ + id: '/(legacy)', getParentRoute: () => rootRouteImport, } as any) const IndexRoute = IndexRouteImport.update({ @@ -69,41 +29,81 @@ const IndexRoute = IndexRouteImport.update({ path: '/', getParentRoute: () => rootRouteImport, } as any) +const legacySettingsRoute = legacySettingsRouteImport.update({ + id: '/settings', + path: '/settings', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyRulesRoute = legacyRulesRouteImport.update({ + id: '/rules', + path: '/rules', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyProxiesRoute = legacyProxiesRouteImport.update({ + id: '/proxies', + path: '/proxies', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyProvidersRoute = legacyProvidersRouteImport.update({ + id: '/providers', + path: '/providers', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyProfilesRoute = legacyProfilesRouteImport.update({ + id: '/profiles', + path: '/profiles', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyLogsRoute = legacyLogsRouteImport.update({ + id: '/logs', + path: '/logs', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyDashboardRoute = legacyDashboardRouteImport.update({ + id: '/dashboard', + path: '/dashboard', + getParentRoute: () => legacyRouteRoute, +} as any) +const legacyConnectionsRoute = legacyConnectionsRouteImport.update({ + id: '/connections', + path: '/connections', + getParentRoute: () => legacyRouteRoute, +} as any) export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/connections': typeof ConnectionsRoute - '/dashboard': typeof DashboardRoute - '/logs': typeof LogsRoute - '/profiles': typeof ProfilesRoute - '/providers': typeof ProvidersRoute - '/proxies': typeof ProxiesRoute - '/rules': typeof RulesRoute - '/settings': typeof SettingsRoute + '/connections': typeof legacyConnectionsRoute + '/dashboard': typeof legacyDashboardRoute + '/logs': typeof legacyLogsRoute + '/profiles': typeof legacyProfilesRoute + '/providers': typeof legacyProvidersRoute + '/proxies': typeof legacyProxiesRoute + '/rules': typeof legacyRulesRoute + '/settings': typeof legacySettingsRoute } export interface FileRoutesByTo { '/': typeof IndexRoute - '/connections': typeof ConnectionsRoute - '/dashboard': typeof DashboardRoute - '/logs': typeof LogsRoute - '/profiles': typeof ProfilesRoute - '/providers': typeof ProvidersRoute - '/proxies': typeof ProxiesRoute - '/rules': typeof RulesRoute - '/settings': typeof SettingsRoute + '/connections': typeof legacyConnectionsRoute + '/dashboard': typeof legacyDashboardRoute + '/logs': typeof legacyLogsRoute + '/profiles': typeof legacyProfilesRoute + '/providers': typeof legacyProvidersRoute + '/proxies': typeof legacyProxiesRoute + '/rules': typeof legacyRulesRoute + '/settings': typeof legacySettingsRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute - '/_layout': typeof LayoutRoute - '/connections': typeof ConnectionsRoute - '/dashboard': typeof DashboardRoute - '/logs': typeof LogsRoute - '/profiles': typeof ProfilesRoute - '/providers': typeof ProvidersRoute - '/proxies': typeof ProxiesRoute - '/rules': typeof RulesRoute - '/settings': typeof SettingsRoute + '/(legacy)': typeof legacyRouteRouteWithChildren + '/(legacy)/connections': typeof legacyConnectionsRoute + '/(legacy)/dashboard': typeof legacyDashboardRoute + '/(legacy)/logs': typeof legacyLogsRoute + '/(legacy)/profiles': typeof legacyProfilesRoute + '/(legacy)/providers': typeof legacyProvidersRoute + '/(legacy)/proxies': typeof legacyProxiesRoute + '/(legacy)/rules': typeof legacyRulesRoute + '/(legacy)/settings': typeof legacySettingsRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -131,93 +131,29 @@ export interface FileRouteTypes { id: | '__root__' | '/' - | '/_layout' - | '/connections' - | '/dashboard' - | '/logs' - | '/profiles' - | '/providers' - | '/proxies' - | '/rules' - | '/settings' + | '/(legacy)' + | '/(legacy)/connections' + | '/(legacy)/dashboard' + | '/(legacy)/logs' + | '/(legacy)/profiles' + | '/(legacy)/providers' + | '/(legacy)/proxies' + | '/(legacy)/rules' + | '/(legacy)/settings' fileRoutesById: FileRoutesById } export interface RootRouteChildren { IndexRoute: typeof IndexRoute - LayoutRoute: typeof LayoutRoute - ConnectionsRoute: typeof ConnectionsRoute - DashboardRoute: typeof DashboardRoute - LogsRoute: typeof LogsRoute - ProfilesRoute: typeof ProfilesRoute - ProvidersRoute: typeof ProvidersRoute - ProxiesRoute: typeof ProxiesRoute - RulesRoute: typeof RulesRoute - SettingsRoute: typeof SettingsRoute + legacyRouteRoute: typeof legacyRouteRouteWithChildren } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/settings': { - id: '/settings' - path: '/settings' - fullPath: '/settings' - preLoaderRoute: typeof SettingsRouteImport - parentRoute: typeof rootRouteImport - } - '/rules': { - id: '/rules' - path: '/rules' - fullPath: '/rules' - preLoaderRoute: typeof RulesRouteImport - parentRoute: typeof rootRouteImport - } - '/proxies': { - id: '/proxies' - path: '/proxies' - fullPath: '/proxies' - preLoaderRoute: typeof ProxiesRouteImport - parentRoute: typeof rootRouteImport - } - '/providers': { - id: '/providers' - path: '/providers' - fullPath: '/providers' - preLoaderRoute: typeof ProvidersRouteImport - parentRoute: typeof rootRouteImport - } - '/profiles': { - id: '/profiles' - path: '/profiles' - fullPath: '/profiles' - preLoaderRoute: typeof ProfilesRouteImport - parentRoute: typeof rootRouteImport - } - '/logs': { - id: '/logs' - path: '/logs' - fullPath: '/logs' - preLoaderRoute: typeof LogsRouteImport - parentRoute: typeof rootRouteImport - } - '/dashboard': { - id: '/dashboard' - path: '/dashboard' - fullPath: '/dashboard' - preLoaderRoute: typeof DashboardRouteImport - parentRoute: typeof rootRouteImport - } - '/connections': { - id: '/connections' - path: '/connections' - fullPath: '/connections' - preLoaderRoute: typeof ConnectionsRouteImport - parentRoute: typeof rootRouteImport - } - '/_layout': { - id: '/_layout' + '/(legacy)': { + id: '/(legacy)' path: '' fullPath: '' - preLoaderRoute: typeof LayoutRouteImport + preLoaderRoute: typeof legacyRouteRouteImport parentRoute: typeof rootRouteImport } '/': { @@ -227,20 +163,94 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } + '/(legacy)/settings': { + id: '/(legacy)/settings' + path: '/settings' + fullPath: '/settings' + preLoaderRoute: typeof legacySettingsRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/rules': { + id: '/(legacy)/rules' + path: '/rules' + fullPath: '/rules' + preLoaderRoute: typeof legacyRulesRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/proxies': { + id: '/(legacy)/proxies' + path: '/proxies' + fullPath: '/proxies' + preLoaderRoute: typeof legacyProxiesRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/providers': { + id: '/(legacy)/providers' + path: '/providers' + fullPath: '/providers' + preLoaderRoute: typeof legacyProvidersRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/profiles': { + id: '/(legacy)/profiles' + path: '/profiles' + fullPath: '/profiles' + preLoaderRoute: typeof legacyProfilesRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/logs': { + id: '/(legacy)/logs' + path: '/logs' + fullPath: '/logs' + preLoaderRoute: typeof legacyLogsRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/dashboard': { + id: '/(legacy)/dashboard' + path: '/dashboard' + fullPath: '/dashboard' + preLoaderRoute: typeof legacyDashboardRouteImport + parentRoute: typeof legacyRouteRoute + } + '/(legacy)/connections': { + id: '/(legacy)/connections' + path: '/connections' + fullPath: '/connections' + preLoaderRoute: typeof legacyConnectionsRouteImport + parentRoute: typeof legacyRouteRoute + } } } +interface legacyRouteRouteChildren { + legacyConnectionsRoute: typeof legacyConnectionsRoute + legacyDashboardRoute: typeof legacyDashboardRoute + legacyLogsRoute: typeof legacyLogsRoute + legacyProfilesRoute: typeof legacyProfilesRoute + legacyProvidersRoute: typeof legacyProvidersRoute + legacyProxiesRoute: typeof legacyProxiesRoute + legacyRulesRoute: typeof legacyRulesRoute + legacySettingsRoute: typeof legacySettingsRoute +} + +const legacyRouteRouteChildren: legacyRouteRouteChildren = { + legacyConnectionsRoute: legacyConnectionsRoute, + legacyDashboardRoute: legacyDashboardRoute, + legacyLogsRoute: legacyLogsRoute, + legacyProfilesRoute: legacyProfilesRoute, + legacyProvidersRoute: legacyProvidersRoute, + legacyProxiesRoute: legacyProxiesRoute, + legacyRulesRoute: legacyRulesRoute, + legacySettingsRoute: legacySettingsRoute, +} + +const legacyRouteRouteWithChildren = legacyRouteRoute._addFileChildren( + legacyRouteRouteChildren, +) + const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, - LayoutRoute: LayoutRoute, - ConnectionsRoute: ConnectionsRoute, - DashboardRoute: DashboardRoute, - LogsRoute: LogsRoute, - ProfilesRoute: ProfilesRoute, - ProvidersRoute: ProvidersRoute, - ProxiesRoute: ProxiesRoute, - RulesRoute: RulesRoute, - SettingsRoute: SettingsRoute, + legacyRouteRoute: legacyRouteRouteWithChildren, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts index 86a35cd102..b11c6f91e9 100644 --- a/clash-nyanpasu/frontend/nyanpasu/vite.config.ts +++ b/clash-nyanpasu/frontend/nyanpasu/vite.config.ts @@ -16,18 +16,6 @@ import react from '@vitejs/plugin-react-swc' const IS_NIGHTLY = process.env.NIGHTLY?.toLowerCase() === 'true' -const devtools = () => { - return { - name: 'react-devtools', - transformIndexHtml(html: string) { - return html.replace( - /<\/head>/, - ``, - ) - }, - } -} - const builtinVars = () => { return { name: 'built-in-vars', @@ -123,7 +111,6 @@ export default defineConfig(({ command, mode }) => { compiler: 'jsx', // or 'solid' }), sassDts({ esmExport: true }), - isDev && devtools(), ], resolve: { alias: { diff --git a/clash-nyanpasu/package.json b/clash-nyanpasu/package.json index 8c7f32d2a7..8d8cf06ee8 100644 --- a/clash-nyanpasu/package.json +++ b/clash-nyanpasu/package.json @@ -5,8 +5,8 @@ "license": "GPL-3.0", "type": "module", "scripts": { - "dev": "run-p -r web:devtools tauri:dev", - "dev:diff": "run-p -r web:devtools tauri:diff", + "dev": "run-p tauri:dev", + "dev:diff": "run-p tauri:diff", "build": "tauri build", "build:debug": "tauri build -f verge-dev deadlock-detection -d -c \"{ \\\"tauri\\\" : { \\\"updater\\\": { \\\"active\\\": false } }} \"", "build:nightly": "tauri build -f nightly -c ./backend/tauri/tauri.nightly.conf.json", @@ -18,7 +18,6 @@ "web:build": "pnpm --filter=@nyanpasu/nyanpasu build", "web:serve": "pnpm --filter=@nyanpasu/nyanpasu preview", "web:visualize": "pnpm --filter=@nyanpasu/nyanpasu bundle:visualize", - "web:devtools": "pnpm react-devtools", "lint": "run-s lint:*", "lint:prettier": "prettier --check .", "lint:eslint": "eslint --cache .", @@ -36,7 +35,6 @@ "fmt": "run-p fmt:*", "fmt:backend": "cargo fmt --manifest-path ./backend/Cargo.toml --all", "fmt:prettier": "prettier --write .", - "check": "tsx scripts/check.ts", "updater": "tsx scripts/updater.ts", "updater:nightly": "tsx scripts/updater-nightly.ts", "send-notify": "tsx scripts/telegram-notify.ts", @@ -50,7 +48,8 @@ "prepare": "husky", "prepare:nightly": "tsx scripts/prepare-nightly.ts", "prepare:release": "tsx scripts/prepare-release.ts", - "prepare:preview": "tsx scripts/prepare-preview.ts" + "prepare:preview": "tsx scripts/prepare-preview.ts", + "prepare:check": "tsx scripts/check.ts" }, "dependencies": { "@prettier/plugin-oxc": "0.0.4", @@ -97,7 +96,6 @@ "prettier-plugin-ember-template-tag": "2.1.0", "prettier-plugin-tailwindcss": "0.7.1", "prettier-plugin-toml": "2.0.6", - "react-devtools": "7.0.1", "stylelint": "16.25.0", "stylelint-config-html": "1.1.0", "stylelint-config-recess-order": "7.4.0", diff --git a/clash-nyanpasu/pnpm-lock.yaml b/clash-nyanpasu/pnpm-lock.yaml index 7dbbfc5751..ed966803c8 100644 --- a/clash-nyanpasu/pnpm-lock.yaml +++ b/clash-nyanpasu/pnpm-lock.yaml @@ -139,9 +139,6 @@ importers: prettier-plugin-toml: specifier: 2.0.6 version: 2.0.6(prettier@3.6.2) - react-devtools: - specifier: 7.0.1 - version: 7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) stylelint: specifier: 16.25.0 version: 16.25.0(typescript@5.9.3) @@ -1507,10 +1504,6 @@ packages: '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@electron/get@2.0.3': - resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} - engines: {node: '>=12'} - '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -2921,10 +2914,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} - '@stylistic/eslint-plugin@2.11.0': resolution: {integrity: sha512-PNRHbydNG5EH8NK4c+izdJlxajIR6GxcUhzsYNRsn6Myep4dsZt0qFCz3rCPnkvgO5FYibDcMqgNHUT+zvjYZw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3074,10 +3063,6 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} - '@tailwindcss/node@4.1.17': resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} @@ -3422,9 +3407,6 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - '@types/cacheable-request@6.0.3': - resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - '@types/conventional-commits-parser@5.0.0': resolution: {integrity: sha512-loB369iXNmAZglwWATL+WRe+CRMmmBPtpolYzIebFaX4YA3x+BEfLqhUAV9WanycKI3TG1IMr5bMJDajDKLlUQ==} @@ -3545,9 +3527,6 @@ packages: '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/http-cache-semantics@4.0.4': - resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} - '@types/js-cookie@2.2.7': resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} @@ -3563,9 +3542,6 @@ packages: '@types/jsonfile@6.1.4': resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} - '@types/keyv@3.1.4': - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -3578,9 +3554,6 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@16.18.108': - resolution: {integrity: sha512-fj42LD82fSv6yN9C6Q4dzS+hujHj+pTv0IpRR3kI20fnYeS0ytBpjFO9OjmDowSPPt4lNKN46JLaKbCyP+BW2A==} - '@types/node@24.10.1': resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} @@ -3609,9 +3582,6 @@ packages: '@types/react@19.2.7': resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} - '@types/responselike@1.0.3': - resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} - '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -3630,9 +3600,6 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.46.3': resolution: {integrity: sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3948,17 +3915,10 @@ packages: react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - ansi-align@2.0.0: - resolution: {integrity: sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==} - ansi-escapes@7.0.0: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} - ansi-regex@3.0.1: - resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} - engines: {node: '>=4'} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3967,10 +3927,6 @@ packages: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -4128,17 +4084,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} - boxen@1.3.0: - resolution: {integrity: sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==} - engines: {node: '>=4'} - brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -4169,9 +4117,6 @@ packages: buffer-builder@0.2.0: resolution: {integrity: sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==} - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -4186,14 +4131,6 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - cacheable-lookup@5.0.4: - resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} - engines: {node: '>=10.6.0'} - - cacheable-request@7.0.4: - resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} - engines: {node: '>=8'} - cacheable@1.10.4: resolution: {integrity: sha512-Gd7ccIUkZ9TE2odLQVS+PDjIvQCdJKUlLdJRVvZu0aipj07Qfx+XIej7hhDrKGGoIxV5m5fT/kOJNJPQhQneRg==} @@ -4232,10 +4169,6 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - camelcase@4.1.0: - resolution: {integrity: sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==} - engines: {node: '>=4'} - camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} @@ -4246,17 +4179,9 @@ packages: caniuse-lite@1.0.30001756: resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} - capture-stack-trace@1.0.2: - resolution: {integrity: sha512-X/WM2UQs6VMHUtjUDnZTRI+i1crWteJySFzr9UpGoQa4WQffXVTTXuekjl7TjZRlcF2XfjgITT0HxZ9RnxeT0w==} - engines: {node: '>=0.10.0'} - ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -4292,9 +4217,6 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - ci-info@1.6.0: - resolution: {integrity: sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==} - classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -4302,10 +4224,6 @@ packages: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} - cli-boxes@1.0.0: - resolution: {integrity: sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==} - engines: {node: '>=0.10.0'} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -4322,23 +4240,14 @@ packages: resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} engines: {node: '>=20'} - clone-response@1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -4394,10 +4303,6 @@ packages: confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - configstore@3.1.5: - resolution: {integrity: sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==} - engines: {node: '>=4'} - connect-history-api-fallback@1.6.0: resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} engines: {node: '>=0.8'} @@ -4486,26 +4391,15 @@ packages: country-emoji@1.5.6: resolution: {integrity: sha512-pSB8OOROfimFc2bcN+H41DuzXYIod/JQ6SgF4pYXkRCm9f8uF1JAJ0vXPhenug6xkpt3Gv33mdypMXB49CJWRA==} - create-error-class@3.0.2: - resolution: {integrity: sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==} - engines: {node: '>=0.10.0'} - cross-env@10.1.0: resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} engines: {node: '>=20'} hasBin: true - cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypto-random-string@1.0.0: - resolution: {integrity: sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==} - engines: {node: '>=4'} - css-functions-list@3.2.3: resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} engines: {node: '>=12 || >=16'} @@ -4762,10 +4656,6 @@ packages: decode-named-character-reference@1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - dedent@1.7.0: resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} peerDependencies: @@ -4774,21 +4664,9 @@ packages: babel-plugin-macros: optional: true - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - default-gateway@6.0.3: - resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} - engines: {node: '>= 10'} - - defer-to-connect@2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -4823,9 +4701,6 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - detect-node@2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -4876,10 +4751,6 @@ packages: dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dot-prop@4.2.1: - resolution: {integrity: sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==} - engines: {node: '>=4'} - dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -4896,9 +4767,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -4910,11 +4778,6 @@ packages: electron-to-chromium@1.5.258: resolution: {integrity: sha512-rHUggNV5jKQ0sSdWwlaRDkFc3/rRJIVnOSe9yR4zrR07m3ZxhP4N27Hlg8VeJGGYgFTxK5NqDmWI4DSH72vIJg==} - electron@23.3.13: - resolution: {integrity: sha512-BaXtHEb+KYKLouUXlUVDa/lj9pj4F5kiE0kwFdJV84Y2EU7euIDgPthfKtchhr5MVHmjtavRMIV/zAwEiSQ9rQ==} - engines: {node: '>= 12.20.55'} - hasBin: true - emoji-regex-xs@1.0.0: resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} @@ -4924,9 +4787,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - enhanced-resolve@5.18.3: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} @@ -5023,9 +4883,6 @@ packages: resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==} engines: {node: '>=0.10'} - es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - es6-iterator@2.0.3: resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==} @@ -5042,10 +4899,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -5269,14 +5122,6 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - execa@0.7.0: - resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==} - engines: {node: '>=4'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - exsolve@1.0.4: resolution: {integrity: sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==} @@ -5289,11 +5134,6 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -5336,9 +5176,6 @@ packages: fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -5429,10 +5266,6 @@ packages: resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} engines: {node: '>=14.14'} - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5487,18 +5320,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@3.0.0: - resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} - engines: {node: '>=4'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -5527,18 +5348,10 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - global-agent@3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} - global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} - global-dirs@0.1.1: - resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} - engines: {node: '>=4'} - global-modules@2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} engines: {node: '>=6'} @@ -5589,14 +5402,6 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - got@11.8.6: - resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} - engines: {node: '>=10.19.0'} - - got@6.7.1: - resolution: {integrity: sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==} - engines: {node: '>=4'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -5606,10 +5411,6 @@ packages: has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -5697,17 +5498,6 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - - http2-wrapper@1.0.3: - resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} - engines: {node: '>=10.19.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -5761,10 +5551,6 @@ packages: resolution: {integrity: sha512-7EyUlPFC0HOlBDpUFGfYstsU7XHxZJKAAMzCT8wZ0hMW7b+hG51LIKTDcsgtz8Pu6YC0HqRVbX+rVUtsGMUKvg==} engines: {node: '>=16.20'} - import-lazy@2.1.0: - resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} - engines: {node: '>=4'} - import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} engines: {node: '>=8'} @@ -5796,10 +5582,6 @@ packages: inline-style-prefixer@7.0.1: resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} - internal-ip@6.2.0: - resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==} - engines: {node: '>=10'} - internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -5820,14 +5602,6 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} - ip-regex@4.3.0: - resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==} - engines: {node: '>=8'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -5875,10 +5649,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@1.2.1: - resolution: {integrity: sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==} - hasBin: true - is-core-module@2.15.1: resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} engines: {node: '>= 0.4'} @@ -5919,10 +5689,6 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-fullwidth-code-point@2.0.0: - resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} - engines: {node: '>=4'} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -5942,14 +5708,6 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-installed-globally@0.1.0: - resolution: {integrity: sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==} - engines: {node: '>=4'} - - is-ip@3.1.0: - resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==} - engines: {node: '>=8'} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -5962,10 +5720,6 @@ packages: resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} engines: {node: '>=16'} - is-npm@1.0.0: - resolution: {integrity: sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==} - engines: {node: '>=0.10.0'} - is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -5978,18 +5732,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@1.0.1: - resolution: {integrity: sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==} - engines: {node: '>=0.10.0'} - is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} - is-path-inside@1.0.1: - resolution: {integrity: sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==} - engines: {node: '>=0.10.0'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -5998,10 +5744,6 @@ packages: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} - is-redirect@1.0.0: - resolution: {integrity: sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==} - engines: {node: '>=0.10.0'} - is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -6010,10 +5752,6 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} - is-retry-allowed@1.2.0: - resolution: {integrity: sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==} - engines: {node: '>=0.10.0'} - is-set@2.0.3: resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} engines: {node: '>= 0.4'} @@ -6026,14 +5764,6 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -6199,9 +5929,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -6214,9 +5941,6 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -6255,10 +5979,6 @@ packages: kolorist@1.8.0: resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - latest-version@3.1.0: - resolution: {integrity: sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==} - engines: {node: '>=4'} - less@4.2.0: resolution: {integrity: sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==} engines: {node: '>=6'} @@ -6430,17 +6150,6 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lowercase-keys@1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} - - lowercase-keys@2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - - lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -6454,10 +6163,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - make-dir@1.3.0: - resolution: {integrity: sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==} - engines: {node: '>=4'} - make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} engines: {node: '>=6'} @@ -6467,10 +6172,6 @@ packages: engines: {node: '>= 18'} hasBin: true - matcher@3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -6523,9 +6224,6 @@ packages: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -6611,22 +6309,10 @@ packages: engines: {node: '>=10.0.0'} hasBin: true - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - mimic-response@1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - - mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} @@ -6785,10 +6471,6 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - npm-normalize-package-bin@4.0.0: resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} engines: {node: ^18.17.0 || >=20.5.0} @@ -6798,14 +6480,6 @@ packages: engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} hasBin: true - npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -6863,10 +6537,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} @@ -6893,18 +6563,6 @@ packages: oxc-resolver@11.12.0: resolution: {integrity: sha512-zmS2q2txiB+hS2u0aiIwmvITIJN8c8ThlWoWB762Wx5nUw8WBlttp0rzt8nnuP1cGIq9YJ7sGxfsgokm+SQk5Q==} - p-cancelable@2.1.1: - resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} - engines: {node: '>=8'} - - p-event@4.2.0: - resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} - engines: {node: '>=8'} - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -6925,14 +6583,6 @@ packages: resolution: {integrity: sha512-xL4PiFRQa/f9L9ZvR4/gUCRNus4N8YX80ku8kv9Jqz+ZokkiZLM0bcvX0gm1F3PDi9SPRsww1BDsTWgE6Y1GLQ==} engines: {node: '>=20'} - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - package-json@4.0.1: - resolution: {integrity: sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==} - engines: {node: '>=4'} - package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -6975,13 +6625,6 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - path-is-inside@1.0.2: - resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} - - path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -6999,9 +6642,6 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - peowly@1.3.2: resolution: {integrity: sha512-BYIrwr8JCXY49jUZscgw311w9oGEKo7ux/s+BxrhKTQbiQ0iYNdZNJ5LgagaeercQdFHwnR7Z5IxxFWVQ+BasQ==} engines: {node: '>=18.6.0'} @@ -7030,10 +6670,6 @@ packages: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - pify@4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} @@ -7145,10 +6781,6 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prepend-http@1.0.4: - resolution: {integrity: sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==} - engines: {node: '>=0.10.0'} - prettier-linter-helpers@1.0.0: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} @@ -7225,10 +6857,6 @@ packages: engines: {node: '>=14'} hasBin: true - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -7238,12 +6866,6 @@ packages: prr@1.0.1: resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - pseudomap@1.0.2: - resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} - - pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -7257,21 +6879,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - - react-devtools-core@7.0.1: - resolution: {integrity: sha512-C3yNvRHaizlpiASzy7b9vbnBGLrhvdhl1CbdU6EnZgxPNbai60szdLtl+VL76UNOt5bOoVTOz5rNWZxgGt+Gsw==} - - react-devtools@7.0.1: - resolution: {integrity: sha512-I2UXoJlsqNeN3uCrlrJw0V+K6HTHU5Q1x+BWJlF99hBC6A/lKhe+IuITZXyKb8BluMReSBYUhUYGkJGgr2KPQQ==} - hasBin: true - react-dom@19.2.0: resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} peerDependencies: @@ -7435,13 +7042,6 @@ packages: resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} engines: {node: '>=4'} - registry-auth-token@3.4.0: - resolution: {integrity: sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==} - - registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} - regjsgen@0.8.0: resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} @@ -7479,9 +7079,6 @@ packages: resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - resolve-alpn@1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -7506,9 +7103,6 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - responselike@2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -7520,10 +7114,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - roarr@2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -7562,9 +7152,6 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -7712,13 +7299,6 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - - semver-diff@2.1.0: - resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==} - engines: {node: '>=0.10.0'} - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -7742,10 +7322,6 @@ packages: engines: {node: '>=10'} hasBin: true - serialize-error@7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} - seroval-plugins@1.3.2: resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} engines: {node: '>=10'} @@ -7772,18 +7348,10 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} - shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -7810,9 +7378,6 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -7922,10 +7487,6 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - string-width@2.1.1: - resolution: {integrity: sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==} - engines: {node: '>=4'} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -7964,10 +7525,6 @@ packages: stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - strip-ansi@4.0.0: - resolution: {integrity: sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==} - engines: {node: '>=4'} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -7980,18 +7537,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -8064,14 +7609,6 @@ packages: resolution: {integrity: sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==} hasBin: true - sumchecker@3.0.1: - resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} - engines: {node: '>= 8.0'} - - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -8135,10 +7672,6 @@ packages: telegram@2.26.22: resolution: {integrity: sha512-EIj7Yrjiu0Yosa3FZ/7EyPg9s6UiTi/zDQrFmR/2Mg7pIUU+XjAit1n1u9OU9h2oRnRM5M+67/fxzQluZpaJJg==} - term-size@1.2.0: - resolution: {integrity: sha512-7dPUZQGy/+m3/wjVz3ZW5dobSoD/02NxJpoXUX0WIyjfVS3l0c+b/+9phIDFA7FHzkYtwtMFgeGZ/Y8jVTeqQQ==} - engines: {node: '>=4'} - terser@5.36.0: resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} @@ -8155,10 +7688,6 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - timed-out@4.0.1: - resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} - engines: {node: '>=0.10.0'} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -8255,10 +7784,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - type@2.7.2: resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==} @@ -8367,10 +7892,6 @@ packages: resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==} engines: {node: '>=18.12.0'} - unique-string@1.0.0: - resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==} - engines: {node: '>=4'} - unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -8398,10 +7919,6 @@ packages: universal-user-agent@7.0.2: resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -8456,10 +7973,6 @@ packages: unrs-resolver@1.10.1: resolution: {integrity: sha512-EFrL7Hw4kmhZdwWO3dwwFJo6hO3FXuQ6Bg8BK/faHZ9m1YxqBS31BNSTxklIQkxK/4LlV8zTYnPsIRLBzTzjCA==} - unzip-response@2.0.1: - resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==} - engines: {node: '>=4'} - update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -8472,17 +7985,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-notifier@2.5.0: - resolution: {integrity: sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==} - engines: {node: '>=4'} - uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse-lax@1.0.0: - resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} - engines: {node: '>=0.10.0'} - use-resize-observer@9.1.0: resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==} peerDependencies: @@ -8692,10 +8197,6 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - widest-line@2.0.1: - resolution: {integrity: sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==} - engines: {node: '>=4'} - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -8714,29 +8215,10 @@ packages: write-file-atomic@1.3.4: resolution: {integrity: sha512-SdrHoC/yVBPpV0Xq/mUZQIpW2sWXAShb/V4pomcJXh92RuaO+f3UTWItiR3Px+pLnV2PvC2/bfn5cwr5X6Vfxw==} - write-file-atomic@2.4.3: - resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} - write-file-atomic@5.0.1: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xdg-basedir@3.0.0: - resolution: {integrity: sha512-1Dly4xqlulvPD3fZUQJLY+FUIeqN3N2MM3uqe4rCJftAvOjFa3jFGfctOgluGx4ahPbUCsZkmJILiP0Vi4T6lQ==} - engines: {node: '>=4'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -8746,9 +8228,6 @@ packages: engines: {node: '>=0.10.32'} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - yallist@2.1.2: - resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -8789,9 +8268,6 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -10117,20 +9593,6 @@ snapshots: '@dual-bundle/import-meta-resolve@4.2.1': {} - '@electron/get@2.0.3': - dependencies: - debug: 4.4.3 - env-paths: 2.2.1 - fs-extra: 8.1.0 - got: 11.8.6 - progress: 2.0.3 - semver: 6.3.1 - sumchecker: 3.0.1 - optionalDependencies: - global-agent: 3.0.0 - transitivePeerDependencies: - - supports-color - '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -10599,7 +10061,7 @@ snapshots: '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 19.2.0 optionalDependencies: @@ -10612,7 +10074,7 @@ snapshots: '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 19.2.0 optionalDependencies: @@ -11440,8 +10902,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sindresorhus/is@4.6.0': {} - '@stylistic/eslint-plugin@2.11.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/utils': 8.46.2(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) @@ -11576,10 +11036,6 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@szmarczak/http-timer@4.0.6': - dependencies: - defer-to-connect: 2.0.1 - '@tailwindcss/node@4.1.17': dependencies: '@jridgewell/remapping': 2.3.5 @@ -11949,13 +11405,6 @@ snapshots: dependencies: '@babel/types': 7.28.4 - '@types/cacheable-request@6.0.3': - dependencies: - '@types/http-cache-semantics': 4.0.4 - '@types/keyv': 3.1.4 - '@types/node': 24.10.1 - '@types/responselike': 1.0.3 - '@types/conventional-commits-parser@5.0.0': dependencies: '@types/node': 24.10.1 @@ -12102,8 +11551,6 @@ snapshots: dependencies: '@types/unist': 3.0.2 - '@types/http-cache-semantics@4.0.4': {} - '@types/js-cookie@2.2.7': {} '@types/js-cookie@3.0.6': {} @@ -12116,10 +11563,6 @@ snapshots: dependencies: '@types/node': 24.10.1 - '@types/keyv@3.1.4': - dependencies: - '@types/node': 24.10.1 - '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.7 @@ -12132,8 +11575,6 @@ snapshots: '@types/ms@0.7.34': {} - '@types/node@16.18.108': {} - '@types/node@24.10.1': dependencies: undici-types: 7.16.0 @@ -12162,10 +11603,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/responselike@1.0.3': - dependencies: - '@types/node': 24.10.1 - '@types/semver@7.7.1': {} '@types/unist@2.0.10': {} @@ -12180,11 +11617,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 24.10.1 - optional: true - '@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -12562,24 +11994,14 @@ snapshots: react-dom: 19.2.0(react@19.2.0) use-resize-observer: 9.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - ansi-align@2.0.0: - dependencies: - string-width: 2.1.1 - ansi-escapes@7.0.0: dependencies: environment: 1.1.0 - ansi-regex@3.0.1: {} - ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -12741,7 +12163,7 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 cosmiconfig: 7.1.0 - resolve: 1.22.8 + resolve: 1.22.10 babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.0): dependencies: @@ -12787,21 +12209,8 @@ snapshots: boolbase@1.0.0: {} - boolean@3.2.0: - optional: true - bottleneck@2.19.5: {} - boxen@1.3.0: - dependencies: - ansi-align: 2.0.0 - camelcase: 4.1.0 - chalk: 2.4.2 - cli-boxes: 1.0.0 - string-width: 2.1.1 - term-size: 1.2.0 - widest-line: 2.0.1 - brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -12837,8 +12246,6 @@ snapshots: buffer-builder@0.2.0: {} - buffer-crc32@0.2.13: {} - buffer-from@1.1.2: {} buffer@6.0.3: @@ -12852,18 +12259,6 @@ snapshots: cac@6.7.14: {} - cacheable-lookup@5.0.4: {} - - cacheable-request@7.0.4: - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.1.1 - keyv: 4.5.4 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 - cacheable@1.10.4: dependencies: hookified: 1.11.0 @@ -12913,24 +12308,14 @@ snapshots: camelcase-css@2.0.1: {} - camelcase@4.1.0: {} - camelcase@6.3.0: {} caniuse-lite@1.0.30001726: {} caniuse-lite@1.0.30001756: {} - capture-stack-trace@1.0.2: {} - ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -12966,16 +12351,12 @@ snapshots: chownr@3.0.0: {} - ci-info@1.6.0: {} - classnames@2.5.1: {} clean-css@5.3.3: dependencies: source-map: 0.6.1 - cli-boxes@1.0.0: {} - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -12997,22 +12378,12 @@ snapshots: strip-ansi: 7.1.0 wrap-ansi: 9.0.0 - clone-response@1.0.3: - dependencies: - mimic-response: 1.0.1 - clsx@2.1.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} colord@2.9.3: {} @@ -13050,15 +12421,6 @@ snapshots: confbox@0.2.2: {} - configstore@3.1.5: - dependencies: - dot-prop: 4.2.1 - graceful-fs: 4.2.11 - make-dir: 1.3.0 - unique-string: 1.0.0 - write-file-atomic: 2.4.3 - xdg-basedir: 3.0.0 - connect-history-api-fallback@1.6.0: {} consola@2.15.3: {} @@ -13143,29 +12505,17 @@ snapshots: country-emoji@1.5.6: {} - create-error-class@3.0.2: - dependencies: - capture-stack-trace: 1.0.2 - cross-env@10.1.0: dependencies: '@epic-web/invariant': 1.0.0 cross-spawn: 7.0.6 - cross-spawn@5.1.0: - dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crypto-random-string@1.0.0: {} - css-functions-list@3.2.3: {} css-in-js-utils@3.1.0: @@ -13427,24 +12777,12 @@ snapshots: dependencies: character-entities: 2.0.2 - decompress-response@6.0.0: - dependencies: - mimic-response: 3.1.0 - dedent@1.7.0(babel-plugin-macros@3.1.0): optionalDependencies: babel-plugin-macros: 3.1.0 - deep-extend@0.6.0: {} - deep-is@0.1.4: {} - default-gateway@6.0.3: - dependencies: - execa: 5.1.1 - - defer-to-connect@2.0.1: {} - define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -13474,9 +12812,6 @@ snapshots: detect-libc@2.0.4: {} - detect-node@2.1.0: - optional: true - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -13494,7 +12829,7 @@ snapshots: dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.28.4 - csstype: 3.1.3 + csstype: 3.2.3 dom-serializer@1.4.1: dependencies: @@ -13543,10 +12878,6 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 - dot-prop@4.2.1: - dependencies: - is-obj: 1.0.1 - dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -13561,8 +12892,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplexer3@0.1.5: {} - ejs@3.1.10: dependencies: jake: 10.9.2 @@ -13571,24 +12900,12 @@ snapshots: electron-to-chromium@1.5.258: {} - electron@23.3.13: - dependencies: - '@electron/get': 2.0.3 - '@types/node': 16.18.108 - extract-zip: 2.0.1 - transitivePeerDependencies: - - supports-color - emoji-regex-xs@1.0.0: {} emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} - end-of-stream@1.4.4: - dependencies: - once: 1.4.0 - enhanced-resolve@5.18.3: dependencies: graceful-fs: 4.2.11 @@ -13848,9 +13165,6 @@ snapshots: esniff: 2.0.1 next-tick: 1.1.0 - es6-error@4.1.1: - optional: true - es6-iterator@2.0.3: dependencies: d: 1.0.2 @@ -13892,8 +13206,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} @@ -14184,28 +13496,6 @@ snapshots: eventemitter3@5.0.1: {} - execa@0.7.0: - dependencies: - cross-spawn: 5.1.0 - get-stream: 3.0.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - exsolve@1.0.4: {} exsolve@1.0.7: {} @@ -14216,16 +13506,6 @@ snapshots: extend@3.0.2: {} - extract-zip@2.0.1: - dependencies: - debug: 4.4.3 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - fast-content-type-parse@3.0.0: {} fast-deep-equal@3.1.3: {} @@ -14268,10 +13548,6 @@ snapshots: dependencies: walk-up-path: 4.0.0 - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 - fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -14360,12 +13636,6 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 - fs-extra@8.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - fs.realpath@1.0.0: optional: true @@ -14439,14 +13709,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@3.0.0: {} - - get-stream@5.2.0: - dependencies: - pump: 3.0.0 - - get-stream@6.0.1: {} - get-symbol-description@1.0.2: dependencies: call-bind: 1.0.8 @@ -14487,24 +13749,10 @@ snapshots: path-is-absolute: 1.0.1 optional: true - global-agent@3.0.0: - dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 - semver: 7.7.3 - serialize-error: 7.0.1 - optional: true - global-directory@4.0.1: dependencies: ini: 4.1.1 - global-dirs@0.1.1: - dependencies: - ini: 1.3.8 - global-modules@2.0.0: dependencies: global-prefix: 3.0.0 @@ -14552,44 +13800,12 @@ snapshots: gopd@1.2.0: {} - got@11.8.6: - dependencies: - '@sindresorhus/is': 4.6.0 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.3 - cacheable-lookup: 5.0.4 - cacheable-request: 7.0.4 - decompress-response: 6.0.0 - http2-wrapper: 1.0.3 - lowercase-keys: 2.0.0 - p-cancelable: 2.1.1 - responselike: 2.0.1 - - got@6.7.1: - dependencies: - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.3 - create-error-class: 3.0.2 - duplexer3: 0.1.5 - get-stream: 3.0.0 - is-redirect: 1.0.0 - is-retry-allowed: 1.2.0 - is-stream: 1.1.0 - lowercase-keys: 1.0.1 - safe-buffer: 5.2.1 - timed-out: 4.0.1 - unzip-response: 2.0.1 - url-parse-lax: 1.0.0 - graceful-fs@4.2.11: {} graphemer@1.4.0: {} has-bigints@1.0.2: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -14709,15 +13925,6 @@ snapshots: domutils: 3.1.0 entities: 4.5.0 - http-cache-semantics@4.1.1: {} - - http2-wrapper@1.0.3: - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - - human-signals@2.1.0: {} - husky@9.1.7: {} hyphenate-style-name@1.1.0: {} @@ -14759,8 +13966,6 @@ snapshots: transitivePeerDependencies: - supports-color - import-lazy@2.1.0: {} - import-lazy@4.0.0: {} import-meta-resolve@4.1.0: {} @@ -14786,13 +13991,6 @@ snapshots: dependencies: css-in-js-utils: 3.1.0 - internal-ip@6.2.0: - dependencies: - default-gateway: 6.0.3 - ipaddr.js: 1.9.1 - is-ip: 3.1.0 - p-event: 4.2.0 - internal-slot@1.0.7: dependencies: es-errors: 1.3.0 @@ -14814,10 +14012,6 @@ snapshots: jsbn: 1.1.0 sprintf-js: 1.1.3 - ip-regex@4.3.0: {} - - ipaddr.js@1.9.1: {} - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -14870,10 +14064,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@1.2.1: - dependencies: - ci-info: 1.6.0 - is-core-module@2.15.1: dependencies: hasown: 2.0.2 @@ -14911,8 +14101,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-fullwidth-code-point@2.0.0: {} - is-fullwidth-code-point@3.0.0: {} is-fullwidth-code-point@5.0.0: @@ -14929,23 +14117,12 @@ snapshots: is-hexadecimal@2.0.1: {} - is-installed-globally@0.1.0: - dependencies: - global-dirs: 0.1.1 - is-path-inside: 1.0.1 - - is-ip@3.1.0: - dependencies: - ip-regex: 4.3.0 - is-map@2.0.3: {} is-negative-zero@2.0.3: {} is-network-error@1.1.0: {} - is-npm@1.0.0: {} - is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -14957,20 +14134,12 @@ snapshots: is-number@7.0.0: {} - is-obj@1.0.1: {} - is-obj@2.0.0: {} - is-path-inside@1.0.1: - dependencies: - path-is-inside: 1.0.2 - is-plain-obj@4.1.0: {} is-plain-object@5.0.0: {} - is-redirect@1.0.0: {} - is-regex@1.1.4: dependencies: call-bind: 1.0.8 @@ -14983,8 +14152,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - is-retry-allowed@1.2.0: {} - is-set@2.0.3: {} is-shared-array-buffer@1.0.3: @@ -14995,10 +14162,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-stream@1.1.0: {} - - is-stream@2.0.1: {} - is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -15130,9 +14293,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: - optional: true - json5@1.0.2: dependencies: minimist: 1.2.8 @@ -15141,10 +14301,6 @@ snapshots: jsonc-parser@3.3.1: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -15193,10 +14349,6 @@ snapshots: kolorist@1.8.0: {} - latest-version@3.1.0: - dependencies: - package-json: 4.0.1 - less@4.2.0: dependencies: copy-anything: 2.0.6 @@ -15356,15 +14508,6 @@ snapshots: dependencies: tslib: 2.8.1 - lowercase-keys@1.0.1: {} - - lowercase-keys@2.0.0: {} - - lru-cache@4.1.5: - dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -15381,10 +14524,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - make-dir@1.3.0: - dependencies: - pify: 3.0.0 - make-dir@2.1.0: dependencies: pify: 4.0.1 @@ -15393,11 +14532,6 @@ snapshots: marked@14.0.0: {} - matcher@3.0.0: - dependencies: - escape-string-regexp: 4.0.0 - optional: true - math-intrinsics@1.1.0: {} mathml-tag-names@2.1.3: {} @@ -15503,8 +14637,6 @@ snapshots: meow@13.2.0: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} meta-json-schema@1.19.16: {} @@ -15652,14 +14784,8 @@ snapshots: mime@3.0.0: {} - mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} - mimic-response@1.0.1: {} - - mimic-response@3.1.0: {} - minimatch@3.0.8: dependencies: brace-expansion: 1.1.11 @@ -15838,8 +14964,6 @@ snapshots: normalize-range@0.1.2: {} - normalize-url@6.1.0: {} - npm-normalize-package-bin@4.0.0: {} npm-run-all2@8.0.4: @@ -15853,14 +14977,6 @@ snapshots: shell-quote: 1.8.1 which: 5.0.0 - npm-run-path@2.0.2: - dependencies: - path-key: 2.0.1 - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -15942,10 +15058,6 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - onetime@7.0.0: dependencies: mimic-function: 5.0.1 @@ -16019,14 +15131,6 @@ snapshots: '@oxc-resolver/binding-win32-ia32-msvc': 11.12.0 '@oxc-resolver/binding-win32-x64-msvc': 11.12.0 - p-cancelable@2.1.1: {} - - p-event@4.2.0: - dependencies: - p-timeout: 3.2.0 - - p-finally@1.0.0: {} - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -16047,17 +15151,6 @@ snapshots: dependencies: is-network-error: 1.1.0 - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - package-json@4.0.1: - dependencies: - got: 6.7.1 - registry-auth-token: 3.4.0 - registry-url: 3.1.0 - semver: 5.7.2 - package-manager-detector@1.3.0: {} pako@2.1.0: {} @@ -16105,10 +15198,6 @@ snapshots: path-is-absolute@1.0.1: optional: true - path-is-inside@1.0.2: {} - - path-key@2.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -16119,8 +15208,6 @@ snapshots: pathe@2.0.3: {} - pend@1.2.0: {} - peowly@1.3.2: {} picocolors@1.1.1: {} @@ -16135,8 +15222,6 @@ snapshots: pify@2.3.0: {} - pify@3.0.0: {} - pify@4.0.1: optional: true @@ -16242,8 +15327,6 @@ snapshots: prelude-ls@1.2.1: {} - prepend-http@1.0.4: {} - prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 @@ -16271,8 +15354,6 @@ snapshots: prettier@3.6.2: {} - progress@2.0.3: {} - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -16284,13 +15365,6 @@ snapshots: prr@1.0.1: optional: true - pseudomap@1.0.2: {} - - pump@3.0.0: - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - punycode@2.3.1: {} quansync@0.2.10: {} @@ -16299,36 +15373,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@5.1.1: {} - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - - react-devtools-core@7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): - dependencies: - shell-quote: 1.8.1 - ws: 7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - react-devtools@7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10): - dependencies: - cross-spawn: 5.1.0 - electron: 23.3.13 - internal-ip: 6.2.0 - minimist: 1.2.8 - react-devtools-core: 7.0.1(bufferutil@4.0.8)(utf-8-validate@5.0.10) - update-notifier: 2.5.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - react-dom@19.2.0(react@19.2.0): dependencies: react: 19.2.0 @@ -16522,15 +15566,6 @@ snapshots: unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.2.0 - registry-auth-token@3.4.0: - dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 - - registry-url@3.1.0: - dependencies: - rc: 1.2.8 - regjsgen@0.8.0: {} regjsparser@0.12.0: @@ -16568,8 +15603,6 @@ snapshots: resize-observer-polyfill@1.5.1: {} - resolve-alpn@1.2.1: {} - resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -16594,10 +15627,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - responselike@2.0.1: - dependencies: - lowercase-keys: 2.0.0 - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -16607,16 +15636,6 @@ snapshots: rfdc@1.4.1: {} - roarr@2.15.4: - dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.4 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.3 - optional: true - robust-predicates@3.0.2: {} rollup-plugin-visualizer@5.12.0(rollup@4.46.2): @@ -16683,8 +15702,6 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.2.1: {} - safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -16818,15 +15835,9 @@ snapshots: scule@1.3.0: {} - semver-compare@1.0.0: + semver@5.7.2: optional: true - semver-diff@2.1.0: - dependencies: - semver: 5.7.2 - - semver@5.7.2: {} - semver@6.3.1: {} semver@7.5.4: @@ -16837,11 +15848,6 @@ snapshots: semver@7.7.3: {} - serialize-error@7.0.1: - dependencies: - type-fest: 0.13.1 - optional: true - seroval-plugins@1.3.2(seroval@1.3.2): dependencies: seroval: 1.3.2 @@ -16872,16 +15878,10 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - shebang-command@1.2.0: - dependencies: - shebang-regex: 1.0.0 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 - shebang-regex@1.0.0: {} - shebang-regex@3.0.0: {} shell-quote@1.8.1: {} @@ -16925,8 +15925,6 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} slash@3.0.0: {} @@ -17021,11 +16019,6 @@ snapshots: string-argv@0.3.2: {} - string-width@2.1.1: - dependencies: - is-fullwidth-code-point: 2.0.0 - strip-ansi: 4.0.0 - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -17099,10 +16092,6 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - strip-ansi@4.0.0: - dependencies: - ansi-regex: 3.0.1 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -17113,12 +16102,6 @@ snapshots: strip-bom@3.0.0: {} - strip-eof@1.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} strip-json-comments@5.0.2: {} @@ -17231,16 +16214,6 @@ snapshots: - supports-color optional: true - sumchecker@3.0.1: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -17322,10 +16295,6 @@ snapshots: transitivePeerDependencies: - supports-color - term-size@1.2.0: - dependencies: - execa: 0.7.0 - terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -17339,8 +16308,6 @@ snapshots: through@2.3.8: {} - timed-out@4.0.1: {} - tiny-invariant@1.3.3: {} tiny-warning@1.0.3: {} @@ -17421,9 +16388,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.13.1: - optional: true - type@2.7.2: {} typed-array-buffer@1.0.2: @@ -17598,10 +16562,6 @@ snapshots: unplugin: 2.3.11 unplugin-utils: 0.3.1 - unique-string@1.0.0: - dependencies: - crypto-random-string: 1.0.0 - unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.2 @@ -17636,8 +16596,6 @@ snapshots: universal-user-agent@7.0.2: {} - universalify@0.1.2: {} - universalify@2.0.1: {} unplugin-auto-import@20.3.0: @@ -17704,8 +16662,6 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.10.1 '@unrs/resolver-binding-win32-x64-msvc': 1.10.1 - unzip-response@2.0.1: {} - update-browserslist-db@1.1.3(browserslist@4.25.1): dependencies: browserslist: 4.25.1 @@ -17718,27 +16674,10 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-notifier@2.5.0: - dependencies: - boxen: 1.3.0 - chalk: 2.4.2 - configstore: 3.1.5 - import-lazy: 2.1.0 - is-ci: 1.2.1 - is-installed-globally: 0.1.0 - is-npm: 1.0.0 - latest-version: 3.1.0 - semver-diff: 2.1.0 - xdg-basedir: 3.0.0 - uri-js@4.4.1: dependencies: punycode: 2.3.1 - url-parse-lax@1.0.0: - dependencies: - prepend-http: 1.0.4 - use-resize-observer@9.1.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@juggle/resize-observer': 3.4.0 @@ -17984,10 +16923,6 @@ snapshots: dependencies: isexe: 3.1.1 - widest-line@2.0.1: - dependencies: - string-width: 2.1.1 - word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -18010,30 +16945,15 @@ snapshots: imurmurhash: 0.1.4 slide: 1.1.6 - write-file-atomic@2.4.3: - dependencies: - graceful-fs: 4.2.11 - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - write-file-atomic@5.0.1: dependencies: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@7.5.10(bufferutil@4.0.8)(utf-8-validate@5.0.10): - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - - xdg-basedir@3.0.0: {} - y18n@5.0.8: {} yaeti@0.0.6: {} - yallist@2.1.2: {} - yallist@3.1.1: {} yallist@4.0.0: {} @@ -18069,11 +16989,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 - yauzl@2.10.0: - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - yocto-queue@0.1.0: {} yocto-queue@1.1.1: {} diff --git a/clash-nyanpasu/scripts/check.ts b/clash-nyanpasu/scripts/check.ts index 3678746e72..32307c0816 100644 --- a/clash-nyanpasu/scripts/check.ts +++ b/clash-nyanpasu/scripts/check.ts @@ -100,8 +100,7 @@ Promise.all(jobs).then(() => { const commands = [ 'pnpm dev - development with react dev tools', - 'pnpm dev:diff - deadlock development with react dev tools (recommend)', - 'pnpm tauri:diff - deadlock development', + 'pnpm dev:diff - deadlock development (recommend)', ] consola.log(' next command:\n') diff --git a/lede/feeds.conf.default b/lede/feeds.conf.default index 3cb8df49e5..90755bdc19 100644 --- a/lede/feeds.conf.default +++ b/lede/feeds.conf.default @@ -1,6 +1,6 @@ src-git packages https://github.com/coolsnowwolf/packages #src-git luci https://github.com/coolsnowwolf/luci.git -src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-25.12 +src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-23.05 #src-git luci https://github.com/coolsnowwolf/luci.git;openwrt-24.10 src-git routing https://github.com/coolsnowwolf/routing src-git telephony https://github.com/coolsnowwolf/telephony.git diff --git a/lede/include/version.mk b/lede/include/version.mk index 8f3b93aa50..abd7bce7f3 100644 --- a/lede/include/version.mk +++ b/lede/include/version.mk @@ -23,13 +23,13 @@ PKG_CONFIG_DEPENDS += \ sanitize = $(call tolower,$(subst _,-,$(subst $(space),-,$(1)))) VERSION_NUMBER:=$(call qstrip,$(CONFIG_VERSION_NUMBER)) -VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),25.12-SNAPSHOT) +VERSION_NUMBER:=$(if $(VERSION_NUMBER),$(VERSION_NUMBER),24.10.3) VERSION_CODE:=$(call qstrip,$(CONFIG_VERSION_CODE)) VERSION_CODE:=$(if $(VERSION_CODE),$(VERSION_CODE),$(REVISION)) VERSION_REPO:=$(call qstrip,$(CONFIG_VERSION_REPO)) -VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/25.12-SNAPSHOT) +VERSION_REPO:=$(if $(VERSION_REPO),$(VERSION_REPO),https://downloads.openwrt.org/releases/24.10.3) VERSION_DIST:=$(call qstrip,$(CONFIG_VERSION_DIST)) VERSION_DIST:=$(if $(VERSION_DIST),$(VERSION_DIST),OpenWrt) diff --git a/lede/package/base-files/image-config.in b/lede/package/base-files/image-config.in index 2cc8a8b2ac..73c62b349a 100644 --- a/lede/package/base-files/image-config.in +++ b/lede/package/base-files/image-config.in @@ -190,7 +190,7 @@ if VERSIONOPT config VERSION_REPO string prompt "Release repository" - default "https://downloads.openwrt.org/releases/25.12-SNAPSHOT" + default "https://downloads.openwrt.org/releases/24.10.3" help This is the repository address embedded in the image, it defaults to the trunk snapshot repo; the url may contain the following placeholders: diff --git a/mihomo/adapter/outbound/sudoku.go b/mihomo/adapter/outbound/sudoku.go index f9313ca39f..bd393ec616 100644 --- a/mihomo/adapter/outbound/sudoku.go +++ b/mihomo/adapter/outbound/sudoku.go @@ -20,17 +20,19 @@ type Sudoku struct { type SudokuOption struct { BasicOption - Name string `proxy:"name"` - Server string `proxy:"server"` - Port int `proxy:"port"` - Key string `proxy:"key"` - AEADMethod string `proxy:"aead-method,omitempty"` - PaddingMin *int `proxy:"padding-min,omitempty"` - PaddingMax *int `proxy:"padding-max,omitempty"` - TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" - EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"` - HTTPMask bool `proxy:"http-mask,omitempty"` - CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + Name string `proxy:"name"` + Server string `proxy:"server"` + Port int `proxy:"port"` + Key string `proxy:"key"` + AEADMethod string `proxy:"aead-method,omitempty"` + PaddingMin *int `proxy:"padding-min,omitempty"` + PaddingMax *int `proxy:"padding-max,omitempty"` + TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + EnablePureDownlink *bool `proxy:"enable-pure-downlink,omitempty"` + HTTPMask bool `proxy:"http-mask,omitempty"` + HTTPMaskStrategy string `proxy:"http-mask-strategy,omitempty"` // "random" (default), "post", "websocket" + CustomTable string `proxy:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + CustomTables []string `proxy:"custom-tables,omitempty"` // optional table rotation patterns, overrides custom-table when non-empty } // DialContext implements C.ProxyAdapter @@ -54,7 +56,9 @@ func (s *Sudoku) DialContext(ctx context.Context, metadata *C.Metadata) (_ C.Con defer done(&err) } - c, err = sudoku.ClientHandshake(c, cfg) + c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{ + HTTPMaskStrategy: s.option.HTTPMaskStrategy, + }) if err != nil { return nil, err } @@ -97,7 +101,9 @@ func (s *Sudoku) ListenPacketContext(ctx context.Context, metadata *C.Metadata) defer done(&err) } - c, err = sudoku.ClientHandshake(c, cfg) + c, err = sudoku.ClientHandshakeWithOptions(c, cfg, sudoku.ClientHandshakeOptions{ + HTTPMaskStrategy: s.option.HTTPMaskStrategy, + }) if err != nil { return nil, err } @@ -185,11 +191,15 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, DisableHTTPMask: !option.HTTPMask, } - table, err := sudoku.NewTableWithCustom(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable) + tables, err := sudoku.NewTablesWithCustomPatterns(sudoku.ClientAEADSeed(option.Key), tableType, option.CustomTable, option.CustomTables) if err != nil { - return nil, fmt.Errorf("build table failed: %w", err) + return nil, fmt.Errorf("build table(s) failed: %w", err) + } + if len(tables) == 1 { + baseConf.Table = tables[0] + } else { + baseConf.Tables = tables } - baseConf.Table = table if option.AEADMethod != "" { baseConf.AEADMethod = option.AEADMethod } diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index 26b1af2c90..04d15bd202 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -1049,7 +1049,9 @@ proxies: # socks5 padding-max: 7 # 最大填充字节数 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy` + # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table http-mask: true # 是否启用http掩码 + # http-mask-strategy: random # 可选:random(默认)、post、websocket;仅在 http-mask=true 时生效 enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与服务端端保持相同(如果此处为false,则要求aead不可为none) # anytls @@ -1591,6 +1593,7 @@ listeners: padding-max: 15 # 填充最大长度,均不建议过大 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 # custom-table: xpxvvpvv # 可选,自定义字节布局,必须包含2个x、2个p、4个v,可随意组合。启用此处则需配置`table-type`为`prefer_entropy` + # custom-tables: ["xpxvvpvv", "vxpvxvvp"] # 可选,自定义字节布局列表(x/v/p),用于 xvp 模式轮换;非空时覆盖 custom-table handshake-timeout: 5 # optional enable-pure-downlink: false # 是否启用混淆下行,false的情况下能在保证数据安全的前提下极大提升下行速度,与客户端保持相同(如果此处为false,则要求aead不可为none) diff --git a/mihomo/go.mod b/mihomo/go.mod index 8fb7e51f1f..c0160c2630 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -43,7 +43,7 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 - github.com/saba-futai/sudoku v0.0.2-c + github.com/saba-futai/sudoku v0.0.2-d github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 diff --git a/mihomo/go.sum b/mihomo/go.sum index 08d1d002a8..c6fba8e505 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/saba-futai/sudoku v0.0.2-c h1:0CaoCKx4Br8UL97fnIxn8Y7rnQpflBza7kfaIrdg2rI= -github.com/saba-futai/sudoku v0.0.2-c/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= +github.com/saba-futai/sudoku v0.0.2-d h1:HW/gIyNUFcDchpMN+ZhluM86U/HGkWkkRV+9Km6WZM8= +github.com/saba-futai/sudoku v0.0.2-d/go.mod h1:Rvggsoprp7HQM7bMIZUd1M27bPj8THRsZdY1dGbIAvo= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/mihomo/listener/config/sudoku.go b/mihomo/listener/config/sudoku.go index b581f3f5ce..848db875d7 100644 --- a/mihomo/listener/config/sudoku.go +++ b/mihomo/listener/config/sudoku.go @@ -9,16 +9,17 @@ import ( // SudokuServer describes a Sudoku inbound server configuration. // It is internal to the listener layer and mainly used for logging and wiring. type SudokuServer struct { - Enable bool `json:"enable"` - Listen string `json:"listen"` - Key string `json:"key"` - AEADMethod string `json:"aead-method,omitempty"` - PaddingMin *int `json:"padding-min,omitempty"` - PaddingMax *int `json:"padding-max,omitempty"` - TableType string `json:"table-type,omitempty"` - HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` - EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"` - CustomTable string `json:"custom-table,omitempty"` + Enable bool `json:"enable"` + Listen string `json:"listen"` + Key string `json:"key"` + AEADMethod string `json:"aead-method,omitempty"` + PaddingMin *int `json:"padding-min,omitempty"` + PaddingMax *int `json:"padding-max,omitempty"` + TableType string `json:"table-type,omitempty"` + HandshakeTimeoutSecond *int `json:"handshake-timeout,omitempty"` + EnablePureDownlink *bool `json:"enable-pure-downlink,omitempty"` + CustomTable string `json:"custom-table,omitempty"` + CustomTables []string `json:"custom-tables,omitempty"` // mihomo private extension (not the part of standard Sudoku protocol) MuxOption sing.MuxOption `json:"mux-option,omitempty"` diff --git a/mihomo/listener/inbound/sudoku.go b/mihomo/listener/inbound/sudoku.go index 6c4092473c..433976026d 100644 --- a/mihomo/listener/inbound/sudoku.go +++ b/mihomo/listener/inbound/sudoku.go @@ -13,14 +13,15 @@ import ( type SudokuOption struct { BaseOption - Key string `inbound:"key"` - AEADMethod string `inbound:"aead-method,omitempty"` - PaddingMin *int `inbound:"padding-min,omitempty"` - PaddingMax *int `inbound:"padding-max,omitempty"` - TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" - HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` - EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"` - CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + Key string `inbound:"key"` + AEADMethod string `inbound:"aead-method,omitempty"` + PaddingMin *int `inbound:"padding-min,omitempty"` + PaddingMax *int `inbound:"padding-max,omitempty"` + TableType string `inbound:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HandshakeTimeoutSecond *int `inbound:"handshake-timeout,omitempty"` + EnablePureDownlink *bool `inbound:"enable-pure-downlink,omitempty"` + CustomTable string `inbound:"custom-table,omitempty"` // optional custom byte layout, e.g. xpxvvpvv + CustomTables []string `inbound:"custom-tables,omitempty"` // mihomo private extension (not the part of standard Sudoku protocol) MuxOption MuxOption `inbound:"mux-option,omitempty"` @@ -57,6 +58,7 @@ func NewSudoku(options *SudokuOption) (*Sudoku, error) { HandshakeTimeoutSecond: options.HandshakeTimeoutSecond, EnablePureDownlink: options.EnablePureDownlink, CustomTable: options.CustomTable, + CustomTables: options.CustomTables, } serverConf.MuxOption = options.MuxOption.Build() diff --git a/mihomo/listener/sudoku/server.go b/mihomo/listener/sudoku/server.go index 8351e36597..e90e231c1d 100644 --- a/mihomo/listener/sudoku/server.go +++ b/mihomo/listener/sudoku/server.go @@ -166,7 +166,7 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) enablePureDownlink = *config.EnablePureDownlink } - table, err := sudoku.NewTableWithCustom(config.Key, tableType, config.CustomTable) + tables, err := sudoku.NewTablesWithCustomPatterns(config.Key, tableType, config.CustomTable, config.CustomTables) if err != nil { _ = l.Close() return nil, err @@ -180,12 +180,16 @@ func New(config LC.SudokuServer, tunnel C.Tunnel, additions ...inbound.Addition) protoConf := sudoku.ProtocolConfig{ Key: config.Key, AEADMethod: defaultConf.AEADMethod, - Table: table, PaddingMin: paddingMin, PaddingMax: paddingMax, EnablePureDownlink: enablePureDownlink, HandshakeTimeoutSeconds: handshakeTimeout, } + if len(tables) == 1 { + protoConf.Table = tables[0] + } else { + protoConf.Tables = tables + } if config.AEADMethod != "" { protoConf.AEADMethod = config.AEADMethod } diff --git a/mihomo/transport/sudoku/features_test.go b/mihomo/transport/sudoku/features_test.go new file mode 100644 index 0000000000..8eb3aedd25 --- /dev/null +++ b/mihomo/transport/sudoku/features_test.go @@ -0,0 +1,192 @@ +package sudoku + +import ( + "bytes" + "io" + "net" + "testing" + "time" + + sudokuobfs "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +type discardConn struct{} + +func (discardConn) Read([]byte) (int, error) { return 0, io.EOF } +func (discardConn) Write(p []byte) (int, error) { return len(p), nil } +func (discardConn) Close() error { return nil } +func (discardConn) LocalAddr() net.Addr { return nil } +func (discardConn) RemoteAddr() net.Addr { return nil } +func (discardConn) SetDeadline(time.Time) error { return nil } +func (discardConn) SetReadDeadline(time.Time) error { return nil } +func (discardConn) SetWriteDeadline(time.Time) error { return nil } + +func TestSudokuObfsWriter_ReducesWriteAllocs(t *testing.T) { + table := sudokuobfs.NewTable("alloc-seed", "prefer_ascii") + w := newSudokuObfsWriter(discardConn{}, table, 0, 0) + + payload := bytes.Repeat([]byte{0x42}, 2048) + if _, err := w.Write(payload); err != nil { + t.Fatalf("warmup write: %v", err) + } + + allocs := testing.AllocsPerRun(100, func() { + if _, err := w.Write(payload); err != nil { + t.Fatalf("write: %v", err) + } + }) + if allocs != 0 { + t.Fatalf("expected 0 allocs/run, got %.2f", allocs) + } +} + +func TestHTTPMaskStrategy_WebSocketAndPost(t *testing.T) { + key := "mask-test-key" + target := "1.1.1.1:80" + table := sudokuobfs.NewTable("mask-seed", "prefer_ascii") + + base := DefaultConfig() + base.Key = key + base.AEADMethod = "chacha20-poly1305" + base.Table = table + base.PaddingMin = 0 + base.PaddingMax = 0 + base.EnablePureDownlink = true + base.HandshakeTimeoutSeconds = 5 + base.DisableHTTPMask = false + base.ServerAddress = "example.com:443" + + cases := []string{"post", "websocket"} + for _, strategy := range cases { + t.Run(strategy, func(t *testing.T) { + serverConn, clientConn := net.Pipe() + defer serverConn.Close() + defer clientConn.Close() + + errCh := make(chan error, 1) + go func() { + defer close(errCh) + session, err := ServerHandshake(serverConn, base) + if err != nil { + errCh <- err + return + } + defer session.Conn.Close() + if session.Type != SessionTypeTCP { + errCh <- io.ErrUnexpectedEOF + return + } + if session.Target != target { + errCh <- io.ErrClosedPipe + return + } + _, _ = session.Conn.Write([]byte("ok")) + }() + + cConn, err := ClientHandshakeWithOptions(clientConn, base, ClientHandshakeOptions{HTTPMaskStrategy: strategy}) + if err != nil { + t.Fatalf("client handshake: %v", err) + } + defer cConn.Close() + + addrBuf, err := EncodeAddress(target) + if err != nil { + t.Fatalf("encode addr: %v", err) + } + if _, err := cConn.Write(addrBuf); err != nil { + t.Fatalf("write addr: %v", err) + } + + buf := make([]byte, 2) + if _, err := io.ReadFull(cConn, buf); err != nil { + t.Fatalf("read: %v", err) + } + if string(buf) != "ok" { + t.Fatalf("unexpected payload: %q", buf) + } + + if err := <-errCh; err != nil { + t.Fatalf("server: %v", err) + } + }) + } +} + +func TestCustomTablesRotation_ProbedByServer(t *testing.T) { + key := "rotate-test-key" + target := "8.8.8.8:53" + + t1, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "xpxvvpvv") + if err != nil { + t.Fatalf("t1: %v", err) + } + t2, err := sudokuobfs.NewTableWithCustom("rotate-seed", "prefer_entropy", "vxpvxvvp") + if err != nil { + t.Fatalf("t2: %v", err) + } + + serverCfg := DefaultConfig() + serverCfg.Key = key + serverCfg.AEADMethod = "chacha20-poly1305" + serverCfg.Tables = []*sudokuobfs.Table{t1, t2} + serverCfg.PaddingMin = 0 + serverCfg.PaddingMax = 0 + serverCfg.EnablePureDownlink = true + serverCfg.HandshakeTimeoutSeconds = 5 + serverCfg.DisableHTTPMask = true + + clientCfg := DefaultConfig() + *clientCfg = *serverCfg + clientCfg.ServerAddress = "example.com:443" + + for i := 0; i < 10; i++ { + serverConn, clientConn := net.Pipe() + + errCh := make(chan error, 1) + go func() { + defer close(errCh) + defer serverConn.Close() + session, err := ServerHandshake(serverConn, serverCfg) + if err != nil { + errCh <- err + return + } + defer session.Conn.Close() + if session.Type != SessionTypeTCP { + errCh <- io.ErrUnexpectedEOF + return + } + if session.Target != target { + errCh <- io.ErrClosedPipe + return + } + _, _ = session.Conn.Write([]byte{0xaa, 0xbb, 0xcc}) + }() + + cConn, err := ClientHandshake(clientConn, clientCfg) + if err != nil { + t.Fatalf("client handshake: %v", err) + } + + addrBuf, err := EncodeAddress(target) + if err != nil { + t.Fatalf("encode addr: %v", err) + } + if _, err := cConn.Write(addrBuf); err != nil { + t.Fatalf("write addr: %v", err) + } + + buf := make([]byte, 3) + if _, err := io.ReadFull(cConn, buf); err != nil { + t.Fatalf("read: %v", err) + } + if !bytes.Equal(buf, []byte{0xaa, 0xbb, 0xcc}) { + t.Fatalf("payload mismatch: %x", buf) + } + _ = cConn.Close() + + if err := <-errCh; err != nil { + t.Fatalf("server: %v", err) + } + } +} diff --git a/mihomo/transport/sudoku/handshake.go b/mihomo/transport/sudoku/handshake.go index d34fceb40e..989d281323 100644 --- a/mihomo/transport/sudoku/handshake.go +++ b/mihomo/transport/sudoku/handshake.go @@ -2,11 +2,13 @@ package sudoku import ( "bufio" + "crypto/rand" "crypto/sha256" "encoding/binary" "fmt" "io" "net" + "strings" "time" "github.com/saba-futai/sudoku/apis" @@ -110,25 +112,35 @@ func downlinkMode(cfg *apis.ProtocolConfig) byte { return downlinkModePacked } -func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig) net.Conn { - base := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false) +func buildClientObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table) net.Conn { + baseReader := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, false) + baseWriter := newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax) if cfg.EnablePureDownlink { - return base + return &directionalConn{ + Conn: raw, + reader: baseReader, + writer: baseWriter, + } } - packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax) + packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax) return &directionalConn{ Conn: raw, reader: packed, - writer: base, + writer: baseWriter, } } -func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, record bool) (*sudoku.Conn, net.Conn) { - uplink := sudoku.NewConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, record) +func buildServerObfsConn(raw net.Conn, cfg *apis.ProtocolConfig, table *sudoku.Table, record bool) (*sudoku.Conn, net.Conn) { + uplink := sudoku.NewConn(raw, table, cfg.PaddingMin, cfg.PaddingMax, record) if cfg.EnablePureDownlink { - return uplink, uplink + downlink := &directionalConn{ + Conn: raw, + reader: uplink, + writer: newSudokuObfsWriter(raw, table, cfg.PaddingMin, cfg.PaddingMax), + } + return uplink, downlink } - packed := sudoku.NewPackedConn(raw, cfg.Table, cfg.PaddingMin, cfg.PaddingMax) + packed := sudoku.NewPackedConn(raw, table, cfg.PaddingMin, cfg.PaddingMax) return uplink, &directionalConn{ Conn: raw, reader: uplink, @@ -170,8 +182,19 @@ func ClientAEADSeed(key string) string { return key } +type ClientHandshakeOptions struct { + // HTTPMaskStrategy controls how the client generates the HTTP mask header when DisableHTTPMask=false. + // Supported: ""/"random" (default), "post", "websocket". + HTTPMaskStrategy string +} + // ClientHandshake performs the client-side Sudoku handshake (without sending target address). func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, error) { + return ClientHandshakeWithOptions(rawConn, cfg, ClientHandshakeOptions{}) +} + +// ClientHandshakeWithOptions performs the client-side Sudoku handshake (without sending target address). +func ClientHandshakeWithOptions(rawConn net.Conn, cfg *apis.ProtocolConfig, opt ClientHandshakeOptions) (net.Conn, error) { if cfg == nil { return nil, fmt.Errorf("config is required") } @@ -180,18 +203,26 @@ func ClientHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (net.Conn, erro } if !cfg.DisableHTTPMask { - if err := httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { + if err := WriteHTTPMaskHeader(rawConn, cfg.ServerAddress, opt.HTTPMaskStrategy); err != nil { return nil, fmt.Errorf("write http mask failed: %w", err) } } - obfsConn := buildClientObfsConn(rawConn, cfg) + table, tableID, err := pickClientTable(cfg) + if err != nil { + return nil, err + } + + obfsConn := buildClientObfsConn(rawConn, cfg, table) cConn, err := crypto.NewAEADConn(obfsConn, ClientAEADSeed(cfg.Key), cfg.AEADMethod) if err != nil { return nil, fmt.Errorf("setup crypto failed: %w", err) } handshake := buildHandshakePayload(cfg.Key) + if len(tableCandidates(cfg)) > 1 { + handshake[15] = tableID + } if _, err := cConn.Write(handshake[:]); err != nil { cConn.Close() return nil, fmt.Errorf("send handshake failed: %w", err) @@ -218,21 +249,25 @@ func ServerHandshake(rawConn net.Conn, cfg *apis.ProtocolConfig) (*ServerSession handshakeTimeout = 5 * time.Second } + rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout)) + bufReader := bufio.NewReader(rawConn) if !cfg.DisableHTTPMask { - if peek, _ := bufReader.Peek(4); len(peek) == 4 && string(peek) == "POST" { + if peek, err := bufReader.Peek(4); err == nil && httpmask.LooksLikeHTTPRequestStart(peek) { if _, err := httpmask.ConsumeHeader(bufReader); err != nil { return nil, fmt.Errorf("invalid http header: %w", err) } } } - rawConn.SetReadDeadline(time.Now().Add(handshakeTimeout)) - bConn := &bufferedConn{ - Conn: rawConn, - r: bufReader, + selectedTable, preRead, err := selectTableByProbe(bufReader, cfg, tableCandidates(cfg)) + if err != nil { + return nil, err } - sConn, obfsConn := buildServerObfsConn(bConn, cfg, true) + + baseConn := &preBufferedConn{Conn: rawConn, buf: preRead} + bConn := &bufferedConn{Conn: baseConn, r: bufio.NewReader(baseConn)} + sConn, obfsConn := buildServerObfsConn(bConn, cfg, selectedTable, true) cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod) if err != nil { return nil, fmt.Errorf("crypto setup failed: %w", err) @@ -313,3 +348,24 @@ func GenKeyPair() (privateKey, publicKey string, err error) { publicKey = crypto.EncodePoint(pair.Public) // Master Public Key for server return } + +func normalizeHTTPMaskStrategy(strategy string) string { + s := strings.TrimSpace(strings.ToLower(strategy)) + switch s { + case "", "random": + return "random" + case "ws": + return "websocket" + default: + return s + } +} + +// randomByte returns a cryptographically random byte (with a math/rand fallback). +func randomByte() byte { + var b [1]byte + if _, err := rand.Read(b[:]); err == nil { + return b[0] + } + return byte(time.Now().UnixNano()) +} diff --git a/mihomo/transport/sudoku/httpmask_strategy.go b/mihomo/transport/sudoku/httpmask_strategy.go new file mode 100644 index 0000000000..dc90991d13 --- /dev/null +++ b/mihomo/transport/sudoku/httpmask_strategy.go @@ -0,0 +1,179 @@ +package sudoku + +import ( + "encoding/base64" + "fmt" + "io" + "math/rand" + "net" + "strconv" + "sync" + "time" + + "github.com/saba-futai/sudoku/pkg/obfs/httpmask" +) + +var ( + httpMaskUserAgents = []string{ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15", + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + } + httpMaskAccepts = []string{ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "application/json, text/plain, */*", + "application/octet-stream", + "*/*", + } + httpMaskAcceptLanguages = []string{ + "en-US,en;q=0.9", + "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", + } + httpMaskAcceptEncodings = []string{ + "gzip, deflate, br", + "gzip, deflate", + } + httpMaskPaths = []string{ + "/api/v1/upload", + "/data/sync", + "/v1/telemetry", + "/session", + "/ws", + } + httpMaskContentTypes = []string{ + "application/octet-stream", + "application/json", + } +) + +var ( + httpMaskRngPool = sync.Pool{ + New: func() any { return rand.New(rand.NewSource(time.Now().UnixNano())) }, + } + httpMaskBufPool = sync.Pool{ + New: func() any { + b := make([]byte, 0, 1024) + return &b + }, + } +) + +func trimPortForHost(host string) string { + if host == "" { + return host + } + h, _, err := net.SplitHostPort(host) + if err == nil && h != "" { + return h + } + return host +} + +func appendCommonHeaders(buf []byte, host string, r *rand.Rand) []byte { + ua := httpMaskUserAgents[r.Intn(len(httpMaskUserAgents))] + accept := httpMaskAccepts[r.Intn(len(httpMaskAccepts))] + lang := httpMaskAcceptLanguages[r.Intn(len(httpMaskAcceptLanguages))] + enc := httpMaskAcceptEncodings[r.Intn(len(httpMaskAcceptEncodings))] + + buf = append(buf, "Host: "...) + buf = append(buf, host...) + buf = append(buf, "\r\nUser-Agent: "...) + buf = append(buf, ua...) + buf = append(buf, "\r\nAccept: "...) + buf = append(buf, accept...) + buf = append(buf, "\r\nAccept-Language: "...) + buf = append(buf, lang...) + buf = append(buf, "\r\nAccept-Encoding: "...) + buf = append(buf, enc...) + buf = append(buf, "\r\nConnection: keep-alive\r\n"...) + buf = append(buf, "Cache-Control: no-cache\r\nPragma: no-cache\r\n"...) + return buf +} + +// WriteHTTPMaskHeader writes an HTTP/1.x request header as a mask, according to strategy. +// Supported strategies: ""/"random", "post", "websocket". +func WriteHTTPMaskHeader(w io.Writer, host string, strategy string) error { + switch normalizeHTTPMaskStrategy(strategy) { + case "random": + return httpmask.WriteRandomRequestHeader(w, host) + case "post": + return writeHTTPMaskPOST(w, host) + case "websocket": + return writeHTTPMaskWebSocket(w, host) + default: + return fmt.Errorf("unsupported http-mask-strategy: %s", strategy) + } +} + +func writeHTTPMaskPOST(w io.Writer, host string) error { + r := httpMaskRngPool.Get().(*rand.Rand) + defer httpMaskRngPool.Put(r) + + path := httpMaskPaths[r.Intn(len(httpMaskPaths))] + ctype := httpMaskContentTypes[r.Intn(len(httpMaskContentTypes))] + + bufPtr := httpMaskBufPool.Get().(*[]byte) + buf := *bufPtr + buf = buf[:0] + defer func() { + if cap(buf) <= 4096 { + *bufPtr = buf + httpMaskBufPool.Put(bufPtr) + } + }() + + const minCL = int64(4 * 1024) + const maxCL = int64(10 * 1024 * 1024) + contentLength := minCL + r.Int63n(maxCL-minCL+1) + + buf = append(buf, "POST "...) + buf = append(buf, path...) + buf = append(buf, " HTTP/1.1\r\n"...) + buf = appendCommonHeaders(buf, host, r) + buf = append(buf, "Content-Type: "...) + buf = append(buf, ctype...) + buf = append(buf, "\r\nContent-Length: "...) + buf = strconv.AppendInt(buf, contentLength, 10) + buf = append(buf, "\r\n\r\n"...) + + _, err := w.Write(buf) + return err +} + +func writeHTTPMaskWebSocket(w io.Writer, host string) error { + r := httpMaskRngPool.Get().(*rand.Rand) + defer httpMaskRngPool.Put(r) + + path := httpMaskPaths[r.Intn(len(httpMaskPaths))] + + bufPtr := httpMaskBufPool.Get().(*[]byte) + buf := *bufPtr + buf = buf[:0] + defer func() { + if cap(buf) <= 4096 { + *bufPtr = buf + httpMaskBufPool.Put(bufPtr) + } + }() + + hostNoPort := trimPortForHost(host) + var keyBytes [16]byte + for i := 0; i < len(keyBytes); i++ { + keyBytes[i] = byte(r.Intn(256)) + } + var wsKey [24]byte + base64.StdEncoding.Encode(wsKey[:], keyBytes[:]) + + buf = append(buf, "GET "...) + buf = append(buf, path...) + buf = append(buf, " HTTP/1.1\r\n"...) + buf = appendCommonHeaders(buf, host, r) + buf = append(buf, "Upgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: "...) + buf = append(buf, wsKey[:]...) + buf = append(buf, "\r\nOrigin: https://"...) + buf = append(buf, hostNoPort...) + buf = append(buf, "\r\n\r\n"...) + + _, err := w.Write(buf) + return err +} diff --git a/mihomo/transport/sudoku/obfs_writer.go b/mihomo/transport/sudoku/obfs_writer.go new file mode 100644 index 0000000000..f980359119 --- /dev/null +++ b/mihomo/transport/sudoku/obfs_writer.go @@ -0,0 +1,113 @@ +package sudoku + +import ( + crypto_rand "crypto/rand" + "encoding/binary" + "math/rand" + "net" + + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +// perm4 matches github.com/saba-futai/sudoku/pkg/obfs/sudoku perm4. +var perm4 = [24][4]byte{ + {0, 1, 2, 3}, + {0, 1, 3, 2}, + {0, 2, 1, 3}, + {0, 2, 3, 1}, + {0, 3, 1, 2}, + {0, 3, 2, 1}, + {1, 0, 2, 3}, + {1, 0, 3, 2}, + {1, 2, 0, 3}, + {1, 2, 3, 0}, + {1, 3, 0, 2}, + {1, 3, 2, 0}, + {2, 0, 1, 3}, + {2, 0, 3, 1}, + {2, 1, 0, 3}, + {2, 1, 3, 0}, + {2, 3, 0, 1}, + {2, 3, 1, 0}, + {3, 0, 1, 2}, + {3, 0, 2, 1}, + {3, 1, 0, 2}, + {3, 1, 2, 0}, + {3, 2, 0, 1}, + {3, 2, 1, 0}, +} + +type sudokuObfsWriter struct { + conn net.Conn + table *sudoku.Table + rng *rand.Rand + paddingRate float32 + + outBuf []byte + pads []byte + padLen int +} + +func newSudokuObfsWriter(conn net.Conn, table *sudoku.Table, pMin, pMax int) *sudokuObfsWriter { + var seedBytes [8]byte + if _, err := crypto_rand.Read(seedBytes[:]); err != nil { + binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63())) + } + seed := int64(binary.BigEndian.Uint64(seedBytes[:])) + localRng := rand.New(rand.NewSource(seed)) + + min := float32(pMin) / 100.0 + span := float32(pMax-pMin) / 100.0 + rate := min + localRng.Float32()*span + + w := &sudokuObfsWriter{ + conn: conn, + table: table, + rng: localRng, + paddingRate: rate, + } + w.pads = table.PaddingPool + w.padLen = len(w.pads) + return w +} + +func (w *sudokuObfsWriter) Write(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + // Worst-case: 4 hints + up to 6 paddings per input byte. + needed := len(p)*10 + 1 + if cap(w.outBuf) < needed { + w.outBuf = make([]byte, 0, needed) + } + out := w.outBuf[:0] + + pads := w.pads + padLen := w.padLen + + for _, b := range p { + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + + puzzles := w.table.EncodeTable[b] + puzzle := puzzles[w.rng.Intn(len(puzzles))] + + perm := perm4[w.rng.Intn(len(perm4))] + for _, idx := range perm { + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + out = append(out, puzzle[idx]) + } + } + + if padLen > 0 && w.rng.Float32() < w.paddingRate { + out = append(out, pads[w.rng.Intn(padLen)]) + } + + w.outBuf = out + _, err := w.conn.Write(out) + return len(p), err +} diff --git a/mihomo/transport/sudoku/table_probe.go b/mihomo/transport/sudoku/table_probe.go new file mode 100644 index 0000000000..f12c172226 --- /dev/null +++ b/mihomo/transport/sudoku/table_probe.go @@ -0,0 +1,152 @@ +package sudoku + +import ( + "bufio" + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "time" + + "github.com/saba-futai/sudoku/apis" + "github.com/saba-futai/sudoku/pkg/crypto" + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +func tableCandidates(cfg *apis.ProtocolConfig) []*sudoku.Table { + if cfg == nil { + return nil + } + if len(cfg.Tables) > 0 { + return cfg.Tables + } + if cfg.Table != nil { + return []*sudoku.Table{cfg.Table} + } + return nil +} + +func pickClientTable(cfg *apis.ProtocolConfig) (*sudoku.Table, byte, error) { + candidates := tableCandidates(cfg) + if len(candidates) == 0 { + return nil, 0, fmt.Errorf("no table configured") + } + if len(candidates) == 1 { + return candidates[0], 0, nil + } + idx := int(randomByte()) % len(candidates) + return candidates[idx], byte(idx), nil +} + +type readOnlyConn struct { + *bytes.Reader +} + +func (c *readOnlyConn) Write([]byte) (int, error) { return 0, io.ErrClosedPipe } +func (c *readOnlyConn) Close() error { return nil } +func (c *readOnlyConn) LocalAddr() net.Addr { return nil } +func (c *readOnlyConn) RemoteAddr() net.Addr { return nil } +func (c *readOnlyConn) SetDeadline(time.Time) error { return nil } +func (c *readOnlyConn) SetReadDeadline(time.Time) error { return nil } +func (c *readOnlyConn) SetWriteDeadline(time.Time) error { return nil } + +func drainBuffered(r *bufio.Reader) ([]byte, error) { + n := r.Buffered() + if n <= 0 { + return nil, nil + } + out := make([]byte, n) + _, err := io.ReadFull(r, out) + return out, err +} + +func probeHandshakeBytes(probe []byte, cfg *apis.ProtocolConfig, table *sudoku.Table) error { + rc := &readOnlyConn{Reader: bytes.NewReader(probe)} + _, obfsConn := buildServerObfsConn(rc, cfg, table, false) + cConn, err := crypto.NewAEADConn(obfsConn, cfg.Key, cfg.AEADMethod) + if err != nil { + return err + } + + var handshakeBuf [16]byte + if _, err := io.ReadFull(cConn, handshakeBuf[:]); err != nil { + return err + } + ts := int64(binary.BigEndian.Uint64(handshakeBuf[:8])) + if absInt64(time.Now().Unix()-ts) > 60 { + return fmt.Errorf("timestamp skew/replay detected") + } + + modeBuf := []byte{0} + if _, err := io.ReadFull(cConn, modeBuf); err != nil { + return err + } + if modeBuf[0] != downlinkMode(cfg) { + return fmt.Errorf("downlink mode mismatch") + } + + return nil +} + +func selectTableByProbe(r *bufio.Reader, cfg *apis.ProtocolConfig, tables []*sudoku.Table) (*sudoku.Table, []byte, error) { + const ( + maxProbeBytes = 64 * 1024 + readChunk = 4 * 1024 + ) + if len(tables) == 0 { + return nil, nil, fmt.Errorf("no table candidates") + } + if len(tables) > 255 { + return nil, nil, fmt.Errorf("too many table candidates: %d", len(tables)) + } + + probe, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + + tmp := make([]byte, readChunk) + for { + if len(tables) == 1 { + tail, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + probe = append(probe, tail...) + return tables[0], probe, nil + } + + needMore := false + for _, table := range tables { + err := probeHandshakeBytes(probe, cfg, table) + if err == nil { + tail, err := drainBuffered(r) + if err != nil { + return nil, nil, fmt.Errorf("drain buffered bytes failed: %w", err) + } + probe = append(probe, tail...) + return table, probe, nil + } + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { + needMore = true + } + } + + if !needMore { + return nil, probe, fmt.Errorf("handshake table selection failed") + } + if len(probe) >= maxProbeBytes { + return nil, probe, fmt.Errorf("handshake probe exceeded %d bytes", maxProbeBytes) + } + + n, err := r.Read(tmp) + if n > 0 { + probe = append(probe, tmp[:n]...) + } + if err != nil { + return nil, probe, fmt.Errorf("handshake probe read failed: %w", err) + } + } +} diff --git a/mihomo/transport/sudoku/tables.go b/mihomo/transport/sudoku/tables.go new file mode 100644 index 0000000000..429a4ab327 --- /dev/null +++ b/mihomo/transport/sudoku/tables.go @@ -0,0 +1,30 @@ +package sudoku + +import ( + "strings" + + "github.com/saba-futai/sudoku/pkg/obfs/sudoku" +) + +// NewTablesWithCustomPatterns builds one or more obfuscation tables from x/v/p custom patterns. +// When customTables is non-empty it overrides customTable (matching upstream Sudoku behavior). +func NewTablesWithCustomPatterns(key string, tableType string, customTable string, customTables []string) ([]*sudoku.Table, error) { + patterns := customTables + if len(patterns) == 0 && strings.TrimSpace(customTable) != "" { + patterns = []string{customTable} + } + if len(patterns) == 0 { + patterns = []string{""} + } + + tables := make([]*sudoku.Table, 0, len(patterns)) + for _, pattern := range patterns { + pattern = strings.TrimSpace(pattern) + t, err := NewTableWithCustom(key, tableType, pattern) + if err != nil { + return nil, err + } + tables = append(tables, t) + } + return tables, nil +} diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua index ac109cd95f..e822fb6d65 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua @@ -308,6 +308,17 @@ o = s:option(DummyValue, "switch_mode", " ") o.template = appname .. "/global/proxy" o:depends({ _tcp_node_bool = "1" }) +-- Node → DNS Depends Settings +o = s:option(DummyValue, "_node_sel_shunt", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ tcp_node = "__always__" }) + +o = s:option(DummyValue, "_node_sel_other", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ _node_sel_shunt = "1", ['!reverse'] = true }) + ---- DNS o = s:option(ListValue, "dns_shunt", "DNS " .. translate("Shunt")) o.default = "chinadns-ng" @@ -333,6 +344,7 @@ end if has_xray then o:value("xray", "Xray") end +o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["tcp_node"] local id_val = f and f:formvalue(section) or "" @@ -362,7 +374,9 @@ o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, ["tcp+doh"] = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "xray" then @@ -377,7 +391,9 @@ o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, doh = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "sing-box" then @@ -444,6 +460,7 @@ o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) o.datatype = "ipaddr" o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) +o:depends({_node_sel_shunt = "1"}) o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o.default = "none" @@ -485,6 +502,7 @@ for k, v in pairs(nodes_table) do udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") s.fields["xray_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) end if v.type == "sing-box" and has_singbox then tcp:value(v.id, v["remark"]) @@ -493,17 +511,13 @@ for k, v in pairs(nodes_table) do udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") s.fields["singbox_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) - end - if has_xray or has_singbox then - s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) end else tcp:value(v.id, v["remark"]) tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - s.fields["dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) end end diff --git a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 30665ed6dd..de37538e08 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -309,6 +309,18 @@ o = s:taboption("Main", Flag, "tcp_node_socks_bind_local", translate("TCP Node") o.default = "1" o:depends({ tcp_node = "", ["!reverse"] = true }) +-- Node → DNS Depends Settings +o = s:taboption("Main", DummyValue, "_node_sel_shunt", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ tcp_node = "__always__" }) + +o = s:taboption("Main", DummyValue, "_node_sel_other", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ _node_sel_shunt = "1", ['!reverse'] = true }) + +-- [[ DNS Settings ]]-- s:tab("DNS", translate("DNS")) o = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shunt")) @@ -388,8 +400,8 @@ end if has_xray then o:value("xray", "Xray") end -o:depends({ dns_shunt = "chinadns-ng", tcp_node = "" }) -o:depends({ dns_shunt = "dnsmasq", tcp_node = "" }) +o:depends({ dns_shunt = "chinadns-ng", _node_sel_other = "1" }) +o:depends({ dns_shunt = "dnsmasq", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["smartdns_dns_mode"] if f and f:formvalue(section) then @@ -408,7 +420,7 @@ if api.is_finded("smartdns") then if has_xray then o:value("xray", "Xray") end - o:depends({ dns_shunt = "smartdns", tcp_node = "" }) + o:depends({ dns_shunt = "smartdns", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["dns_mode"] if f and f:formvalue(section) then @@ -468,7 +480,9 @@ o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") o:depends("smartdns_dns_mode", "xray") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, ["tcp+doh"] = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "xray" or s.fields["smartdns_dns_mode"]:formvalue(section) == "xray" then @@ -484,7 +498,9 @@ o:value("doh", "DoH") o:depends("dns_mode", "sing-box") o:depends("smartdns_dns_mode", "sing-box") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, doh = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "sing-box" or s.fields["smartdns_dns_mode"]:formvalue(section) == "sing-box" then @@ -548,6 +564,7 @@ o.datatype = "ipaddr" o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) o:depends("dns_shunt", "smartdns") +o:depends("_node_sel_shunt", "1") o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) o.default = "0" @@ -557,6 +574,7 @@ o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"}) o:depends({dns_mode = "xray", dns_shunt = "dnsmasq"}) o:depends({dns_mode = "xray", dns_shunt = "chinadns-ng"}) o:depends({smartdns_dns_mode = "xray", dns_shunt = "smartdns"}) +o:depends("_node_sel_shunt", "1") o.validate = function(self, value, t) if value and value == "1" then local _dns_mode = s.fields["dns_mode"]:formvalue(t) @@ -810,22 +828,15 @@ for k, v in pairs(nodes_table) do udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", tcp_node = v.id }) - s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", tcp_node = v.id }) - s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id }) - s.fields["remote_fakedns"]:depends({ tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) + s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", _node_sel_shunt = "1" }) + s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", _node_sel_shunt = "1" }) end else tcp:value(v.id, v["remark"]) tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - s.fields["dns_mode"]:depends({ dns_shunt = "chinadns-ng", tcp_node = v.id }) - s.fields["dns_mode"]:depends({ dns_shunt = "dnsmasq", tcp_node = v.id }) - if api.is_finded("smartdns") then - s.fields["smartdns_dns_mode"]:depends({ dns_shunt = "smartdns", tcp_node = v.id }) - end end if v.type == "Socks" then if has_singbox or has_xray then diff --git a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua index 7b7be7cee9..63dc72556e 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/openwrt-passwall/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -201,8 +201,8 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, wsSettings = (node.transport == "ws") and { path = node.ws_path or "/", - headers = (node.ws_host or node.ws_user_agent) and { - Host = node.ws_host, + host = node.ws_host, + headers = node.ws_user_agent and { ["User-Agent"] = node.ws_user_agent } or nil, maxEarlyData = tonumber(node.ws_maxEarlyData) or nil, diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm index 685ea00918..1bcf0ab744 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm @@ -139,7 +139,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>; lv_openPanel(cbid,display,panel,listContainer,hiddenSelect,searchInput); } }); - lv_adaptiveStyle(cbid); // copy select styles + lv_registerAdaptive(cbid); })(); //]]> diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm index 922db7279b..21538a2ee0 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm @@ -244,9 +244,11 @@ local appname = api.appname return lv_rgbToHex(r, g, b); } + // copy select styles function lv_adaptiveStyle(cbid) { const display = document.getElementById(cbid + ".display"); const hiddenSelect = document.getElementById(cbid); + const panel = document.getElementById(cbid + ".panel"); if (hiddenSelect && display) { const elOption = hiddenSelect.getElementsByTagName("option")[0] const styleSelect = window.getComputedStyle(hiddenSelect) @@ -466,7 +468,13 @@ local appname = api.appname } panel.style.left = rect.left + "px"; panel.style.top = top + "px"; - panel.style.minWidth = rect.width + "px"; + const panelRect = panel.getBoundingClientRect(); + const displayWidth = rect.width; + const remainingWidth = window.innerWidth - panelRect.left - 12; + const maxWidth = Math.max(displayWidth, Math.floor(remainingWidth)); + panel.style.maxWidth = maxWidth + "px"; + panel.style.minWidth = displayWidth + "px"; + panel.style.width = "auto"; panel.style.visibility = ""; } @@ -601,22 +609,24 @@ local appname = api.appname if(!li || li === listContainer) return; const key = li.getAttribute('data-key') || ""; const text = li.querySelector(".lv-item-label")?.textContent || li.textContent || key; - if (key !== hiddenSelect.value) { - //动态改值 + + const changed = key !== hiddenSelect.value; + if (changed) { + //改值 hiddenSelect.options[0].value = key; + hiddenSelect.options[0].text = key; hiddenSelect.value = key; labelSpan.textContent = text; labelSpan.title = text; - setTimeout(() => { - try { - const evt = new Event('change', { bubbles: true }); - hiddenSelect.dispatchEvent(evt); - } catch(e){} - }, 0); lv_highlightSelectedItem(listContainer, hiddenSelect); lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput); } lv_closePanel(cbid,panel,listContainer,hiddenSelect,searchInput); + if (changed) { + setTimeout(() => { + hiddenSelect.dispatchEvent(new Event('change', { bubbles: true })); + }, 0); + } }); // 搜索功能 @@ -635,27 +645,22 @@ local appname = api.appname } }); }); - - // 设置宽度 - panel.style.maxWidth = lv_getPanelMaxWidth(display); - panel.style.minWidth = display.getBoundingClientRect().width + "px"; - panel.style.width = "auto"; } - function lv_getPanelMaxWidth(display) { - if (!display) return 0; - const rectW = el => el && el.getBoundingClientRect().width; - const fallback = rectW(display) || 0; - const cbiValue = display.closest(".cbi-value"); - if (cbiValue) { - const valueW = rectW(cbiValue); - const titleW = rectW(cbiValue.querySelector(".cbi-value-title")); - if (valueW) { - return Math.floor(titleW ? valueW - titleW : valueW); - } - } - const fieldW = rectW(display.closest(".cbi-value-field")); - return Math.floor(fieldW || fallback); + const lv_adaptiveControls = new Set(); + function lv_registerAdaptive(cbid) { + lv_adaptiveControls.add(cbid); + lv_adaptiveStyle(cbid); } + let lv_adaptiveTicking = false; + window.addEventListener("resize", () => { + if (!lv_adaptiveTicking) { + lv_adaptiveTicking = true; + requestAnimationFrame(() => { + lv_adaptiveControls.forEach(cbid => lv_adaptiveStyle(cbid)); + lv_adaptiveTicking = false; + }); + } + }); //]]> diff --git a/shadowsocks-rust/Cargo.lock b/shadowsocks-rust/Cargo.lock index 28e85c9a3d..f97cf9cbdc 100644 --- a/shadowsocks-rust/Cargo.lock +++ b/shadowsocks-rust/Cargo.lock @@ -821,7 +821,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -928,7 +928,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1470,7 +1470,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -2127,7 +2127,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2452,7 +2452,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.1", + "socket2 0.5.10", "thiserror", "tokio", "tracing", @@ -2489,9 +2489,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -2604,9 +2604,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.25" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64", "bytes", @@ -2758,7 +2758,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -3432,7 +3432,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4060,7 +4060,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] diff --git a/sing-box/.github/CRONET_GO_VERSION b/sing-box/.github/CRONET_GO_VERSION index 7616cee1c2..a7af089083 100644 --- a/sing-box/.github/CRONET_GO_VERSION +++ b/sing-box/.github/CRONET_GO_VERSION @@ -1 +1 @@ -fe7ab107d3a222ca878b9a727d76075938ee7cde +3745171528eaf62dd8819df4c089b1259a32a1f2 diff --git a/sing-box/.github/workflows/build.yml b/sing-box/.github/workflows/build.yml index 928b7ce9d4..d256558932 100644 --- a/sing-box/.github/workflows/build.yml +++ b/sing-box/.github/workflows/build.yml @@ -69,11 +69,11 @@ jobs: strategy: matrix: include: - - { os: linux, arch: amd64, variant: purego, openwrt: "x86_64" } + - { os: linux, arch: amd64, variant: purego, naive: true, openwrt: "x86_64" } - { os: linux, arch: amd64, variant: glibc, naive: true } - { os: linux, arch: amd64, variant: musl, naive: true, debian: amd64, rpm: x86_64, pacman: x86_64, openwrt: "x86_64" } - - { os: linux, arch: arm64, variant: purego, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } + - { os: linux, arch: arm64, variant: purego, naive: true, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } - { os: linux, arch: arm64, variant: glibc, naive: true } - { os: linux, arch: arm64, variant: musl, naive: true, debian: arm64, rpm: aarch64, pacman: aarch64, openwrt: "aarch64_cortex-a53 aarch64_cortex-a72 aarch64_cortex-a76 aarch64_generic" } @@ -190,7 +190,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound" fi @@ -427,7 +427,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.legacy_go124 }}" != "true" ]]; then TAGS="${TAGS},with_naive_outbound" fi @@ -495,7 +495,7 @@ jobs: - name: Build run: | mkdir -p dist - go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` + go build -v -trimpath -o dist/sing-box.exe -tags "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_naive_outbound,with_purego,badlinkname,tfogo_checklinkname0" ` -ldflags "-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0" ` ./cmd/sing-box env: @@ -885,6 +885,16 @@ jobs: with: path: dist merge-multiple: true + - name: Generate SFA version metadata + run: |- + VERSION_CODE=$(grep VERSION_CODE clients/android/version.properties | cut -d= -f2) + cat > dist/SFA-version-metadata.json << EOF + { + "version_code": ${VERSION_CODE}, + "version_name": "${VERSION}" + } + EOF + cat dist/SFA-version-metadata.json - name: Upload builds if: ${{ env.PUBLISHED == 'false' }} run: |- diff --git a/sing-box/.github/workflows/docker.yml b/sing-box/.github/workflows/docker.yml index e4041b5b00..5447457e26 100644 --- a/sing-box/.github/workflows/docker.yml +++ b/sing-box/.github/workflows/docker.yml @@ -93,7 +93,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound,with_musl" fi diff --git a/sing-box/.github/workflows/linux.yml b/sing-box/.github/workflows/linux.yml index eb1f32805c..1f56467c76 100644 --- a/sing-box/.github/workflows/linux.yml +++ b/sing-box/.github/workflows/linux.yml @@ -116,7 +116,7 @@ jobs: - name: Set build tags run: | set -xeuo pipefail - TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0' + TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0' if [[ "${{ matrix.naive }}" == "true" ]]; then TAGS="${TAGS},with_naive_outbound,with_musl" fi diff --git a/sing-box/Dockerfile b/sing-box/Dockerfile index 5162d46131..fb39e8b603 100644 --- a/sing-box/Dockerfile +++ b/sing-box/Dockerfile @@ -13,7 +13,7 @@ RUN set -ex \ && export COMMIT=$(git rev-parse --short HEAD) \ && export VERSION=$(go run ./cmd/internal/read_tag) \ && go build -v -trimpath -tags \ - "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" \ + "with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" \ -o /go/bin/sing-box \ -ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \ ./cmd/sing-box diff --git a/sing-box/Makefile b/sing-box/Makefile index fcf5b332f4..9a364af1a0 100644 --- a/sing-box/Makefile +++ b/sing-box/Makefile @@ -1,6 +1,6 @@ NAME = sing-box COMMIT = $(shell git rev-parse --short HEAD) -TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0 +TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0 GOHOSTOS = $(shell go env GOHOSTOS) GOHOSTARCH = $(shell go env GOHOSTARCH) diff --git a/sing-box/constant/proxy.go b/sing-box/constant/proxy.go index a54a3a75d8..a519362345 100644 --- a/sing-box/constant/proxy.go +++ b/sing-box/constant/proxy.go @@ -29,6 +29,7 @@ const ( TypeResolved = "resolved" TypeSSMAPI = "ssm-api" TypeCCM = "ccm" + TypeOCM = "ocm" ) const ( diff --git a/sing-box/docs/changelog.md b/sing-box/docs/changelog.md index d67cbed7e2..c587f2aadb 100644 --- a/sing-box/docs/changelog.md +++ b/sing-box/docs/changelog.md @@ -2,6 +2,15 @@ icon: material/alert-decagram --- +#### 1.13.0-alpha.30 + +* Add OpenAI Codex Multiplexer service **1** +* Fixes and improvements + +**1**: + +See [OCM](/configuration/service/ocm). + #### 1.13.0-alpha.29 * Add UDP over TCP support for naiveproxy outbound **1** diff --git a/sing-box/docs/configuration/service/index.md b/sing-box/docs/configuration/service/index.md index 2bd1a4a3f8..de3583b2ba 100644 --- a/sing-box/docs/configuration/service/index.md +++ b/sing-box/docs/configuration/service/index.md @@ -25,6 +25,7 @@ icon: material/new-box |------------|------------------------| | `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | +| `ocm` | [OCM](./ocm) | | `resolved` | [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/sing-box/docs/configuration/service/index.zh.md b/sing-box/docs/configuration/service/index.zh.md index b4a73eda92..a0d18cbba7 100644 --- a/sing-box/docs/configuration/service/index.zh.md +++ b/sing-box/docs/configuration/service/index.zh.md @@ -25,6 +25,7 @@ icon: material/new-box |-----------|------------------------| | `ccm` | [CCM](./ccm) | | `derp` | [DERP](./derp) | +| `ocm` | [OCM](./ocm) | | `resolved`| [Resolved](./resolved) | | `ssm-api` | [SSM API](./ssm-api) | diff --git a/sing-box/docs/configuration/service/ocm.md b/sing-box/docs/configuration/service/ocm.md new file mode 100644 index 0000000000..59dba7daa8 --- /dev/null +++ b/sing-box/docs/configuration/service/ocm.md @@ -0,0 +1,171 @@ +--- +icon: material/new-box +--- + +!!! question "Since sing-box 1.13.0" + +# OCM + +OCM (OpenAI Codex Multiplexer) service is a multiplexing service that allows you to access your local OpenAI Codex subscription remotely through custom tokens. + +It handles OAuth authentication with OpenAI's API on your local machine while allowing remote clients to authenticate using custom tokens. + +### Structure + +```json +{ + "type": "ocm", + + ... // Listen Fields + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### Listen Fields + +See [Listen Fields](/configuration/shared/listen/) for details. + +### Fields + +#### credential_path + +Path to the OpenAI OAuth credentials file. + +If not specified, defaults to `~/.codex/auth.json`. + +Refreshed tokens are automatically written back to the same location. + +#### usages_path + +Path to the file for storing aggregated API usage statistics. + +Usage tracking is disabled if not specified. + +When enabled, the service tracks and saves comprehensive statistics including: +- Request counts +- Token usage (input, output, cached) +- Calculated costs in USD based on OpenAI API pricing + +Statistics are organized by model and optionally by user when authentication is enabled. + +The statistics file is automatically saved every minute and upon service shutdown. + +#### users + +List of authorized users for token authentication. + +If empty, no authentication is required. + +Object format: + +```json +{ + "name": "", + "token": "" +} +``` + +Object fields: + +- `name`: Username identifier for tracking purposes. +- `token`: Bearer token for authentication. Clients authenticate by setting the `Authorization: Bearer ` header. + +#### headers + +Custom HTTP headers to send to the OpenAI API. + +These headers will override any existing headers with the same name. + +#### detour + +Outbound tag for connecting to the OpenAI API. + +#### tls + +TLS configuration, see [TLS](/configuration/shared/tls/#inbound). + +### Example + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +``` + +Then run: + +```bash +codex --model-provider ocm +``` + +### Example with Authentication + +#### Server + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-alice-secret-token" + }, + { + "name": "bob", + "token": "sk-bob-secret-token" + } + ] + } + ] +} +``` + +#### Client + +Add to `~/.codex/config.toml`: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +experimental_bearer_token = "sk-alice-secret-token" +``` + +Then run: + +```bash +codex --model-provider ocm +``` diff --git a/sing-box/docs/configuration/service/ocm.zh.md b/sing-box/docs/configuration/service/ocm.zh.md new file mode 100644 index 0000000000..ee1d851013 --- /dev/null +++ b/sing-box/docs/configuration/service/ocm.zh.md @@ -0,0 +1,171 @@ +--- +icon: material/new-box +--- + +!!! question "自 sing-box 1.13.0 起" + +# OCM + +OCM(OpenAI Codex 多路复用器)服务是一个多路复用服务,允许您通过自定义令牌远程访问本地的 OpenAI Codex 订阅。 + +它在本地机器上处理与 OpenAI API 的 OAuth 身份验证,同时允许远程客户端使用自定义令牌进行身份验证。 + +### 结构 + +```json +{ + "type": "ocm", + + ... // 监听字段 + + "credential_path": "", + "usages_path": "", + "users": [], + "headers": {}, + "detour": "", + "tls": {} +} +``` + +### 监听字段 + +参阅 [监听字段](/zh/configuration/shared/listen/) 了解详情。 + +### 字段 + +#### credential_path + +OpenAI OAuth 凭据文件的路径。 + +如果未指定,默认值为 `~/.codex/auth.json`。 + +刷新的令牌会自动写回相同位置。 + +#### usages_path + +用于存储聚合 API 使用统计信息的文件路径。 + +如果未指定,使用跟踪将被禁用。 + +启用后,服务会跟踪并保存全面的统计信息,包括: +- 请求计数 +- 令牌使用量(输入、输出、缓存) +- 基于 OpenAI API 定价计算的美元成本 + +统计信息按模型以及可选的用户(启用身份验证时)进行组织。 + +统计文件每分钟自动保存一次,并在服务关闭时保存。 + +#### users + +用于令牌身份验证的授权用户列表。 + +如果为空,则不需要身份验证。 + +对象格式: + +```json +{ + "name": "", + "token": "" +} +``` + +对象字段: + +- `name`:用于跟踪的用户名标识符。 +- `token`:用于身份验证的 Bearer 令牌。客户端通过设置 `Authorization: Bearer ` 头进行身份验证。 + +#### headers + +发送到 OpenAI API 的自定义 HTTP 头。 + +这些头会覆盖同名的现有头。 + +#### detour + +用于连接 OpenAI API 的出站标签。 + +#### tls + +TLS 配置,参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 + +### 示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "127.0.0.1", + "listen_port": 8080 + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +``` + +然后运行: + +```bash +codex --model-provider ocm +``` + +### 带身份验证的示例 + +#### 服务端 + +```json +{ + "services": [ + { + "type": "ocm", + "listen": "0.0.0.0", + "listen_port": 8080, + "usages_path": "./codex-usages.json", + "users": [ + { + "name": "alice", + "token": "sk-alice-secret-token" + }, + { + "name": "bob", + "token": "sk-bob-secret-token" + } + ] + } + ] +} +``` + +#### 客户端 + +在 `~/.codex/config.toml` 中添加: + +```toml +[model_providers.ocm] +name = "OCM Proxy" +base_url = "http://127.0.0.1:8080/v1" +wire_api = "responses" +requires_openai_auth = false +experimental_bearer_token = "sk-alice-secret-token" +``` + +然后运行: + +```bash +codex --model-provider ocm +``` diff --git a/sing-box/go.mod b/sing-box/go.mod index 5bd2a6527f..88fa0348a3 100644 --- a/sing-box/go.mod +++ b/sing-box/go.mod @@ -21,12 +21,13 @@ require ( github.com/metacubex/utls v1.8.3 github.com/mholt/acmez/v3 v3.1.2 github.com/miekg/dns v1.1.67 + github.com/openai/openai-go/v3 v3.13.0 github.com/oschwald/maxminddb-golang v1.13.1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cors v1.2.1 - github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 - github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 + github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0 + github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0 github.com/sagernet/fswatch v0.1.1 github.com/sagernet/gomobile v0.1.10 github.com/sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1 @@ -107,29 +108,29 @@ require ( github.com/prometheus-community/pro-bing v0.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/safchain/ethtool v0.3.0 // indirect - github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b // indirect - github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b // indirect + github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f // indirect + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f // indirect github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect github.com/sagernet/nftables v0.3.0-beta.4 // indirect github.com/spf13/pflag v1.0.6 // indirect diff --git a/sing-box/go.sum b/sing-box/go.sum index 97dc5459c2..99465d060c 100644 --- a/sing-box/go.sum +++ b/sing-box/go.sum @@ -132,6 +132,8 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/openai/openai-go/v3 v3.13.0 h1:arSFmVHcBHNVYG5iqspPJrLoin0Qqn2JcCLWWcTcM1Q= +github.com/openai/openai-go/v3 v3.13.0/go.mod h1:cdufnVK14cWcT9qA1rRtrXx4FTRsgbDPW7Ia7SS5cZo= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -152,56 +154,56 @@ github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkk github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= -github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7 h1:jb/nr5YECJ56gcAphQ7tBWierrBbaLT7v1MI9n3e/Gw= -github.com/sagernet/cronet-go v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= -github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7 h1:6PjoWjKnYrz/HmEezV1Z5K39EC8l+sek1V14aXlslyc= -github.com/sagernet/cronet-go/all v0.0.0-20251215064722-77bfb8fdd9f7/go.mod h1:SrXj1iQMVqZcy8XINBJOhlBncfCe7DimX6mTRY+rdDw= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b h1:+Dk1yBvaKl49l8j3YFoEvraAdt7VMy7n2Qzrs40/ekI= -github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b h1:tjkKLyRhD1ePdl48SjW38o7yjW1fCJ2x2nyvq5e/8oE= -github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b h1:P++HSm1JhmkKbDskFNfQuR8aCTg5uEWe2/5qFfj+6YU= -github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b h1:Rbo1r5Mk8yWlZTC8gcyuQFv2BXUI1/wWMC9Vc+cJNQ8= -github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b h1:VA1M5Yw09HiBD+Zemq6mOBVwBd4pr47LMN9WKOVf62Q= -github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AdWIsXfKxH3/hGjiYqcUSc0fb+R4vONjfRaO0emwdNA= -github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:sg8SupaVsj0Krc4DKSC1n2quig08bRtmsF0/iwwXeAI= -github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b h1:0dmsm/vEAYxQjtH4sS/A8X6bf6YqS0I0Vc6oDZdnlRc= -github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:jnT/bYjzvdfGVgPEgZX0Mi0qkm8qcU/DluV+TqShVPg= -github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b h1:/NqFcrdXS3e3Ad+ILfrwXFw3urwwFsQ1XxrDW9PkU4E= -github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b h1:vqeLRyeHq++RCcuUriJflTQne7hldEVJ19Or0xwCIrs= -github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b h1:Xr7dFoKy0o2YdPl2JcU7GtM4NxQyS8vGovd6Aw4pX8I= -github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b h1:GEt+x1qXt8xicDSD4GXOHs0WrVec5HAo+HmBAXzkidg= -github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b h1:MbjH6TmLoXlAkBWoUzuNF2w0FPfOMY6Rj9T226fe858= -github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251215064325-26e9598ca37b/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b h1:AP85VNYiACL8QQeXqCUB8hz5hFOUtgwReLELRhve/4c= -github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b h1:4uNGGiOrJsa2S+PteucoO/Qyzz7FWHNJw2ezOkS7QiM= -github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b h1:N5yoxOlynwvTgaJnEOsL3iuI6FFmDJy1toyNSU+vlLA= -github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251215064325-26e9598ca37b/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b h1:JKyBNyt/DWAutvuDFjFTi0dMe0bh5zG7UUpZHH8Uqzo= -github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b h1:m0sCMM6ry0+eXBuTPLGY9JYOVcIvtHcDEcstMo+oSTU= -github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b h1:UURnlFD48/9wn7cdi1NqYQuTvJZEFuQShxX8pvk2Fsg= -github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251215064325-26e9598ca37b/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b h1:jhwpI5IXK5RPvbk9+xUV9GAw2QeRZvcZprp4bJOP9e0= -github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b h1:qoleSwhzgH6jDSwqktbJCPDex4yMWtijcouGR8+BL+s= -github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b h1:v7eakED1u8ZTKjmqxa+Eu0S5ewK+r+mfEf9KI6ymu+I= -github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251215064325-26e9598ca37b/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= +github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0 h1:LESmw76c/bzprJSDdAPpXTDLbZnir2w3TEX08uPD+Ls= +github.com/sagernet/cronet-go v0.0.0-20251216133850-319203a5f9c0/go.mod h1:DzcRxPQdpy5y2bbabpFXotAzPfY2P4HKZ8rQj3dSClo= +github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0 h1:q2Y9oZBQyCyFV7hMdZXtpPabJWq1WiWL/eyKRbj2z28= +github.com/sagernet/cronet-go/all v0.0.0-20251216133850-319203a5f9c0/go.mod h1:ozG0O0AvB4rKe9A7twrqp4UvnExSzmbEhsdcmm14PwM= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f h1:eXI9DXjk4WtcukZumm2EEihwvNswjcoe3oT9LrriOxw= +github.com/sagernet/cronet-go/lib/android_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:XXDwdjX/T8xftoeJxQmbBoYXZp8MAPFR2CwbFuTpEtw= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:AmjLqs5BiC8YPi900EB5LdEZK5kWmHg0MMmQbpT9WsM= +github.com/sagernet/cronet-go/lib/android_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:iNiUGoLtnr8/JTuVNj7XJbmpOAp2C6+B81KDrPxwaZM= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f h1:ts10PNXwK+2HP15ushAYJVMtoUV7V9UAOr+8AMr00uw= +github.com/sagernet/cronet-go/lib/android_arm v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:19ILNUOGIzRdOqa2mq+iY0JoHxuieB7/lnjYeaA2vEc= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:dUd/75r4DC/P0374iXDyx7vPlCt7j13vuuCsoPhvJvg= +github.com/sagernet/cronet-go/lib/android_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:JxzGyQf94Cr6sBShKqODGDyRUlESfJK/Njcz9Lz6qMQ= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:Wfjf525uzg8TRV752sn6N7Ycw+Ee0Eorytd2cT7aVh0= +github.com/sagernet/cronet-go/lib/darwin_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:KN+9T9TBycGOLzmKU4QdcHAJEj6Nlx48ifnlTvvHMvs= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:y8KaKG9TgX5KPcUxzMu/hiPChoUNL8U17f92YWrV4zo= +github.com/sagernet/cronet-go/lib/darwin_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:kojvtUc29KKnk8hs2QIANynVR59921SnGWA9kXohHc0= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:mxy23kdWCNe0zLPC8+jw0kmvPNeC8WPRGA5O9BWvC0U= +github.com/sagernet/cronet-go/lib/ios_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:hkQzRE5GDbaH1/ioqYh0Taho4L6i0yLRCVEZ5xHz5M0= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:TNPhjBjjMBJTRIoK+0gqIjDks/5bJm47sGZ3bw8gCFs= +github.com/sagernet/cronet-go/lib/ios_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:tzVJFTOm66UxLxy6K0ZN5Ic2PC79e+sKKnt+V9puEa4= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:WBSrJO0CiqWKS7sMstL0zJJVvt2L7TrmBmbj8vPz+gY= +github.com/sagernet/cronet-go/lib/ios_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:M/pN6m3j0HFU6/y83n0HU6GLYys3tYdr/xTE8hVEGMo= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f h1:UOJuw1PUXmLGlVTdvr7U93/olD6CeDgFpkeKg7OdzbI= +github.com/sagernet/cronet-go/lib/linux_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:cGh5hO6eljCo6KMQ/Cel8Xgq4+etL0awZLRBDVG1EZQ= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f h1:wfENhS38qymYlW0Ef9Y45tvPRAEyj5WFcQDVebg0Uqc= +github.com/sagernet/cronet-go/lib/linux_386_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:JFE0/cxaKkx0wqPMZU7MgaplQlU0zudv82dROJjClKU= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:JMdNN9P8Pt65PKwUOrK6/ejXrosOPyAP7QdlA2X71E0= +github.com/sagernet/cronet-go/lib/linux_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:vU8VftFeSt7fURCa3JXD6+k6ss1YAX+idQjPvHmJ2tI= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f h1:6lyBrJKu+nQSiNdP1jWLKbhEnJ5pJ3Amcr96qAk/Wxk= +github.com/sagernet/cronet-go/lib/linux_amd64_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:vCe4OUuL+XOUge9v3MyTD45BnuAXiH+DkjN9quDXJzQ= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f h1:9/7uFHDpI5ZGzLAVSro0dBGOVBxboIFsxtI2QIrf3CI= +github.com/sagernet/cronet-go/lib/linux_arm v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:w9amBWrvjtohQzBGCKJ7LCh22LhTIJs4sE7cYaKQzM0= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:3vgjnWxtlP2qqCP9A3kA4IRRr3AjDzF0C8NiB0qRpnY= +github.com/sagernet/cronet-go/lib/linux_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:TqlsFtcYS/etTeck46kHBeT8Le0Igw1Q/AV88UnMS3s= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f h1:HXNSrz0zn5LgbdfLoIJb45YQY789vs8EfSjZwX6P46Y= +github.com/sagernet/cronet-go/lib/linux_arm64_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:B6Qd0vys8sv9OKVRN6J9RqDzYRGE938Fb2zrYdBDyTQ= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f h1:mEys4XlVs9Ewnj08PPkuMD+7Cd9+kWIe2UAvhBDc4t4= +github.com/sagernet/cronet-go/lib/linux_arm_musl v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:3tXMMFY7AHugOVBZ5Al7cL7JKsnFOe5bMVr0hZPk3ow= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:Jsqlc8k52ZsOF6nd/Pq7jh9MhZUEh6wxbabL8Y5CilU= +github.com/sagernet/cronet-go/lib/tvos_amd64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:aaX0YGl8nhGmfRWI8bc3BtDjY8Vzx6O0cS/e1uqxDq4= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:wP4nDfGDca5JvqTj1mpqu3CMfMahsmIY1vPyALTtqXI= +github.com/sagernet/cronet-go/lib/tvos_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:EdzMKA96xITc42QEI+ct4SwqX8Dn3ltKK8wzdkLWpSc= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f h1:psbgr8u0fuUwH95k8+Z4HfMcyOevAXARGRLdfexsldo= +github.com/sagernet/cronet-go/lib/tvos_arm64_simulator v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:qix4kv1TTAJ5tY4lJ9vjhe9EY4mM+B7H5giOhbxDVcc= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f h1:xzfMiwWj9cyJWDr0WWC6xkZ14RWo/XqEQrJlJm6MZag= +github.com/sagernet/cronet-go/lib/windows_386 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:rnS7D+ULJX2PrP0Cy+05GS0mRZ2PP6+gVSroZKt8fjk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f h1:AxA2aJ5IcEUkAgsEDeu6ICHd01F+Kn61bXKfZB4uKTk= +github.com/sagernet/cronet-go/lib/windows_amd64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:lm9w/oCCRyBiUa3G8lDQTT8x/ONUvgVR2iV9fVzUZB8= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f h1:M21bGiwCgCY2rl0huKXo36eV0D4ep3ZgRPh6BKgvGh0= +github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20251216133425-9f6a31f51e7f/go.mod h1:n34YyLgapgjWdKa0IoeczjAFCwD3/dxbsH5sucKw0bw= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/gomobile v0.1.10 h1:ElqZ0OVDvyQlU91MU0C9cfU0FrILBbc65+NOKzZ1t0c= diff --git a/sing-box/include/ocm.go b/sing-box/include/ocm.go new file mode 100644 index 0000000000..cdea9eeaea --- /dev/null +++ b/sing-box/include/ocm.go @@ -0,0 +1,12 @@ +//go:build with_ocm + +package include + +import ( + "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/service/ocm" +) + +func registerOCMService(registry *service.Registry) { + ocm.RegisterService(registry) +} diff --git a/sing-box/include/ocm_stub.go b/sing-box/include/ocm_stub.go new file mode 100644 index 0000000000..d5a94fcba9 --- /dev/null +++ b/sing-box/include/ocm_stub.go @@ -0,0 +1,20 @@ +//go:build !with_ocm + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/service" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerOCMService(registry *service.Registry) { + service.Register[option.OCMServiceOptions](registry, C.TypeOCM, func(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + return nil, E.New(`OCM is not included in this build, rebuild with -tags with_ocm`) + }) +} diff --git a/sing-box/include/registry.go b/sing-box/include/registry.go index 8f08189d40..d909b8500b 100644 --- a/sing-box/include/registry.go +++ b/sing-box/include/registry.go @@ -136,6 +136,7 @@ func ServiceRegistry() *service.Registry { registerDERPService(registry) registerCCMService(registry) + registerOCMService(registry) return registry } diff --git a/sing-box/mkdocs.yml b/sing-box/mkdocs.yml index c49bfa2a67..a505f3e4d1 100644 --- a/sing-box/mkdocs.yml +++ b/sing-box/mkdocs.yml @@ -176,6 +176,8 @@ nav: - DERP: configuration/service/derp.md - Resolved: configuration/service/resolved.md - SSM API: configuration/service/ssm-api.md + - CCM: configuration/service/ccm.md + - OCM: configuration/service/ocm.md markdown_extensions: - pymdownx.inlinehilite - pymdownx.snippets diff --git a/sing-box/option/ocm.go b/sing-box/option/ocm.go new file mode 100644 index 0000000000..c13a1c1f53 --- /dev/null +++ b/sing-box/option/ocm.go @@ -0,0 +1,20 @@ +package option + +import ( + "github.com/sagernet/sing/common/json/badoption" +) + +type OCMServiceOptions struct { + ListenOptions + InboundTLSOptionsContainer + CredentialPath string `json:"credential_path,omitempty"` + Users []OCMUser `json:"users,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` + Detour string `json:"detour,omitempty"` + UsagesPath string `json:"usages_path,omitempty"` +} + +type OCMUser struct { + Name string `json:"name,omitempty"` + Token string `json:"token,omitempty"` +} diff --git a/sing-box/release/local/common.sh b/sing-box/release/local/common.sh index d24bba4759..68a494babd 100755 --- a/sing-box/release/local/common.sh +++ b/sing-box/release/local/common.sh @@ -11,7 +11,7 @@ INSTALL_CONFIG_PATH="/usr/local/etc/sing-box" INSTALL_DATA_PATH="/var/lib/sing-box" SYSTEMD_SERVICE_PATH="/etc/systemd/system" -DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,badlinkname,tfogo_checklinkname0" +DEFAULT_BUILD_TAGS="with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,badlinkname,tfogo_checklinkname0" setup_environment() { if [ -d /usr/local/go ]; then diff --git a/sing-box/service/ccm/service_usage.go b/sing-box/service/ccm/service_usage.go index 53ae46587a..7d39e3ce56 100644 --- a/sing-box/service/ccm/service_usage.go +++ b/sing-box/service/ccm/service_usage.go @@ -124,12 +124,69 @@ var ( CacheWritePrice: 3.75, } + opus45Pricing = ModelPricing{ + InputPrice: 5.0, + OutputPrice: 25.0, + CacheReadPrice: 0.5, + CacheWritePrice: 6.25, + } + + sonnet45StandardPricing = ModelPricing{ + InputPrice: 3.0, + OutputPrice: 15.0, + CacheReadPrice: 0.3, + CacheWritePrice: 3.75, + } + + sonnet45PremiumPricing = ModelPricing{ + InputPrice: 6.0, + OutputPrice: 22.5, + CacheReadPrice: 0.6, + CacheWritePrice: 7.5, + } + + haiku45Pricing = ModelPricing{ + InputPrice: 1.0, + OutputPrice: 5.0, + CacheReadPrice: 0.1, + CacheWritePrice: 1.25, + } + + haiku3Pricing = ModelPricing{ + InputPrice: 0.25, + OutputPrice: 1.25, + CacheReadPrice: 0.03, + CacheWritePrice: 0.3, + } + + opus3Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 75.0, + CacheReadPrice: 1.5, + CacheWritePrice: 18.75, + } + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^claude-opus-4-5-`), + standardPricing: opus45Pricing, + premiumPricing: nil, + }, { pattern: regexp.MustCompile(`^claude-(?:opus-4-|4-opus-|opus-4-1-)`), standardPricing: opus4Pricing, premiumPricing: nil, }, + { + pattern: regexp.MustCompile(`^claude-(?:opus-3-|3-opus-)`), + standardPricing: opus3Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:sonnet-4-5-|4-5-sonnet-)`), + standardPricing: sonnet45StandardPricing, + premiumPricing: &sonnet45PremiumPricing, + }, { pattern: regexp.MustCompile(`^claude-3-7-sonnet-`), standardPricing: sonnet4StandardPricing, @@ -140,6 +197,16 @@ var ( standardPricing: sonnet4StandardPricing, premiumPricing: &sonnet4PremiumPricing, }, + { + pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), + standardPricing: sonnet35Pricing, + premiumPricing: nil, + }, + { + pattern: regexp.MustCompile(`^claude-(?:haiku-4-5-|4-5-haiku-)`), + standardPricing: haiku45Pricing, + premiumPricing: nil, + }, { pattern: regexp.MustCompile(`^claude-haiku-4-`), standardPricing: haiku4Pricing, @@ -151,8 +218,8 @@ var ( premiumPricing: nil, }, { - pattern: regexp.MustCompile(`^claude-3-5-sonnet-`), - standardPricing: sonnet35Pricing, + pattern: regexp.MustCompile(`^claude-3-haiku-`), + standardPricing: haiku3Pricing, premiumPricing: nil, }, } diff --git a/sing-box/service/ocm/credential.go b/sing-box/service/ocm/credential.go new file mode 100644 index 0000000000..76651a8e14 --- /dev/null +++ b/sing-box/service/ocm/credential.go @@ -0,0 +1,173 @@ +package ocm + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "time" + + E "github.com/sagernet/sing/common/exceptions" +) + +const ( + oauth2ClientID = "app_EMoamEEZ73f0CkXaXp7hrann" + oauth2TokenURL = "https://auth.openai.com/oauth/token" + openaiAPIBaseURL = "https://api.openai.com" + chatGPTBackendURL = "https://chatgpt.com/backend-api/codex" + tokenRefreshIntervalDays = 8 +) + +func getRealUser() (*user.User, error) { + if sudoUser := os.Getenv("SUDO_USER"); sudoUser != "" { + sudoUserInfo, err := user.Lookup(sudoUser) + if err == nil { + return sudoUserInfo, nil + } + } + return user.Current() +} + +func getDefaultCredentialsPath() (string, error) { + if codexHome := os.Getenv("CODEX_HOME"); codexHome != "" { + return filepath.Join(codexHome, "auth.json"), nil + } + userInfo, err := getRealUser() + if err != nil { + return "", err + } + return filepath.Join(userInfo.HomeDir, ".codex", "auth.json"), nil +} + +func readCredentialsFromFile(path string) (*oauthCredentials, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var credentials oauthCredentials + err = json.Unmarshal(data, &credentials) + if err != nil { + return nil, err + } + return &credentials, nil +} + +func writeCredentialsToFile(credentials *oauthCredentials, path string) error { + data, err := json.MarshalIndent(credentials, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, data, 0o600) +} + +type oauthCredentials struct { + APIKey string `json:"OPENAI_API_KEY,omitempty"` + Tokens *tokenData `json:"tokens,omitempty"` + LastRefresh *time.Time `json:"last_refresh,omitempty"` +} + +type tokenData struct { + IDToken string `json:"id_token,omitempty"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + AccountID string `json:"account_id,omitempty"` +} + +func (c *oauthCredentials) isAPIKeyMode() bool { + return c.APIKey != "" +} + +func (c *oauthCredentials) getAccessToken() string { + if c.APIKey != "" { + return c.APIKey + } + if c.Tokens != nil { + return c.Tokens.AccessToken + } + return "" +} + +func (c *oauthCredentials) getAccountID() string { + if c.Tokens != nil { + return c.Tokens.AccountID + } + return "" +} + +func (c *oauthCredentials) needsRefresh() bool { + if c.APIKey != "" { + return false + } + if c.Tokens == nil || c.Tokens.RefreshToken == "" { + return false + } + if c.LastRefresh == nil { + return true + } + return time.Since(*c.LastRefresh) >= time.Duration(tokenRefreshIntervalDays)*24*time.Hour +} + +func refreshToken(httpClient *http.Client, credentials *oauthCredentials) (*oauthCredentials, error) { + if credentials.Tokens == nil || credentials.Tokens.RefreshToken == "" { + return nil, E.New("refresh token is empty") + } + + requestBody, err := json.Marshal(map[string]string{ + "grant_type": "refresh_token", + "refresh_token": credentials.Tokens.RefreshToken, + "client_id": oauth2ClientID, + "scope": "openid profile email", + }) + if err != nil { + return nil, E.Cause(err, "marshal request") + } + + request, err := http.NewRequest("POST", oauth2TokenURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, err + } + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + + response, err := httpClient.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + body, _ := io.ReadAll(response.Body) + return nil, E.New("refresh failed: ", response.Status, " ", string(body)) + } + + var tokenResponse struct { + IDToken string `json:"id_token"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + } + err = json.NewDecoder(response.Body).Decode(&tokenResponse) + if err != nil { + return nil, E.Cause(err, "decode response") + } + + newCredentials := *credentials + if newCredentials.Tokens == nil { + newCredentials.Tokens = &tokenData{} + } + if tokenResponse.IDToken != "" { + newCredentials.Tokens.IDToken = tokenResponse.IDToken + } + if tokenResponse.AccessToken != "" { + newCredentials.Tokens.AccessToken = tokenResponse.AccessToken + } + if tokenResponse.RefreshToken != "" { + newCredentials.Tokens.RefreshToken = tokenResponse.RefreshToken + } + now := time.Now() + newCredentials.LastRefresh = &now + + return &newCredentials, nil +} diff --git a/sing-box/service/ocm/credential_darwin.go b/sing-box/service/ocm/credential_darwin.go new file mode 100644 index 0000000000..f3da2a63ed --- /dev/null +++ b/sing-box/service/ocm/credential_darwin.go @@ -0,0 +1,25 @@ +//go:build darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/sing-box/service/ocm/credential_other.go b/sing-box/service/ocm/credential_other.go new file mode 100644 index 0000000000..22dfd0337a --- /dev/null +++ b/sing-box/service/ocm/credential_other.go @@ -0,0 +1,25 @@ +//go:build !darwin + +package ocm + +func platformReadCredentials(customPath string) (*oauthCredentials, error) { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return nil, err + } + } + return readCredentialsFromFile(customPath) +} + +func platformWriteCredentials(credentials *oauthCredentials, customPath string) error { + if customPath == "" { + var err error + customPath, err = getDefaultCredentialsPath() + if err != nil { + return err + } + } + return writeCredentialsToFile(credentials, customPath) +} diff --git a/sing-box/service/ocm/service.go b/sing-box/service/ocm/service.go new file mode 100644 index 0000000000..e8f9541054 --- /dev/null +++ b/sing-box/service/ocm/service.go @@ -0,0 +1,555 @@ +package ocm + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io" + "mime" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/sagernet/sing-box/adapter" + boxService "github.com/sagernet/sing-box/adapter/service" + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + aTLS "github.com/sagernet/sing/common/tls" + + "github.com/go-chi/chi/v5" + "github.com/openai/openai-go/v3" + "github.com/openai/openai-go/v3/responses" + "golang.org/x/net/http2" +) + +func RegisterService(registry *boxService.Registry) { + boxService.Register[option.OCMServiceOptions](registry, C.TypeOCM, NewService) +} + +type errorResponse struct { + Error errorDetails `json:"error"` +} + +type errorDetails struct { + Type string `json:"type"` + Code string `json:"code,omitempty"` + Message string `json:"message"` +} + +func writeJSONError(w http.ResponseWriter, r *http.Request, statusCode int, errorType string, message string) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + json.NewEncoder(w).Encode(errorResponse{ + Error: errorDetails{ + Type: errorType, + Message: message, + }, + }) +} + +func isHopByHopHeader(header string) bool { + switch strings.ToLower(header) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade", "host": + return true + default: + return false + } +} + +type Service struct { + boxService.Adapter + ctx context.Context + logger log.ContextLogger + credentialPath string + credentials *oauthCredentials + users []option.OCMUser + httpClient *http.Client + httpHeaders http.Header + listener *listener.Listener + tlsConfig tls.ServerConfig + httpServer *http.Server + userManager *UserManager + accessMutex sync.RWMutex + usageTracker *AggregatedUsage + trackingGroup sync.WaitGroup + shuttingDown bool +} + +func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.OCMServiceOptions) (adapter.Service, error) { + serviceDialer, err := dialer.NewWithOptions(dialer.Options{ + Context: ctx, + Options: option.DialerOptions{ + Detour: options.Detour, + }, + RemoteIsDomain: true, + }) + if err != nil { + return nil, E.Cause(err, "create dialer") + } + + httpClient := &http.Client{ + Transport: &http.Transport{ + ForceAttemptHTTP2: true, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return serviceDialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) + }, + }, + } + + userManager := &UserManager{ + tokenMap: make(map[string]string), + } + + var usageTracker *AggregatedUsage + if options.UsagesPath != "" { + usageTracker = &AggregatedUsage{ + LastUpdated: time.Now(), + Combinations: make([]CostCombination, 0), + filePath: options.UsagesPath, + logger: logger, + } + } + + service := &Service{ + Adapter: boxService.NewAdapter(C.TypeOCM, tag), + ctx: ctx, + logger: logger, + credentialPath: options.CredentialPath, + users: options.Users, + httpClient: httpClient, + httpHeaders: options.Headers.Build(), + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + }), + userManager: userManager, + usageTracker: usageTracker, + } + + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + service.tlsConfig = tlsConfig + } + + return service, nil +} + +func (s *Service) Start(stage adapter.StartStage) error { + if stage != adapter.StartStateStart { + return nil + } + + s.userManager.UpdateUsers(s.users) + + credentials, err := platformReadCredentials(s.credentialPath) + if err != nil { + return E.Cause(err, "read credentials") + } + s.credentials = credentials + + if s.usageTracker != nil { + err = s.usageTracker.Load() + if err != nil { + s.logger.Warn("load usage statistics: ", err) + } + } + + router := chi.NewRouter() + router.Mount("/", s) + + s.httpServer = &http.Server{Handler: router} + + if s.tlsConfig != nil { + err = s.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + + tcpListener, err := s.listener.ListenTCP() + if err != nil { + return err + } + + if s.tlsConfig != nil { + if !common.Contains(s.tlsConfig.NextProtos(), http2.NextProtoTLS) { + s.tlsConfig.SetNextProtos(append([]string{"h2"}, s.tlsConfig.NextProtos()...)) + } + tcpListener = aTLS.NewListener(tcpListener, s.tlsConfig) + } + + go func() { + serveErr := s.httpServer.Serve(tcpListener) + if serveErr != nil && !errors.Is(serveErr, http.ErrServerClosed) { + s.logger.Error("serve error: ", serveErr) + } + }() + + return nil +} + +func (s *Service) getAccessToken() (string, error) { + s.accessMutex.RLock() + if !s.credentials.needsRefresh() { + token := s.credentials.getAccessToken() + s.accessMutex.RUnlock() + return token, nil + } + s.accessMutex.RUnlock() + + s.accessMutex.Lock() + defer s.accessMutex.Unlock() + + if !s.credentials.needsRefresh() { + return s.credentials.getAccessToken(), nil + } + + newCredentials, err := refreshToken(s.httpClient, s.credentials) + if err != nil { + return "", err + } + + s.credentials = newCredentials + + err = platformWriteCredentials(newCredentials, s.credentialPath) + if err != nil { + s.logger.Warn("persist refreshed token: ", err) + } + + return newCredentials.getAccessToken(), nil +} + +func (s *Service) getAccountID() string { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.getAccountID() +} + +func (s *Service) isAPIKeyMode() bool { + s.accessMutex.RLock() + defer s.accessMutex.RUnlock() + return s.credentials.isAPIKeyMode() +} + +func (s *Service) getBaseURL() string { + if s.isAPIKeyMode() { + return openaiAPIBaseURL + } + return chatGPTBackendURL +} + +func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { + path := r.URL.Path + if !strings.HasPrefix(path, "/v1/") { + writeJSONError(w, r, http.StatusNotFound, "invalid_request_error", "path must start with /v1/") + return + } + + var proxyPath string + if s.isAPIKeyMode() { + proxyPath = path + } else { + if path == "/v1/chat/completions" { + writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error", + "chat completions endpoint is only available in API key mode") + return + } + proxyPath = strings.TrimPrefix(path, "/v1") + } + + var username string + if len(s.users) > 0 { + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": missing Authorization header") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key") + return + } + clientToken := strings.TrimPrefix(authHeader, "Bearer ") + if clientToken == authHeader { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": invalid Authorization format") + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format") + return + } + var ok bool + username, ok = s.userManager.Authenticate(clientToken) + if !ok { + s.logger.Warn("authentication failed for request from ", r.RemoteAddr, ": unknown key: ", clientToken) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key") + return + } + } + + var requestModel string + + if s.usageTracker != nil && r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + var request struct { + Model string `json:"model"` + } + err := json.Unmarshal(bodyBytes, &request) + if err == nil { + requestModel = request.Model + } + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } + + accessToken, err := s.getAccessToken() + if err != nil { + s.logger.Error("get access token: ", err) + writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "Authentication failed") + return + } + + proxyURL := s.getBaseURL() + proxyPath + if r.URL.RawQuery != "" { + proxyURL += "?" + r.URL.RawQuery + } + proxyRequest, err := http.NewRequestWithContext(r.Context(), r.Method, proxyURL, r.Body) + if err != nil { + s.logger.Error("create proxy request: ", err) + writeJSONError(w, r, http.StatusInternalServerError, "api_error", "Internal server error") + return + } + + for key, values := range r.Header { + if !isHopByHopHeader(key) && key != "Authorization" { + proxyRequest.Header[key] = values + } + } + + for key, values := range s.httpHeaders { + proxyRequest.Header.Del(key) + proxyRequest.Header[key] = values + } + + proxyRequest.Header.Set("Authorization", "Bearer "+accessToken) + + if accountID := s.getAccountID(); accountID != "" { + proxyRequest.Header.Set("ChatGPT-Account-Id", accountID) + } + + response, err := s.httpClient.Do(proxyRequest) + if err != nil { + writeJSONError(w, r, http.StatusBadGateway, "api_error", err.Error()) + return + } + defer response.Body.Close() + + for key, values := range response.Header { + if !isHopByHopHeader(key) { + w.Header()[key] = values + } + } + w.WriteHeader(response.StatusCode) + + trackUsage := s.usageTracker != nil && response.StatusCode == http.StatusOK && + (path == "/v1/chat/completions" || strings.HasPrefix(path, "/v1/responses")) + if trackUsage { + s.handleResponseWithTracking(w, response, path, requestModel, username) + } else { + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + if err == nil && mediaType != "text/event-stream" { + _, _ = io.Copy(w, response.Body) + return + } + flusher, ok := w.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + buffer := make([]byte, buf.BufferSize) + for { + n, err := response.Body.Read(buffer) + if n > 0 { + _, writeError := w.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + if err != nil { + return + } + } + } +} + +func (s *Service) handleResponseWithTracking(writer http.ResponseWriter, response *http.Response, path string, requestModel string, username string) { + isChatCompletions := path == "/v1/chat/completions" + mediaType, _, err := mime.ParseMediaType(response.Header.Get("Content-Type")) + isStreaming := err == nil && mediaType == "text/event-stream" + + if !isStreaming { + bodyBytes, err := io.ReadAll(response.Body) + if err != nil { + s.logger.Error("read response body: ", err) + return + } + + var responseModel string + var inputTokens, outputTokens, cachedTokens int64 + + if isChatCompletions { + var chatCompletion openai.ChatCompletion + if json.Unmarshal(bodyBytes, &chatCompletion) == nil { + responseModel = chatCompletion.Model + inputTokens = chatCompletion.Usage.PromptTokens + outputTokens = chatCompletion.Usage.CompletionTokens + cachedTokens = chatCompletion.Usage.PromptTokensDetails.CachedTokens + } + } else { + var responsesResponse responses.Response + if json.Unmarshal(bodyBytes, &responsesResponse) == nil { + responseModel = string(responsesResponse.Model) + inputTokens = responsesResponse.Usage.InputTokens + outputTokens = responsesResponse.Usage.OutputTokens + cachedTokens = responsesResponse.Usage.InputTokensDetails.CachedTokens + } + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel == "" { + responseModel = requestModel + } + if responseModel != "" { + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + } + } + + _, _ = writer.Write(bodyBytes) + return + } + + flusher, ok := writer.(http.Flusher) + if !ok { + s.logger.Error("streaming not supported") + return + } + + var inputTokens, outputTokens, cachedTokens int64 + var responseModel string + buffer := make([]byte, buf.BufferSize) + var leftover []byte + + for { + n, err := response.Body.Read(buffer) + if n > 0 { + data := append(leftover, buffer[:n]...) + lines := bytes.Split(data, []byte("\n")) + + if err == nil { + leftover = lines[len(lines)-1] + lines = lines[:len(lines)-1] + } else { + leftover = nil + } + + for _, line := range lines { + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + if bytes.HasPrefix(line, []byte("data: ")) { + eventData := bytes.TrimPrefix(line, []byte("data: ")) + if bytes.Equal(eventData, []byte("[DONE]")) { + continue + } + + if isChatCompletions { + var chatChunk openai.ChatCompletionChunk + if json.Unmarshal(eventData, &chatChunk) == nil { + if chatChunk.Model != "" { + responseModel = chatChunk.Model + } + if chatChunk.Usage.PromptTokens > 0 { + inputTokens = chatChunk.Usage.PromptTokens + cachedTokens = chatChunk.Usage.PromptTokensDetails.CachedTokens + } + if chatChunk.Usage.CompletionTokens > 0 { + outputTokens = chatChunk.Usage.CompletionTokens + } + } + } else { + var streamEvent responses.ResponseStreamEventUnion + if json.Unmarshal(eventData, &streamEvent) == nil { + if streamEvent.Type == "response.completed" { + completedEvent := streamEvent.AsResponseCompleted() + if string(completedEvent.Response.Model) != "" { + responseModel = string(completedEvent.Response.Model) + } + if completedEvent.Response.Usage.InputTokens > 0 { + inputTokens = completedEvent.Response.Usage.InputTokens + cachedTokens = completedEvent.Response.Usage.InputTokensDetails.CachedTokens + } + if completedEvent.Response.Usage.OutputTokens > 0 { + outputTokens = completedEvent.Response.Usage.OutputTokens + } + } + } + } + } + } + + _, writeError := writer.Write(buffer[:n]) + if writeError != nil { + s.logger.Error("write streaming response: ", writeError) + return + } + flusher.Flush() + } + + if err != nil { + if responseModel == "" { + responseModel = requestModel + } + + if inputTokens > 0 || outputTokens > 0 { + if responseModel != "" { + s.usageTracker.AddUsage(responseModel, inputTokens, outputTokens, cachedTokens, username) + } + } + return + } + } +} + +func (s *Service) Close() error { + err := common.Close( + common.PtrOrNil(s.httpServer), + common.PtrOrNil(s.listener), + s.tlsConfig, + ) + + if s.usageTracker != nil { + s.usageTracker.cancelPendingSave() + saveErr := s.usageTracker.Save() + if saveErr != nil { + s.logger.Error("save usage statistics: ", saveErr) + } + } + + return err +} diff --git a/sing-box/service/ocm/service_usage.go b/sing-box/service/ocm/service_usage.go new file mode 100644 index 0000000000..7089f4d391 --- /dev/null +++ b/sing-box/service/ocm/service_usage.go @@ -0,0 +1,445 @@ +package ocm + +import ( + "encoding/json" + "math" + "os" + "regexp" + "sync" + "time" + + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" +) + +type UsageStats struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` +} + +func (u *UsageStats) UnmarshalJSON(data []byte) error { + type Alias UsageStats + aux := &struct { + *Alias + PromptTokens int64 `json:"prompt_tokens"` + CompletionTokens int64 `json:"completion_tokens"` + }{ + Alias: (*Alias)(u), + } + err := json.Unmarshal(data, aux) + if err != nil { + return err + } + if u.InputTokens == 0 && aux.PromptTokens > 0 { + u.InputTokens = aux.PromptTokens + } + if u.OutputTokens == 0 && aux.CompletionTokens > 0 { + u.OutputTokens = aux.CompletionTokens + } + return nil +} + +type CostCombination struct { + Model string `json:"model"` + Total UsageStats `json:"total"` + ByUser map[string]UsageStats `json:"by_user"` +} + +type AggregatedUsage struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + mutex sync.Mutex + filePath string + logger log.ContextLogger + lastSaveTime time.Time + pendingSave bool + saveTimer *time.Timer + saveMutex sync.Mutex +} + +type UsageStatsJSON struct { + RequestCount int `json:"request_count"` + InputTokens int64 `json:"input_tokens"` + OutputTokens int64 `json:"output_tokens"` + CachedTokens int64 `json:"cached_tokens"` + CostUSD float64 `json:"cost_usd"` +} + +type CostCombinationJSON struct { + Model string `json:"model"` + Total UsageStatsJSON `json:"total"` + ByUser map[string]UsageStatsJSON `json:"by_user"` +} + +type CostsSummaryJSON struct { + TotalUSD float64 `json:"total_usd"` + ByUser map[string]float64 `json:"by_user"` +} + +type AggregatedUsageJSON struct { + LastUpdated time.Time `json:"last_updated"` + Costs CostsSummaryJSON `json:"costs"` + Combinations []CostCombinationJSON `json:"combinations"` +} + +type ModelPricing struct { + InputPrice float64 + OutputPrice float64 + CachedInputPrice float64 +} + +type modelFamily struct { + pattern *regexp.Regexp + pricing ModelPricing +} + +var ( + gpt4oPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 1.25, + } + + gpt4oMiniPricing = ModelPricing{ + InputPrice: 0.15, + OutputPrice: 0.6, + CachedInputPrice: 0.075, + } + + gpt4oAudioPricing = ModelPricing{ + InputPrice: 2.5, + OutputPrice: 10.0, + CachedInputPrice: 1.25, + } + + o1Pricing = ModelPricing{ + InputPrice: 15.0, + OutputPrice: 60.0, + CachedInputPrice: 7.5, + } + + o1MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + o3Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 1.0, + } + + o4MiniPricing = ModelPricing{ + InputPrice: 1.1, + OutputPrice: 4.4, + CachedInputPrice: 0.55, + } + + gpt41Pricing = ModelPricing{ + InputPrice: 2.0, + OutputPrice: 8.0, + CachedInputPrice: 0.5, + } + + gpt41MiniPricing = ModelPricing{ + InputPrice: 0.4, + OutputPrice: 1.6, + CachedInputPrice: 0.1, + } + + gpt41NanoPricing = ModelPricing{ + InputPrice: 0.1, + OutputPrice: 0.4, + CachedInputPrice: 0.025, + } + + modelFamilies = []modelFamily{ + { + pattern: regexp.MustCompile(`^gpt-4\.1-nano`), + pricing: gpt41NanoPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1-mini`), + pricing: gpt41MiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4\.1`), + pricing: gpt41Pricing, + }, + { + pattern: regexp.MustCompile(`^o4-mini`), + pricing: o4MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3-mini`), + pricing: o3MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o3`), + pricing: o3Pricing, + }, + { + pattern: regexp.MustCompile(`^o1-mini`), + pricing: o1MiniPricing, + }, + { + pattern: regexp.MustCompile(`^o1`), + pricing: o1Pricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-audio`), + pricing: gpt4oAudioPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o-mini`), + pricing: gpt4oMiniPricing, + }, + { + pattern: regexp.MustCompile(`^gpt-4o`), + pricing: gpt4oPricing, + }, + { + pattern: regexp.MustCompile(`^chatgpt-4o`), + pricing: gpt4oPricing, + }, + } +) + +func getPricing(model string) ModelPricing { + for _, family := range modelFamilies { + if family.pattern.MatchString(model) { + return family.pricing + } + } + return gpt4oPricing +} + +func calculateCost(stats UsageStats, model string) float64 { + pricing := getPricing(model) + + regularInputTokens := stats.InputTokens - stats.CachedTokens + if regularInputTokens < 0 { + regularInputTokens = 0 + } + + cost := (float64(regularInputTokens)*pricing.InputPrice + + float64(stats.OutputTokens)*pricing.OutputPrice + + float64(stats.CachedTokens)*pricing.CachedInputPrice) / 1_000_000 + + return math.Round(cost*100) / 100 +} + +func (u *AggregatedUsage) ToJSON() *AggregatedUsageJSON { + u.mutex.Lock() + defer u.mutex.Unlock() + + result := &AggregatedUsageJSON{ + LastUpdated: u.LastUpdated, + Combinations: make([]CostCombinationJSON, len(u.Combinations)), + Costs: CostsSummaryJSON{ + TotalUSD: 0, + ByUser: make(map[string]float64), + }, + } + + for i, combo := range u.Combinations { + totalCost := calculateCost(combo.Total, combo.Model) + + result.Costs.TotalUSD += totalCost + + comboJSON := CostCombinationJSON{ + Model: combo.Model, + Total: UsageStatsJSON{ + RequestCount: combo.Total.RequestCount, + InputTokens: combo.Total.InputTokens, + OutputTokens: combo.Total.OutputTokens, + CachedTokens: combo.Total.CachedTokens, + CostUSD: totalCost, + }, + ByUser: make(map[string]UsageStatsJSON), + } + + for user, userStats := range combo.ByUser { + userCost := calculateCost(userStats, combo.Model) + result.Costs.ByUser[user] += userCost + + comboJSON.ByUser[user] = UsageStatsJSON{ + RequestCount: userStats.RequestCount, + InputTokens: userStats.InputTokens, + OutputTokens: userStats.OutputTokens, + CachedTokens: userStats.CachedTokens, + CostUSD: userCost, + } + } + + result.Combinations[i] = comboJSON + } + + result.Costs.TotalUSD = math.Round(result.Costs.TotalUSD*100) / 100 + for user, cost := range result.Costs.ByUser { + result.Costs.ByUser[user] = math.Round(cost*100) / 100 + } + + return result +} + +func (u *AggregatedUsage) Load() error { + u.mutex.Lock() + defer u.mutex.Unlock() + + data, err := os.ReadFile(u.filePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + var temp struct { + LastUpdated time.Time `json:"last_updated"` + Combinations []CostCombination `json:"combinations"` + } + + err = json.Unmarshal(data, &temp) + if err != nil { + return err + } + + u.LastUpdated = temp.LastUpdated + u.Combinations = temp.Combinations + + for i := range u.Combinations { + if u.Combinations[i].ByUser == nil { + u.Combinations[i].ByUser = make(map[string]UsageStats) + } + } + + return nil +} + +func (u *AggregatedUsage) Save() error { + jsonData := u.ToJSON() + + data, err := json.MarshalIndent(jsonData, "", " ") + if err != nil { + return err + } + + tmpFile := u.filePath + ".tmp" + err = os.WriteFile(tmpFile, data, 0o644) + if err != nil { + return err + } + defer os.Remove(tmpFile) + err = os.Rename(tmpFile, u.filePath) + if err == nil { + u.saveMutex.Lock() + u.lastSaveTime = time.Now() + u.saveMutex.Unlock() + } + return err +} + +func (u *AggregatedUsage) AddUsage(model string, inputTokens, outputTokens, cachedTokens int64, user string) error { + if model == "" { + return E.New("model cannot be empty") + } + + u.mutex.Lock() + defer u.mutex.Unlock() + + u.LastUpdated = time.Now() + + var combo *CostCombination + for i := range u.Combinations { + if u.Combinations[i].Model == model { + combo = &u.Combinations[i] + break + } + } + + if combo == nil { + newCombo := CostCombination{ + Model: model, + Total: UsageStats{}, + ByUser: make(map[string]UsageStats), + } + u.Combinations = append(u.Combinations, newCombo) + combo = &u.Combinations[len(u.Combinations)-1] + } + + combo.Total.RequestCount++ + combo.Total.InputTokens += inputTokens + combo.Total.OutputTokens += outputTokens + combo.Total.CachedTokens += cachedTokens + + if user != "" { + userStats := combo.ByUser[user] + userStats.RequestCount++ + userStats.InputTokens += inputTokens + userStats.OutputTokens += outputTokens + userStats.CachedTokens += cachedTokens + combo.ByUser[user] = userStats + } + + go u.scheduleSave() + + return nil +} + +func (u *AggregatedUsage) scheduleSave() { + const saveInterval = time.Minute + + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + timeSinceLastSave := time.Since(u.lastSaveTime) + + if timeSinceLastSave >= saveInterval { + go u.saveAsync() + return + } + + if u.pendingSave { + return + } + + u.pendingSave = true + remainingTime := saveInterval - timeSinceLastSave + + u.saveTimer = time.AfterFunc(remainingTime, func() { + u.saveMutex.Lock() + u.pendingSave = false + u.saveMutex.Unlock() + u.saveAsync() + }) +} + +func (u *AggregatedUsage) saveAsync() { + err := u.Save() + if err != nil { + if u.logger != nil { + u.logger.Error("save usage statistics: ", err) + } + } +} + +func (u *AggregatedUsage) cancelPendingSave() { + u.saveMutex.Lock() + defer u.saveMutex.Unlock() + + if u.saveTimer != nil { + u.saveTimer.Stop() + u.saveTimer = nil + } + u.pendingSave = false +} diff --git a/sing-box/service/ocm/service_user.go b/sing-box/service/ocm/service_user.go new file mode 100644 index 0000000000..494b981b9b --- /dev/null +++ b/sing-box/service/ocm/service_user.go @@ -0,0 +1,29 @@ +package ocm + +import ( + "sync" + + "github.com/sagernet/sing-box/option" +) + +type UserManager struct { + accessMutex sync.RWMutex + tokenMap map[string]string +} + +func (m *UserManager) UpdateUsers(users []option.OCMUser) { + m.accessMutex.Lock() + defer m.accessMutex.Unlock() + tokenMap := make(map[string]string, len(users)) + for _, user := range users { + tokenMap[user.Token] = user.Name + } + m.tokenMap = tokenMap +} + +func (m *UserManager) Authenticate(token string) (string, bool) { + m.accessMutex.RLock() + username, found := m.tokenMap[token] + m.accessMutex.RUnlock() + return username, found +} diff --git a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js index 3bde752d82..1a982965cc 100644 --- a/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js +++ b/small/luci-app-fchomo/htdocs/luci-static/resources/view/fchomo/global.js @@ -841,7 +841,22 @@ return view.extend({ _('Routing mode of the traffic enters mihomo via firewall rules.')); so.value('', _('All allowed')); so.value('bypass_cn', _('Bypass CN')); - so.value('routing_gfw', _('Routing GFW')); + if (features.has_dnsmasq_full) + so.value('routing_gfw', _('Routing GFW')); + so.validate = function(section_id, value) { + const mode = this.section.getOption('routing_mode').formvalue(section_id); + let pd = this.section.getUIElement(section_id, 'routing_domain').node.querySelector('input'); + + // Force enabled + if (mode === 'routing_gfw') { + pd.checked = true; + pd.disabled = true; + } else { + pd.removeAttribute('disabled'); + } + + return true; + } so = ss.taboption('routing_control', form.Flag, 'routing_domain', _('Handle domain'), _('Routing mode will be handle domain.') + '
' + diff --git a/small/luci-app-passwall/Makefile b/small/luci-app-passwall/Makefile index c005c43384..02ecc563a7 100644 --- a/small/luci-app-passwall/Makefile +++ b/small/luci-app-passwall/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall -PKG_VERSION:=25.12.13 +PKG_VERSION:=25.12.16 PKG_RELEASE:=1 PKG_PO_VERSION:=$(PKG_VERSION) diff --git a/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js b/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js index 993e88f396..94d9fac567 100644 --- a/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js +++ b/small/luci-app-passwall/htdocs/luci-static/resources/view/passwall/qrcode.min.js @@ -1 +1 @@ -var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file +var QRCode;!function(){function t(t){this.mode=r.MODE_8BIT_BYTE,this.data=t,this.parsedData=[];for(var e=0,o=this.data.length;e65536?(i[0]=240|(1835008&n)>>>18,i[1]=128|(258048&n)>>>12,i[2]=128|(4032&n)>>>6,i[3]=128|63&n):n>2048?(i[0]=224|(61440&n)>>>12,i[1]=128|(4032&n)>>>6,i[2]=128|63&n):n>128?(i[0]=192|(1984&n)>>>6,i[1]=128|63&n):i[0]=n,this.parsedData.push(i)}this.parsedData=Array.prototype.concat.apply([],this.parsedData),this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function e(t,e){this.typeNumber=t,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}t.prototype={getLength:function(t){return this.parsedData.length},write:function(t){for(var e=0,r=this.parsedData.length;e=7&&this.setupTypeNumber(t),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r)},setupPositionProbePattern:function(t,e){for(var r=-1;r<=7;r++)if(!(t+r<=-1||this.moduleCount<=t+r))for(var o=-1;o<=7;o++)e+o<=-1||this.moduleCount<=e+o||(this.modules[t+r][e+o]=0<=r&&r<=6&&(0==o||6==o)||0<=o&&o<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=o&&o<=4)},getBestMaskPattern:function(){for(var t=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var o=g.getLostPoint(this);(0==r||t>o)&&(t=o,e=r)}return e},createMovieClip:function(t,e,r){var o=t.createEmptyMovieClip(e,r);this.make();for(var i=0;i>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=o}for(r=0;r<18;r++){o=!t&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=o}},setupTypeInfo:function(t,e){for(var r=this.errorCorrectLevel<<3|e,o=g.getBCHTypeInfo(r),i=0;i<15;i++){var n=!t&&1==(o>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n}for(i=0;i<15;i++){n=!t&&1==(o>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n}this.modules[this.moduleCount-8][8]=!t},mapData:function(t,e){for(var r=-1,o=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var s=0;s<2;s++)if(null==this.modules[o][a-s]){var h=!1;n>>i&1)),g.getMask(e,o,a-s)&&(h=!h),this.modules[o][a-s]=h,-1==--i&&(n++,i=7)}if((o+=r)<0||this.moduleCount<=o){o-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(t,r,o){for(var i=m.getRSBlocks(t,r),n=new _,a=0;a8*h)throw new Error("code length overflow. ("+n.getLengthInBits()+">"+8*h+")");for(n.getLengthInBits()+4<=8*h&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*h||(n.put(e.PAD0,8),n.getLengthInBits()>=8*h));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(t,e){for(var r=0,o=0,i=0,n=new Array(e.length),a=new Array(e.length),s=0;s=0?d.get(c):0}}var m=0;for(u=0;u=0;)e^=g.G15<=0;)e^=g.G18<>>=1;return e},getPatternPosition:function(t){return g.PATTERN_POSITION_TABLE[t-1]},getMask:function(t,e,r){switch(t){case i:return(e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case s:return(e+r)%3==0;case h:return(Math.floor(e/2)+Math.floor(r/3))%2==0;case l:return e*r%2+e*r%3==0;case u:return(e*r%2+e*r%3)%2==0;case f:return(e*r%3+(e+r)%2)%2==0;default:throw new Error("bad maskPattern:"+t)}},getErrorCorrectPolynomial:function(t){for(var e=new p([1],0),r=0;r5&&(r+=3+n-5)}for(o=0;o=256;)t-=255;return d.EXP_TABLE[t]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},c=0;c<8;c++)d.EXP_TABLE[c]=1<>>7-t%8&1)},put:function(t,e){for(var r=0;r>>e-r-1&1))},getLengthInBits:function(){return this.length},putBit:function(t){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),t&&(this.buffer[e]|=128>>>this.length%8),this.length++}};var v=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];function C(){var t=!1,e=navigator.userAgent;if(/android/i.test(e)){t=!0;var r=e.toString().match(/android ([0-9]\.[0-9])/i);r&&r[1]&&(t=parseFloat(r[1]))}return t}var w=function(){var t=function(t,e){this._el=t,this._htOption=e};return t.prototype.draw=function(t){var e=this._htOption,r=this._el,o=t.getModuleCount();Math.floor(e.width/o),Math.floor(e.height/o);function i(t,e){var r=document.createElementNS("http://www.w3.org/2000/svg",t);for(var o in e)e.hasOwnProperty(o)&&r.setAttribute(o,e[o]);return r}this.clear();var n=i("svg",{viewBox:"0 0 "+String(o)+" "+String(o),width:"100%",height:"100%",fill:e.colorLight});n.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),r.appendChild(n),n.appendChild(i("rect",{fill:e.colorLight,width:"100%",height:"100%"})),n.appendChild(i("rect",{fill:e.colorDark,width:"1",height:"1",id:"template"}));for(var a=0;a'],s=0;s");for(var h=0;h');a.push("")}a.push(""),r.innerHTML=a.join("");var l=r.childNodes[0],u=(e.width-l.offsetWidth)/2,f=(e.height-l.offsetHeight)/2;u>0&&f>0&&(l.style.margin=f+"px "+u+"px")},t.prototype.clear=function(){this._el.innerHTML=""},t}():function(){function t(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}if(this._android&&this._android<=2.1){var e=1/window.devicePixelRatio,r=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(t,o,i,n,a,s,h,l,u){if("nodeName"in t&&/img/i.test(t.nodeName))for(var f=arguments.length-1;f>=1;f--)arguments[f]=arguments[f]*e;else void 0===l&&(arguments[1]*=e,arguments[2]*=e,arguments[3]*=e,arguments[4]*=e);r.apply(this,arguments)}}var o=function(t,e){this._bIsPainted=!1,this._android=C(),this._htOption=e,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=e.width,this._elCanvas.height=e.height,t.appendChild(this._elCanvas),this._el=t,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.alt="Scan me!",this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return o.prototype.draw=function(t){var e=this._elImage,r=this._oContext,o=this._htOption,i=t.getModuleCount(),n=o.width/i,a=o.height/i,s=Math.round(n),h=Math.round(a);e.style.display="none",this.clear();for(var l=0;lv.length)throw new Error("Too long data");return r}(QRCode=function(t,e){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:o.H},"string"==typeof e&&(e={text:e}),e)for(var r in e)this._htOption[r]=e[r];"string"==typeof t&&(t=document.getElementById(t)),this._htOption.useSVG&&(D=w),this._android=C(),this._el=t,this._oQRCode=null,this._oDrawing=new D(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)}).prototype.makeCode=function(t){this._oQRCode=new e(A(t,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(t),this._oQRCode.make(),this._el.title=t,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=o}(),"undefined"!=typeof module&&(module.exports=QRCode); diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua index ac109cd95f..e822fb6d65 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/acl_config.lua @@ -308,6 +308,17 @@ o = s:option(DummyValue, "switch_mode", " ") o.template = appname .. "/global/proxy" o:depends({ _tcp_node_bool = "1" }) +-- Node → DNS Depends Settings +o = s:option(DummyValue, "_node_sel_shunt", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ tcp_node = "__always__" }) + +o = s:option(DummyValue, "_node_sel_other", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ _node_sel_shunt = "1", ['!reverse'] = true }) + ---- DNS o = s:option(ListValue, "dns_shunt", "DNS " .. translate("Shunt")) o.default = "chinadns-ng" @@ -333,6 +344,7 @@ end if has_xray then o:value("xray", "Xray") end +o:depends({ _tcp_node_bool = "1", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["tcp_node"] local id_val = f and f:formvalue(section) or "" @@ -362,7 +374,9 @@ o:value("tcp", "TCP") o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, ["tcp+doh"] = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "xray" then @@ -377,7 +391,9 @@ o:value("tcp", "TCP") o:value("doh", "DoH") o:depends("dns_mode", "sing-box") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, doh = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "sing-box" then @@ -444,6 +460,7 @@ o = s:option(Value, "remote_dns_client_ip", translate("EDNS Client Subnet")) o.datatype = "ipaddr" o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) +o:depends({_node_sel_shunt = "1"}) o = s:option(ListValue, "chinadns_ng_default_tag", translate("Default DNS")) o.default = "none" @@ -485,6 +502,7 @@ for k, v in pairs(nodes_table) do udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") s.fields["xray_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) end if v.type == "sing-box" and has_singbox then tcp:value(v.id, v["remark"]) @@ -493,17 +511,13 @@ for k, v in pairs(nodes_table) do udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") s.fields["singbox_dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) - end - if has_xray or has_singbox then - s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) end else tcp:value(v.id, v["remark"]) tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - s.fields["dns_mode"]:depends({ _tcp_node_bool = "1", tcp_node = v.id }) end end diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua index 30665ed6dd..de37538e08 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/global.lua @@ -309,6 +309,18 @@ o = s:taboption("Main", Flag, "tcp_node_socks_bind_local", translate("TCP Node") o.default = "1" o:depends({ tcp_node = "", ["!reverse"] = true }) +-- Node → DNS Depends Settings +o = s:taboption("Main", DummyValue, "_node_sel_shunt", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ tcp_node = "__always__" }) + +o = s:taboption("Main", DummyValue, "_node_sel_other", "") +o.template = appname .. "/cbi/hidevalue" +o.value = "1" +o:depends({ _node_sel_shunt = "1", ['!reverse'] = true }) + +-- [[ DNS Settings ]]-- s:tab("DNS", translate("DNS")) o = s:taboption("DNS", ListValue, "dns_shunt", "DNS " .. translate("Shunt")) @@ -388,8 +400,8 @@ end if has_xray then o:value("xray", "Xray") end -o:depends({ dns_shunt = "chinadns-ng", tcp_node = "" }) -o:depends({ dns_shunt = "dnsmasq", tcp_node = "" }) +o:depends({ dns_shunt = "chinadns-ng", _node_sel_other = "1" }) +o:depends({ dns_shunt = "dnsmasq", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["smartdns_dns_mode"] if f and f:formvalue(section) then @@ -408,7 +420,7 @@ if api.is_finded("smartdns") then if has_xray then o:value("xray", "Xray") end - o:depends({ dns_shunt = "smartdns", tcp_node = "" }) + o:depends({ dns_shunt = "smartdns", _node_sel_other = "1" }) o.remove = function(self, section) local f = s.fields["dns_mode"] if f and f:formvalue(section) then @@ -468,7 +480,9 @@ o:value("tcp+doh", "TCP + DoH (" .. translate("A/AAAA type") .. ")") o:depends("dns_mode", "xray") o:depends("smartdns_dns_mode", "xray") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, ["tcp+doh"] = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "xray" or s.fields["smartdns_dns_mode"]:formvalue(section) == "xray" then @@ -484,7 +498,9 @@ o:value("doh", "DoH") o:depends("dns_mode", "sing-box") o:depends("smartdns_dns_mode", "sing-box") o.cfgvalue = function(self, section) - return m:get(section, "v2ray_dns_mode") + local v = m:get(section, "v2ray_dns_mode") + local key = { udp = true, tcp = true, doh = true } + return (v and key[v]) and v or self.default end o.write = function(self, section, value) if s.fields["dns_mode"]:formvalue(section) == "sing-box" or s.fields["smartdns_dns_mode"]:formvalue(section) == "sing-box" then @@ -548,6 +564,7 @@ o.datatype = "ipaddr" o:depends({dns_mode = "sing-box"}) o:depends({dns_mode = "xray"}) o:depends("dns_shunt", "smartdns") +o:depends("_node_sel_shunt", "1") o = s:taboption("DNS", Flag, "remote_fakedns", "FakeDNS", translate("Use FakeDNS work in the shunt domain that proxy.")) o.default = "0" @@ -557,6 +574,7 @@ o:depends({smartdns_dns_mode = "sing-box", dns_shunt = "smartdns"}) o:depends({dns_mode = "xray", dns_shunt = "dnsmasq"}) o:depends({dns_mode = "xray", dns_shunt = "chinadns-ng"}) o:depends({smartdns_dns_mode = "xray", dns_shunt = "smartdns"}) +o:depends("_node_sel_shunt", "1") o.validate = function(self, value, t) if value and value == "1" then local _dns_mode = s.fields["dns_mode"]:formvalue(t) @@ -810,22 +828,15 @@ for k, v in pairs(nodes_table) do udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", tcp_node = v.id }) - s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", tcp_node = v.id }) - s.fields["remote_dns_client_ip"]:depends({ tcp_node = v.id }) - s.fields["remote_fakedns"]:depends({ tcp_node = v.id }) + s.fields["_node_sel_shunt"]:depends({ tcp_node = v.id }) + s.fields["xray_dns_mode"]:depends({ [v.id .. "-type"] = "Xray", _node_sel_shunt = "1" }) + s.fields["singbox_dns_mode"]:depends({ [v.id .. "-type"] = "sing-box", _node_sel_shunt = "1" }) end else tcp:value(v.id, v["remark"]) tcp.group[#tcp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") udp:value(v.id, v["remark"]) udp.group[#udp.group+1] = (v.group and v.group ~= "") and v.group or translate("default") - - s.fields["dns_mode"]:depends({ dns_shunt = "chinadns-ng", tcp_node = v.id }) - s.fields["dns_mode"]:depends({ dns_shunt = "dnsmasq", tcp_node = v.id }) - if api.is_finded("smartdns") then - s.fields["smartdns_dns_mode"]:depends({ dns_shunt = "smartdns", tcp_node = v.id }) - end end if v.type == "Socks" then if has_singbox or has_xray then diff --git a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua index 4c1aae949a..92145d3993 100644 --- a/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua +++ b/small/luci-app-passwall/luasrc/model/cbi/passwall/client/node_subscribe.lua @@ -147,7 +147,7 @@ o = s:option(DummyValue, "_stop", translate("Delete All Subscribe Node")) o.rawhtml = true function o.cfgvalue(self, section) return string.format( - [[]], + [[]], translate("Delete All Subscribe Node")) end @@ -155,7 +155,7 @@ o = s:option(DummyValue, "_update", translate("Manual subscription All")) o.rawhtml = true o.cfgvalue = function(self, section) return string.format([[ - ]], + ]], translate("Manual subscription All")) end @@ -231,7 +231,7 @@ o.rawhtml = true function o.cfgvalue(self, section) local remark = m:get(section, "remark") or "" return string.format( - [[]], + [[]], remark, translate("Delete the subscribed node")) end @@ -239,7 +239,7 @@ o = s:option(DummyValue, "_update", translate("Manual subscription")) o.rawhtml = true o.cfgvalue = function(self, section) return string.format([[ - ]], + ]], section, translate("Manual subscription")) end diff --git a/small/luci-app-passwall/luasrc/passwall/util_xray.lua b/small/luci-app-passwall/luasrc/passwall/util_xray.lua index 7b7be7cee9..63dc72556e 100644 --- a/small/luci-app-passwall/luasrc/passwall/util_xray.lua +++ b/small/luci-app-passwall/luasrc/passwall/util_xray.lua @@ -201,8 +201,8 @@ function gen_outbound(flag, node, tag, proxy_table) } or nil, wsSettings = (node.transport == "ws") and { path = node.ws_path or "/", - headers = (node.ws_host or node.ws_user_agent) and { - Host = node.ws_host, + host = node.ws_host, + headers = node.ws_user_agent and { ["User-Agent"] = node.ws_user_agent } or nil, maxEarlyData = tonumber(node.ws_maxEarlyData) or nil, diff --git a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm index 685ea00918..1bcf0ab744 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue.htm @@ -139,7 +139,7 @@ window.lv_dropdown_data["<%=cbid%>"] = <%=json.stringify(dropdown_data)%>; lv_openPanel(cbid,display,panel,listContainer,hiddenSelect,searchInput); } }); - lv_adaptiveStyle(cbid); // copy select styles + lv_registerAdaptive(cbid); })(); //]]> diff --git a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm index 922db7279b..21538a2ee0 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/cbi/nodes_listvalue_com.htm @@ -244,9 +244,11 @@ local appname = api.appname return lv_rgbToHex(r, g, b); } + // copy select styles function lv_adaptiveStyle(cbid) { const display = document.getElementById(cbid + ".display"); const hiddenSelect = document.getElementById(cbid); + const panel = document.getElementById(cbid + ".panel"); if (hiddenSelect && display) { const elOption = hiddenSelect.getElementsByTagName("option")[0] const styleSelect = window.getComputedStyle(hiddenSelect) @@ -466,7 +468,13 @@ local appname = api.appname } panel.style.left = rect.left + "px"; panel.style.top = top + "px"; - panel.style.minWidth = rect.width + "px"; + const panelRect = panel.getBoundingClientRect(); + const displayWidth = rect.width; + const remainingWidth = window.innerWidth - panelRect.left - 12; + const maxWidth = Math.max(displayWidth, Math.floor(remainingWidth)); + panel.style.maxWidth = maxWidth + "px"; + panel.style.minWidth = displayWidth + "px"; + panel.style.width = "auto"; panel.style.visibility = ""; } @@ -601,22 +609,24 @@ local appname = api.appname if(!li || li === listContainer) return; const key = li.getAttribute('data-key') || ""; const text = li.querySelector(".lv-item-label")?.textContent || li.textContent || key; - if (key !== hiddenSelect.value) { - //动态改值 + + const changed = key !== hiddenSelect.value; + if (changed) { + //改值 hiddenSelect.options[0].value = key; + hiddenSelect.options[0].text = key; hiddenSelect.value = key; labelSpan.textContent = text; labelSpan.title = text; - setTimeout(() => { - try { - const evt = new Event('change', { bubbles: true }); - hiddenSelect.dispatchEvent(evt); - } catch(e){} - }, 0); lv_highlightSelectedItem(listContainer, hiddenSelect); lv_updateGroupCounts(cbid, listContainer, hiddenSelect, searchInput); } lv_closePanel(cbid,panel,listContainer,hiddenSelect,searchInput); + if (changed) { + setTimeout(() => { + hiddenSelect.dispatchEvent(new Event('change', { bubbles: true })); + }, 0); + } }); // 搜索功能 @@ -635,27 +645,22 @@ local appname = api.appname } }); }); - - // 设置宽度 - panel.style.maxWidth = lv_getPanelMaxWidth(display); - panel.style.minWidth = display.getBoundingClientRect().width + "px"; - panel.style.width = "auto"; } - function lv_getPanelMaxWidth(display) { - if (!display) return 0; - const rectW = el => el && el.getBoundingClientRect().width; - const fallback = rectW(display) || 0; - const cbiValue = display.closest(".cbi-value"); - if (cbiValue) { - const valueW = rectW(cbiValue); - const titleW = rectW(cbiValue.querySelector(".cbi-value-title")); - if (valueW) { - return Math.floor(titleW ? valueW - titleW : valueW); - } - } - const fieldW = rectW(display.closest(".cbi-value-field")); - return Math.floor(fieldW || fallback); + const lv_adaptiveControls = new Set(); + function lv_registerAdaptive(cbid) { + lv_adaptiveControls.add(cbid); + lv_adaptiveStyle(cbid); } + let lv_adaptiveTicking = false; + window.addEventListener("resize", () => { + if (!lv_adaptiveTicking) { + lv_adaptiveTicking = true; + requestAnimationFrame(() => { + lv_adaptiveControls.forEach(cbid => lv_adaptiveStyle(cbid)); + lv_adaptiveTicking = false; + }); + } + }); //]]> diff --git a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm index 9a25a3ee24..91f5f410d7 100644 --- a/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm +++ b/small/luci-app-passwall/luasrc/view/passwall/node_list/link_share_man.htm @@ -1657,7 +1657,9 @@ local hysteria2_type = get_core("hysteria2_type", {{has_hysteria2,"hysteria2"},{ +
+
- <%+cbi/valuefooter%> diff --git a/small/luci-app-passwall/po/zh-cn/passwall.po b/small/luci-app-passwall/po/zh-cn/passwall.po index 04d5303a6f..c04c6d1cb0 100644 --- a/small/luci-app-passwall/po/zh-cn/passwall.po +++ b/small/luci-app-passwall/po/zh-cn/passwall.po @@ -1121,7 +1121,7 @@ msgid "Are you sure you want to delete all subscribed nodes?" msgstr "您确定要删除所有已订阅的节点吗?" msgid "Manual subscription All" -msgstr "手动订阅全部" +msgstr "手动订阅全部链接" msgid "This remark already exists, please change a new remark." msgstr "此备注已存在,请改一个新的备注。" diff --git a/small/naiveproxy/Makefile b/small/naiveproxy/Makefile index 7bdc17150f..9818157faf 100644 --- a/small/naiveproxy/Makefile +++ b/small/naiveproxy/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=naiveproxy -PKG_VERSION:=140.0.7339.123-3 +PKG_VERSION:=143.0.7499.109-1 PKG_RELEASE:=1 # intel 80386 & riscv64 & cortex-a76 @@ -30,7 +30,7 @@ else ifeq ($(ARCH_PACKAGES),arm_arm1176jzf-s_vfp) else ifeq ($(ARCH_PACKAGES),arm_arm926ej-s) PKG_HASH:=27b28beaa032165e9b93d423353ced3cfe594c71353fd9d109699f330b785732 else ifeq ($(ARCH_PACKAGES),arm_cortex-a15_neon-vfpv4) - PKG_HASH:=14d4929d2b417baa7f31df114eefa2d0a1ae4bc255cf1b3a037558bd67b77faf + PKG_HASH:=5e2dd76559c1eef027471ddadb646a5aaff7f4d2b337bf9aa609bc38dde4247b else ifeq ($(ARCH_PACKAGES),arm_cortex-a5_vfpv4) PKG_HASH:=49a9c7eecab54155a31c3bfae71cf5193a881189715c7b4d29ce50a8c5c759d6 else ifeq ($(ARCH_PACKAGES),arm_cortex-a7) @@ -46,9 +46,9 @@ else ifeq ($(ARCH_PACKAGES),arm_cortex-a9) else ifeq ($(ARCH_PACKAGES),arm_cortex-a9_neon) PKG_HASH:=ab5a699ef830d65344f014547ae6ce73e0f25103ad02d3e80a5d441e19c7e21e else ifeq ($(ARCH_PACKAGES),arm_cortex-a9_vfpv3-d16) - PKG_HASH:=efb7650c31c862564f39be269d67fca4e1a43bf356a5d4c1c158cc83e7c378e2 + PKG_HASH:=294b676fb05f7042e16ca5871200915f3bcc73a6650c99f17857d651af6bb920 else ifeq ($(ARCH_PACKAGES),arm_mpcore) - PKG_HASH:=1e0fa7d2f283807a019c85ddb58f99b711bcf86eb4ac759562b221d998091093 + PKG_HASH:=0498d19350ae3e6c064913758199c53014254c4765a106eac1913fd7642eff41 else ifeq ($(ARCH_PACKAGES),arm_xscale) PKG_HASH:=40175f13102597b88f92c664bf5c595ab69b248a5b64229a6aba388be77345b4 else ifeq ($(ARCH_PACKAGES),mipsel_24kc) @@ -60,7 +60,7 @@ else ifeq ($(ARCH_PACKAGES),riscv64) else ifeq ($(ARCH_PACKAGES),x86) PKG_HASH:=a3e8aad951330b995273176cc310038971f4f3fff56ab047d5e60e3a0eb67d14 else ifeq ($(ARCH_PACKAGES),x86_64) - PKG_HASH:=d6c39befccb1f3ad54ffa11c5ae8ad11a90151998eeaae6b1a73cc0702f24966 + PKG_HASH:=7387ce2af58463735d839f45d8564626cdc1baea3e053c2479ba5fe0f2fa721f else PKG_HASH:=dummy endif diff --git a/small/v2ray-geodata/Makefile b/small/v2ray-geodata/Makefile index f114d64182..6b282d594a 100644 --- a/small/v2ray-geodata/Makefile +++ b/small/v2ray-geodata/Makefile @@ -21,13 +21,13 @@ define Download/geoip HASH:=6878dbacfb1fcb1ee022f63ed6934bcefc95a3c4ba10c88f1131fb88dbf7c337 endef -GEOSITE_VER:=20251215045505 +GEOSITE_VER:=20251216015506 GEOSITE_FILE:=dlc.dat.$(GEOSITE_VER) define Download/geosite URL:=https://github.com/v2fly/domain-list-community/releases/download/$(GEOSITE_VER)/ URL_FILE:=dlc.dat FILE:=$(GEOSITE_FILE) - HASH:=8ac5d1021786c3bcd1ee96219421a8d312cfa5ecb81596f8f2dcf14ef4cd0b82 + HASH:=a0ed32edde7bc08e5531cee979b4da31ef966347162cd351afe836829df6e5c6 endef GEOSITE_IRAN_VER:=202512150045 diff --git a/v2rayn/.github/workflows/build-linux.yml b/v2rayn/.github/workflows/build-linux.yml index d5c40e576f..7ac333f2e6 100644 --- a/v2rayn/.github/workflows/build-linux.yml +++ b/v2rayn/.github/workflows/build-linux.yml @@ -50,7 +50,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r linux-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o "$OutputPathArm64" - name: Upload build artifacts - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: v2rayN-linux path: | @@ -116,7 +116,7 @@ jobs: fetch-depth: '0' - name: Restore build artifacts - uses: actions/download-artifact@v6 + uses: actions/download-artifact@v7 with: name: v2rayN-linux path: ${{ github.workspace }}/v2rayN/Release @@ -137,7 +137,7 @@ jobs: ls -R "$GITHUB_WORKSPACE/dist/rpm" || true - name: Upload RPM artifacts - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: v2rayN-rpm path: dist/rpm/**/*.rpm diff --git a/v2rayn/.github/workflows/build-osx.yml b/v2rayn/.github/workflows/build-osx.yml index 2143779638..ee01e92ca8 100644 --- a/v2rayn/.github/workflows/build-osx.yml +++ b/v2rayn/.github/workflows/build-osx.yml @@ -45,7 +45,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r osx-arm64 -p:SelfContained=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: v2rayN-macos path: | diff --git a/v2rayn/.github/workflows/build-windows-desktop.yml b/v2rayn/.github/workflows/build-windows-desktop.yml index 5da5f3dbdf..b2c3f7943d 100644 --- a/v2rayn/.github/workflows/build-windows-desktop.yml +++ b/v2rayn/.github/workflows/build-windows-desktop.yml @@ -45,7 +45,7 @@ jobs: dotnet publish ./AmazTool/AmazTool.csproj -c Release -r win-arm64 -p:SelfContained=true -p:EnableWindowsTargeting=true -p:PublishTrimmed=true -o $OutputPathArm64 - name: Upload build artifacts - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: v2rayN-windows-desktop path: | diff --git a/v2rayn/.github/workflows/build-windows.yml b/v2rayn/.github/workflows/build-windows.yml index 82542a6bf1..c4c7e60dc6 100644 --- a/v2rayn/.github/workflows/build-windows.yml +++ b/v2rayn/.github/workflows/build-windows.yml @@ -46,7 +46,7 @@ jobs: - name: Upload build artifacts - uses: actions/upload-artifact@v5.0.0 + uses: actions/upload-artifact@v6.0.0 with: name: v2rayN-windows path: | diff --git a/yt-dlp/test/helper.py b/yt-dlp/test/helper.py index e96835fc46..5a937d9617 100644 --- a/yt-dlp/test/helper.py +++ b/yt-dlp/test/helper.py @@ -261,7 +261,7 @@ def sanitize_got_info_dict(got_dict): def expect_info_dict(self, got_dict, expected_dict): ALLOWED_KEYS_SORT_ORDER = ( # NB: Keep in sync with the docstring of extractor/common.py - 'id', 'ext', 'direct', 'display_id', 'title', 'alt_title', 'description', 'media_type', + 'ie_key', 'url', 'id', 'ext', 'direct', 'display_id', 'title', 'alt_title', 'description', 'media_type', 'uploader', 'uploader_id', 'uploader_url', 'channel', 'channel_id', 'channel_url', 'channel_is_verified', 'channel_follower_count', 'comment_count', 'view_count', 'concurrent_view_count', 'like_count', 'dislike_count', 'repost_count', 'average_rating', 'age_limit', 'duration', 'thumbnail', 'heatmap', diff --git a/yt-dlp/yt_dlp/extractor/youtube/_tab.py b/yt-dlp/yt_dlp/extractor/youtube/_tab.py index f991d99759..450b4aa51d 100644 --- a/yt-dlp/yt_dlp/extractor/youtube/_tab.py +++ b/yt-dlp/yt_dlp/extractor/youtube/_tab.py @@ -382,7 +382,8 @@ class YoutubeTabBaseInfoExtractor(YoutubeBaseInfoExtractor): ('accessibilityText', {lambda x: re.fullmatch(r'(.+), (?:[\d,.]+(?:[KM]| million)?|No) views? - play Short', x)}, 1)), any), 'view_count': ('overlayMetadata', 'secondaryText', 'content', {parse_count}), }), - thumbnails=self._extract_thumbnails(renderer, 'thumbnail', final_key='sources')) + thumbnails=self._extract_thumbnails( + renderer, ('thumbnailViewModel', 'thumbnailViewModel', 'image'), final_key='sources')) return def _video_entry(self, video_renderer): @@ -1585,7 +1586,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): 'playlist_count': 50, 'expected_warnings': ['YouTube Music is not directly supported'], }, { - # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test 'note': 'unlisted single video playlist', 'url': 'https://www.youtube.com/playlist?list=PLt5yu3-wZAlQLfIN0MMgp0wVV6MP3bM4_', 'info_dict': { @@ -1885,8 +1885,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): 'playlist_mincount': 30, }, { # Shorts url result in shorts tab - # TODO: Fix channel id extraction - # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test 'url': 'https://www.youtube.com/channel/UCiu-3thuViMebBjw_5nWYrA/shorts', 'info_dict': { 'id': 'UCiu-3thuViMebBjw_5nWYrA', @@ -1915,7 +1913,6 @@ class YoutubeTabIE(YoutubeTabBaseInfoExtractor): 'params': {'extract_flat': True}, }, { # Live video status should be extracted - # TODO: fix test suite, 208163447408c78673b08c172beafe5c310fb167 broke this test 'url': 'https://www.youtube.com/channel/UCQvWX73GQygcwXOTSf_VDVg/live', 'info_dict': { 'id': 'UCQvWX73GQygcwXOTSf_VDVg',