new: Big Change, Add support for Extensions 😍

This commit is contained in:
hiddify
2024-09-28 20:31:38 +02:00
parent 1f485e1193
commit 3a508b7929
44 changed files with 8946 additions and 619 deletions

View File

@@ -6,11 +6,14 @@ import (
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
"github.com/sagernet/sing-box/experimental/libbox"
"google.golang.org/grpc"
)
var systemInfoObserver = NewObserver[pb.SystemInfo](10)
var outboundsInfoObserver = NewObserver[pb.OutboundGroupList](10)
var mainOutboundsInfoObserver = NewObserver[pb.OutboundGroupList](10)
var (
systemInfoObserver = NewObserver[pb.SystemInfo](10)
outboundsInfoObserver = NewObserver[pb.OutboundGroupList](10)
mainOutboundsInfoObserver = NewObserver[pb.OutboundGroupList](10)
)
var (
statusClient *libbox.CommandClient
@@ -18,13 +21,13 @@ var (
groupInfoOnlyClient *libbox.CommandClient
)
func (s *CoreService) GetSystemInfo(stream pb.Core_GetSystemInfoServer) error {
func (s *CoreService) GetSystemInfo(req *pb.Empty, stream grpc.ServerStreamingServer[pb.SystemInfo]) error {
if statusClient == nil {
statusClient = libbox.NewCommandClient(
&CommandClientHandler{},
&libbox.CommandClientOptions{
Command: libbox.CommandStatus,
StatusInterval: 1000000000, //1000ms debounce
StatusInterval: 1000000000, // 1000ms debounce
},
)
@@ -35,18 +38,14 @@ func (s *CoreService) GetSystemInfo(stream pb.Core_GetSystemInfoServer) error {
statusClient.Connect()
}
sub, _, _ := systemInfoObserver.Subscribe()
stopch := make(chan int)
go func() {
stream.Recv()
close(stopch)
}()
sub, done, _ := systemInfoObserver.Subscribe()
for {
select {
case <-stream.Context().Done():
break
case <-stopch:
break
return nil
case <-done:
return nil
case info := <-sub:
stream.Send(&info)
case <-time.After(1000 * time.Millisecond):
@@ -54,13 +53,13 @@ func (s *CoreService) GetSystemInfo(stream pb.Core_GetSystemInfoServer) error {
}
}
func (s *CoreService) OutboundsInfo(stream pb.Core_OutboundsInfoServer) error {
func (s *CoreService) OutboundsInfo(req *pb.Empty, stream grpc.ServerStreamingServer[pb.OutboundGroupList]) error {
if groupClient == nil {
groupClient = libbox.NewCommandClient(
&CommandClientHandler{},
&libbox.CommandClientOptions{
Command: libbox.CommandGroup,
StatusInterval: 500000000, //500ms debounce
StatusInterval: 500000000, // 500ms debounce
},
)
@@ -71,18 +70,14 @@ func (s *CoreService) OutboundsInfo(stream pb.Core_OutboundsInfoServer) error {
groupClient.Connect()
}
sub, _, _ := outboundsInfoObserver.Subscribe()
stopch := make(chan int)
go func() {
stream.Recv()
close(stopch)
}()
sub, done, _ := outboundsInfoObserver.Subscribe()
for {
select {
case <-stream.Context().Done():
break
case <-stopch:
break
return nil
case <-done:
return nil
case info := <-sub:
stream.Send(&info)
case <-time.After(500 * time.Millisecond):
@@ -90,13 +85,13 @@ func (s *CoreService) OutboundsInfo(stream pb.Core_OutboundsInfoServer) error {
}
}
func (s *CoreService) MainOutboundsInfo(stream pb.Core_MainOutboundsInfoServer) error {
func (s *CoreService) MainOutboundsInfo(req *pb.Empty, stream grpc.ServerStreamingServer[pb.OutboundGroupList]) error {
if groupInfoOnlyClient == nil {
groupInfoOnlyClient = libbox.NewCommandClient(
&CommandClientHandler{},
&libbox.CommandClientOptions{
Command: libbox.CommandGroupInfoOnly,
StatusInterval: 500000000, //500ms debounce
StatusInterval: 500000000, // 500ms debounce
},
)
@@ -107,18 +102,14 @@ func (s *CoreService) MainOutboundsInfo(stream pb.Core_MainOutboundsInfoServer)
groupInfoOnlyClient.Connect()
}
sub, _, _ := mainOutboundsInfoObserver.Subscribe()
stopch := make(chan int)
go func() {
stream.Recv()
close(stopch)
}()
sub, stopch, _ := mainOutboundsInfoObserver.Subscribe()
for {
select {
case <-stream.Context().Done():
break
return nil
case <-stopch:
break
return nil
case info := <-sub:
stream.Send(&info)
case <-time.After(500 * time.Millisecond):
@@ -129,9 +120,9 @@ func (s *CoreService) MainOutboundsInfo(stream pb.Core_MainOutboundsInfoServer)
func (s *CoreService) SelectOutbound(ctx context.Context, in *pb.SelectOutboundRequest) (*pb.Response, error) {
return SelectOutbound(in)
}
func SelectOutbound(in *pb.SelectOutboundRequest) (*pb.Response, error) {
err := libbox.NewStandaloneCommandClient().SelectOutbound(in.GroupTag, in.OutboundTag)
if err != nil {
return &pb.Response{
ResponseCode: pb.ResponseCode_FAILED,
@@ -148,9 +139,9 @@ func SelectOutbound(in *pb.SelectOutboundRequest) (*pb.Response, error) {
func (s *CoreService) UrlTest(ctx context.Context, in *pb.UrlTestRequest) (*pb.Response, error) {
return UrlTest(in)
}
func UrlTest(in *pb.UrlTestRequest) (*pb.Response, error) {
err := libbox.NewStandaloneCommandClient().URLTest(in.GroupTag)
if err != nil {
return &pb.Response{
ResponseCode: pb.ResponseCode_FAILED,
@@ -162,5 +153,4 @@ func UrlTest(in *pb.UrlTestRequest) (*pb.Response, error) {
ResponseCode: pb.ResponseCode_OK,
Message: "",
}, nil
}

View File

@@ -3,16 +3,18 @@ package v2
import (
"encoding/json"
"fmt"
"time"
"github.com/hiddify/hiddify-core/bridge"
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
"google.golang.org/grpc"
)
var coreInfoObserver = NewObserver[pb.CoreInfoResponse](10)
var CoreState = pb.CoreState_STOPPED
var (
coreInfoObserver = *NewObserver[*pb.CoreInfoResponse](1)
CoreState = pb.CoreState_STOPPED
)
func SetCoreStatus(state pb.CoreState, msgType pb.MessageType, message string) pb.CoreInfoResponse {
func SetCoreStatus(state pb.CoreState, msgType pb.MessageType, message string) *pb.CoreInfoResponse {
msg := fmt.Sprintf("%s: %s %s", state.String(), msgType.String(), message)
if msgType == pb.MessageType_EMPTY {
msg = fmt.Sprintf("%s: %s", state.String(), message)
@@ -24,32 +26,32 @@ func SetCoreStatus(state pb.CoreState, msgType pb.MessageType, message string) p
MessageType: msgType,
Message: message,
}
coreInfoObserver.Emit(info)
coreInfoObserver.Emit(&info)
if useFlutterBridge {
msg, _ := json.Marshal(StatusMessage{Status: convert2OldState(CoreState)})
bridge.SendStringToPort(statusPropagationPort, string(msg))
}
return info
return &info
}
func (s *CoreService) CoreInfoListener(stream pb.Core_CoreInfoListenerServer) error {
coreSub, _, _ := coreInfoObserver.Subscribe()
func (s *CoreService) CoreInfoListener(req *pb.Empty, stream grpc.ServerStreamingServer[pb.CoreInfoResponse]) error {
coreSub, done, err := coreInfoObserver.Subscribe()
if err != nil {
return err
}
defer coreInfoObserver.UnSubscribe(coreSub)
stopch := make(chan int)
go func() {
stream.Recv()
close(stopch)
}()
for {
select {
case <-stream.Context().Done():
return nil
case <-stopch:
case <-done:
return nil
case info := <-coreSub:
stream.Send(&info)
case <-time.After(500 * time.Millisecond):
stream.Send(info)
// case <-time.After(500 * time.Millisecond):
// info := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_EMPTY, "")
// stream.Send(info)
}
}
}

View File

@@ -83,7 +83,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_READING_CONFIG, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
content = string(fileContent)
}
@@ -96,7 +96,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_PARSING_CONFIG, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
if !in.EnableRawConfig {
Log(pb.LogLevel_DEBUG, pb.LogType_CORE, "Building config")
@@ -105,7 +105,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_BUILDING_CONFIG, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
parsedContent = *parsedContent_tmp
}
@@ -122,7 +122,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_COMMAND_SERVER, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
}
@@ -132,7 +132,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_CREATE_SERVICE, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
Log(pb.LogLevel_DEBUG, pb.LogType_CORE, "Service.. started")
if in.DelayStart {
@@ -144,7 +144,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_SERVICE, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
return &resp, err
return resp, err
}
Box = instance
if in.EnableOldCommandServer {
@@ -152,7 +152,7 @@ func StartService(in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
}
resp := SetCoreStatus(pb.CoreState_STARTED, pb.MessageType_EMPTY, "")
return &resp, nil
return resp, nil
}
func (s *CoreService) Parse(ctx context.Context, in *pb.ParseRequest) (*pb.ParseResponse, error) {
@@ -199,13 +199,13 @@ func Parse(in *pb.ParseRequest) (*pb.ParseResponse, error) {
}, err
}
func (s *CoreService) ChangeHiddifyOptions(ctx context.Context, in *pb.ChangeHiddifyOptionsRequest) (*pb.CoreInfoResponse, error) {
return ChangeHiddifyOptions(in)
func (s *CoreService) ChangeHiddifySettings(ctx context.Context, in *pb.ChangeHiddifySettingsRequest) (*pb.CoreInfoResponse, error) {
return ChangeHiddifySettings(in)
}
func ChangeHiddifyOptions(in *pb.ChangeHiddifyOptionsRequest) (*pb.CoreInfoResponse, error) {
HiddifyOptions = &config.HiddifyOptions{}
err := json.Unmarshal([]byte(in.HiddifyOptionsJson), HiddifyOptions)
func ChangeHiddifySettings(in *pb.ChangeHiddifySettingsRequest) (*pb.CoreInfoResponse, error) {
HiddifyOptions = config.DefaultHiddifyOptions()
err := json.Unmarshal([]byte(in.HiddifySettingsJson), HiddifyOptions)
if err != nil {
return nil, err
}
@@ -314,7 +314,7 @@ func Stop() (*pb.CoreInfoResponse, error) {
oldCommandServer = nil
}
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_EMPTY, "")
return &resp, nil
return resp, nil
}
func (s *CoreService) Restart(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) {

View File

@@ -33,6 +33,11 @@ func StartGrpcServer(listenAddressG string, service string) (*grpc.Server, error
}
s := grpc.NewServer()
if service == "core" {
// Setup("./tmp/", "./tmp", "./tmp", 11111, false)
Setup("./tmp", "./", "./tmp", 0, false)
useFlutterBridge = false
pb.RegisterCoreServer(s, &CoreService{})
pb.RegisterExtensionHostServiceServer(s, &extension.ExtensionHostService{})
} else if service == "hello" {

172
v2/independent_instance.go Normal file
View File

@@ -0,0 +1,172 @@
package v2
import (
"fmt"
"io"
"net"
"net/http"
"time"
"github.com/hiddify/hiddify-core/config"
"golang.org/x/net/proxy"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/option"
)
func getRandomAvailblePort() uint16 {
// TODO: implement it
listener, err := net.Listen("tcp", ":0")
if err != nil {
panic(err)
}
defer listener.Close()
return uint16(listener.Addr().(*net.TCPAddr).Port)
}
func RunInstanceString(hiddifySettings *config.HiddifyOptions, proxiesInput string) (*HiddifyService, error) {
if hiddifySettings == nil {
hiddifySettings = config.DefaultHiddifyOptions()
}
singconfigs, err := config.ParseConfigContentToOptions(proxiesInput, true, hiddifySettings, false)
if err != nil {
return nil, err
}
return RunInstance(hiddifySettings, singconfigs)
}
func RunInstance(hiddifySettings *config.HiddifyOptions, singconfig *option.Options) (*HiddifyService, error) {
if hiddifySettings == nil {
hiddifySettings = config.DefaultHiddifyOptions()
}
hiddifySettings.EnableClashApi = false
hiddifySettings.InboundOptions.MixedPort = getRandomAvailblePort()
hiddifySettings.InboundOptions.EnableTun = false
hiddifySettings.InboundOptions.EnableTunService = false
hiddifySettings.InboundOptions.SetSystemProxy = false
hiddifySettings.InboundOptions.TProxyPort = 0
hiddifySettings.InboundOptions.LocalDnsPort = 0
hiddifySettings.Region = "other"
hiddifySettings.BlockAds = false
hiddifySettings.LogFile = "/dev/null"
finalConfigs, err := config.BuildConfig(*hiddifySettings, *singconfig)
if err != nil {
return nil, err
}
instance, err := NewService(*finalConfigs)
if err != nil {
return nil, err
}
err = instance.Start()
if err != nil {
return nil, err
}
<-time.After(250 * time.Millisecond)
hservice := &HiddifyService{libbox: instance, ListenPort: hiddifySettings.InboundOptions.MixedPort}
hservice.PingCloudflare()
return hservice, nil
}
type HiddifyService struct {
libbox *libbox.BoxService
ListenPort uint16
}
// dialer, err := s.libbox.GetInstance().Router().Dialer(context.Background())
func (s *HiddifyService) Close() error {
return s.libbox.Close()
}
func (s *HiddifyService) GetContent(url string) (string, error) {
return s.ContentFromURL("GET", url, 10*time.Second)
}
func (s *HiddifyService) ContentFromURL(method string, url string, timeout time.Duration) (string, error) {
if method == "" {
return "", fmt.Errorf("empty method")
}
if url == "" {
return "", fmt.Errorf("empty url")
}
req, err := http.NewRequest(method, url, nil)
if err != nil {
return "", err
}
dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", s.ListenPort), nil, proxy.Direct)
if err != nil {
return "", err
}
transport := &http.Transport{
Dial: dialer.Dial,
}
client := &http.Client{
Transport: transport,
Timeout: timeout,
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
return "", fmt.Errorf("request failed with status code: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
if body == nil {
return "", fmt.Errorf("empty body")
}
return string(body), nil
}
func (s *HiddifyService) PingCloudflare() (time.Duration, error) {
return s.Ping("http://cp.cloudflare.com")
}
// func (s *HiddifyService) RawConnection(ctx context.Context, url string) (net.Conn, error) {
// return
// }
func (s *HiddifyService) PingAverage(url string, count int) (time.Duration, error) {
if count <= 0 {
return -1, fmt.Errorf("count must be greater than 0")
}
var sum int
real_count := 0
for i := 0; i < count; i++ {
delay, err := s.Ping(url)
if err == nil {
real_count++
sum += int(delay.Milliseconds())
} else if real_count == 0 && i > count/2 {
return -1, fmt.Errorf("ping average failed")
}
}
return time.Duration(sum / real_count * int(time.Millisecond)), nil
}
func (s *HiddifyService) Ping(url string) (time.Duration, error) {
startTime := time.Now()
_, err := s.ContentFromURL("HEAD", url, 4*time.Second)
if err != nil {
return -1, err
}
duration := time.Since(startTime)
return duration, nil
}

View File

@@ -6,10 +6,11 @@ import (
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
"github.com/sagernet/sing/common/observable"
"google.golang.org/grpc"
)
func NewObserver[T any](listenerBufferSize int) *observable.Observer[T] {
return observable.NewObserver[T](&observable.Subscriber[T]{}, listenerBufferSize)
return observable.NewObserver(observable.NewSubscriber[T](listenerBufferSize), listenerBufferSize)
}
var logObserver = NewObserver[pb.LogMessage](10)
@@ -17,29 +18,24 @@ var logObserver = NewObserver[pb.LogMessage](10)
func Log(level pb.LogLevel, typ pb.LogType, message string) {
if level != pb.LogLevel_DEBUG {
fmt.Printf("%s %s %s\n", level, typ, message)
}
logObserver.Emit(pb.LogMessage{
Level: level,
Type: typ,
Message: message,
})
}
func (s *CoreService) LogListener(stream pb.Core_LogListenerServer) error {
logSub, _, _ := logObserver.Subscribe()
func (s *CoreService) LogListener(req *pb.Empty, stream grpc.ServerStreamingServer[pb.LogMessage]) error {
logSub, stopch, _ := logObserver.Subscribe()
defer logObserver.UnSubscribe(logSub)
stopch := make(chan int)
go func() {
stream.Recv()
close(stopch)
}()
for {
select {
case <-stream.Context().Done():
return nil
case <-stopch:
return nil
case info := <-logSub:
stream.Send(&info)
case <-time.After(500 * time.Millisecond):

View File

@@ -68,7 +68,7 @@ func readAndBuildConfig(hiddifySettingPath string, configPath string, defaultCon
}
if hiddifySettingPath != "" {
hiddifyconfig, err = readHiddifyOptionsAt(hiddifySettingPath)
hiddifyconfig, err = ReadHiddifyOptionsAt(hiddifySettingPath)
if err != nil {
return result, err
}
@@ -96,7 +96,7 @@ func readConfigContent(configPath string) (ConfigResult, error) {
fmt.Println("Error creating request:", err)
return ConfigResult{}, err
}
req.Header.Set("User-Agent", "HiddifyNext/17.5.0 ("+runtime.GOOS+") like ClashMeta v2ray sing-box")
req.Header.Set("User-Agent", "HiddifyNext/2.3.1 ("+runtime.GOOS+") like ClashMeta v2ray sing-box")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error making GET request:", err)
@@ -222,7 +222,7 @@ func readConfigBytes(content []byte) (*option.Options, error) {
return &options, nil
}
func readHiddifyOptionsAt(path string) (*config.HiddifyOptions, error) {
func ReadHiddifyOptionsAt(path string) (*config.HiddifyOptions, error) {
content, err := os.ReadFile(path)
if err != nil {
return nil, err