release: version 0.17.7

This commit is contained in:
Hiddify
2024-03-10 19:45:03 +01:00
parent 5318d56327
commit c86b5ccb0d
14 changed files with 1195 additions and 1195 deletions

View File

@@ -1,11 +1,11 @@
//go:build !cgo //go:build !cgo
// +build !cgo // +build !cgo
package bridge package bridge
import "unsafe" import "unsafe"
func InitializeDartApi(api unsafe.Pointer) { func InitializeDartApi(api unsafe.Pointer) {
} }
func SendStringToPort(port int64, msg string) { func SendStringToPort(port int64, msg string) {
} }

View File

@@ -1,11 +1,11 @@
package main package main
import ( import (
"os" "os"
"github.com/hiddify/libcore/cmd" "github.com/hiddify/libcore/cmd"
) )
func main() { func main() {
cmd.ParseCli(os.Args[1:]) cmd.ParseCli(os.Args[1:])
} }

View File

@@ -1,26 +1,26 @@
package main package main
/* /*
#include <stdlib.h> #include <stdlib.h>
*/ */
import "C" import "C"
import ( import (
"unsafe" "unsafe"
"github.com/hiddify/libcore/cmd" "github.com/hiddify/libcore/cmd"
) )
//export parseCli //export parseCli
func parseCli(argc C.int, argv **C.char) *C.char { func parseCli(argc C.int, argv **C.char) *C.char {
args := make([]string, argc) args := make([]string, argc)
for i := 0; i < int(argc); i++ { for i := 0; i < int(argc); i++ {
// fmt.Println("parseCli", C.GoString(*argv)) // fmt.Println("parseCli", C.GoString(*argv))
args[i] = C.GoString(*argv) args[i] = C.GoString(*argv)
argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + uintptr(unsafe.Sizeof(*argv)))) argv = (**C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(argv)) + uintptr(unsafe.Sizeof(*argv))))
} }
err := cmd.ParseCli(args[1:]) err := cmd.ParseCli(args[1:])
if err != nil { if err != nil {
return C.CString(err.Error()) return C.CString(err.Error())
} }
return C.CString("") return C.CString("")
} }

View File

@@ -1,223 +1,223 @@
package global package global
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
"github.com/hiddify/libcore/config" "github.com/hiddify/libcore/config"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
) )
func RunStandalone(hiddifySettingPath string, configPath string) error { func RunStandalone(hiddifySettingPath string, configPath string) error {
fmt.Println("Running in standalone mode") fmt.Println("Running in standalone mode")
current, err := readAndBuildConfig(hiddifySettingPath, configPath) current, err := readAndBuildConfig(hiddifySettingPath, configPath)
if err != nil { if err != nil {
fmt.Printf("Error in read and build config %v", err) fmt.Printf("Error in read and build config %v", err)
return err return err
} }
go StartServiceC(false, current.Config) go StartServiceC(false, current.Config)
go updateConfigInterval(current, hiddifySettingPath, configPath) go updateConfigInterval(current, hiddifySettingPath, configPath)
fmt.Printf("Press CTRL+C to stop\n") fmt.Printf("Press CTRL+C to stop\n")
fmt.Printf("Open http://localhost:6756/?secret=hiddify in your browser\n") fmt.Printf("Open http://localhost:6756/?secret=hiddify in your browser\n")
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan <-sigChan
err = StopServiceC() err = StopServiceC()
return err return err
} }
type ConfigResult struct { type ConfigResult struct {
Config string Config string
RefreshInterval int RefreshInterval int
} }
func readAndBuildConfig(hiddifySettingPath string, configPath string) (ConfigResult, error) { func readAndBuildConfig(hiddifySettingPath string, configPath string) (ConfigResult, error) {
var result ConfigResult var result ConfigResult
result, err := readConfigContent(configPath) result, err := readConfigContent(configPath)
if err != nil { if err != nil {
return result, err return result, err
} }
hiddifyconfig := config.DefaultConfigOptions() hiddifyconfig := config.DefaultConfigOptions()
if hiddifySettingPath != "" { if hiddifySettingPath != "" {
hiddifyconfig, err = readConfigOptionsAt(hiddifySettingPath) hiddifyconfig, err = readConfigOptionsAt(hiddifySettingPath)
if err != nil { if err != nil {
return result, err return result, err
} }
} }
result.Config, err = buildConfig(result.Config, *hiddifyconfig) result.Config, err = buildConfig(result.Config, *hiddifyconfig)
if err != nil { if err != nil {
return result, err return result, err
} }
return result, nil return result, nil
} }
func readConfigContent(configPath string) (ConfigResult, error) { func readConfigContent(configPath string) (ConfigResult, error) {
var content string var content string
var refreshInterval int var refreshInterval int
if strings.HasPrefix(configPath, "http://") || strings.HasPrefix(configPath, "https://") { if strings.HasPrefix(configPath, "http://") || strings.HasPrefix(configPath, "https://") {
client := &http.Client{} client := &http.Client{}
// Create a new request // Create a new request
req, err := http.NewRequest("GET", configPath, nil) req, err := http.NewRequest("GET", configPath, nil)
if err != nil { if err != nil {
fmt.Println("Error creating request:", err) fmt.Println("Error creating request:", err)
return ConfigResult{}, 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/17.5.0 ("+runtime.GOOS+") like ClashMeta v2ray sing-box")
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Println("Error making GET request:", err) fmt.Println("Error making GET request:", err)
return ConfigResult{}, err return ConfigResult{}, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return ConfigResult{}, fmt.Errorf("failed to read config body: %w", err) return ConfigResult{}, fmt.Errorf("failed to read config body: %w", err)
} }
content = string(body) content = string(body)
refreshInterval, _ = extractRefreshInterval(resp.Header, content) refreshInterval, _ = extractRefreshInterval(resp.Header, content)
fmt.Printf("Refresh interval: %d\n", refreshInterval) fmt.Printf("Refresh interval: %d\n", refreshInterval)
} else { } else {
data, err := ioutil.ReadFile(configPath) data, err := ioutil.ReadFile(configPath)
if err != nil { if err != nil {
return ConfigResult{}, fmt.Errorf("failed to read config file: %w", err) return ConfigResult{}, fmt.Errorf("failed to read config file: %w", err)
} }
content = string(data) content = string(data)
} }
return ConfigResult{ return ConfigResult{
Config: content, Config: content,
RefreshInterval: refreshInterval, RefreshInterval: refreshInterval,
}, nil }, nil
} }
func extractRefreshInterval(header http.Header, bodyStr string) (int, error) { func extractRefreshInterval(header http.Header, bodyStr string) (int, error) {
refreshIntervalStr := header.Get("profile-update-interval") refreshIntervalStr := header.Get("profile-update-interval")
if refreshIntervalStr != "" { if refreshIntervalStr != "" {
refreshInterval, err := strconv.Atoi(refreshIntervalStr) refreshInterval, err := strconv.Atoi(refreshIntervalStr)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to parse refresh interval from header: %w", err) return 0, fmt.Errorf("failed to parse refresh interval from header: %w", err)
} }
return refreshInterval, nil return refreshInterval, nil
} }
lines := strings.Split(bodyStr, "\n") lines := strings.Split(bodyStr, "\n")
for _, line := range lines { for _, line := range lines {
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
if strings.HasPrefix(line, "//profile-update-interval:") || strings.HasPrefix(line, "#profile-update-interval:") { if strings.HasPrefix(line, "//profile-update-interval:") || strings.HasPrefix(line, "#profile-update-interval:") {
parts := strings.SplitN(line, ":", 2) parts := strings.SplitN(line, ":", 2)
str := strings.TrimSpace(parts[1]) str := strings.TrimSpace(parts[1])
refreshInterval, err := strconv.Atoi(str) refreshInterval, err := strconv.Atoi(str)
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to parse refresh interval from body: %w", err) return 0, fmt.Errorf("failed to parse refresh interval from body: %w", err)
} }
return refreshInterval, nil return refreshInterval, nil
} }
} }
return 0, nil return 0, nil
} }
func buildConfig(configContent string, options config.ConfigOptions) (string, error) { func buildConfig(configContent string, options config.ConfigOptions) (string, error) {
parsedContent, err := config.ParseConfigContent(configContent, true) parsedContent, err := config.ParseConfigContent(configContent, true)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to parse config content: %w", err) return "", fmt.Errorf("failed to parse config content: %w", err)
} }
singconfigs, err := readConfigBytes([]byte(parsedContent)) singconfigs, err := readConfigBytes([]byte(parsedContent))
if err != nil { if err != nil {
return "", err return "", err
} }
finalconfig, err := config.BuildConfig(options, *singconfigs) finalconfig, err := config.BuildConfig(options, *singconfigs)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to build config: %w", err) return "", fmt.Errorf("failed to build config: %w", err)
} }
finalconfig.Log.Output = "" finalconfig.Log.Output = ""
finalconfig.Experimental.ClashAPI.ExternalUI = "webui" finalconfig.Experimental.ClashAPI.ExternalUI = "webui"
if options.AllowConnectionFromLAN { if options.AllowConnectionFromLAN {
finalconfig.Experimental.ClashAPI.ExternalController = "0.0.0.0:6756" finalconfig.Experimental.ClashAPI.ExternalController = "0.0.0.0:6756"
} else { } else {
finalconfig.Experimental.ClashAPI.ExternalController = "127.0.0.1:6756" finalconfig.Experimental.ClashAPI.ExternalController = "127.0.0.1:6756"
} }
if finalconfig.Experimental.ClashAPI.Secret == "" { if finalconfig.Experimental.ClashAPI.Secret == "" {
// finalconfig.Experimental.ClashAPI.Secret = "hiddify" // finalconfig.Experimental.ClashAPI.Secret = "hiddify"
} }
if err := SetupC("./", "./", "./tmp", false); err != nil { if err := SetupC("./", "./", "./tmp", false); err != nil {
return "", fmt.Errorf("failed to set up global configuration: %w", err) return "", fmt.Errorf("failed to set up global configuration: %w", err)
} }
configStr, err := config.ToJson(*finalconfig) configStr, err := config.ToJson(*finalconfig)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to convert config to JSON: %w", err) return "", fmt.Errorf("failed to convert config to JSON: %w", err)
} }
return configStr, nil return configStr, nil
} }
func updateConfigInterval(current ConfigResult, hiddifySettingPath string, configPath string) { func updateConfigInterval(current ConfigResult, hiddifySettingPath string, configPath string) {
if current.RefreshInterval <= 0 { if current.RefreshInterval <= 0 {
return return
} }
for { for {
<-time.After(time.Duration(current.RefreshInterval) * time.Hour) <-time.After(time.Duration(current.RefreshInterval) * time.Hour)
new, err := readAndBuildConfig(hiddifySettingPath, configPath) new, err := readAndBuildConfig(hiddifySettingPath, configPath)
if err != nil { if err != nil {
continue continue
} }
if new.Config != current.Config { if new.Config != current.Config {
go StopServiceC() go StopServiceC()
go StartServiceC(false, new.Config) go StartServiceC(false, new.Config)
} }
current = new current = new
} }
} }
func readConfigBytes(content []byte) (*option.Options, error) { func readConfigBytes(content []byte) (*option.Options, error) {
var options option.Options var options option.Options
err := options.UnmarshalJSON(content) err := options.UnmarshalJSON(content)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &options, nil return &options, nil
} }
func readConfigOptionsAt(path string) (*config.ConfigOptions, error) { func readConfigOptionsAt(path string) (*config.ConfigOptions, error) {
content, err := os.ReadFile(path) content, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var options config.ConfigOptions var options config.ConfigOptions
err = json.Unmarshal(content, &options) err = json.Unmarshal(content, &options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if options.Warp.WireguardConfigStr != "" { if options.Warp.WireguardConfigStr != "" {
err := json.Unmarshal([]byte(options.Warp.WireguardConfigStr), &options.Warp.WireguardConfig) err := json.Unmarshal([]byte(options.Warp.WireguardConfigStr), &options.Warp.WireguardConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
return &options, nil return &options, nil
} }

View File

@@ -1,214 +1,214 @@
syntax = "proto3"; syntax = "proto3";
package hiddifyrpc; package hiddifyrpc;
option go_package = "./hiddifyrpc"; option go_package = "./hiddifyrpc";
enum ResponseCode { enum ResponseCode {
OK = 0; OK = 0;
FAILED = 1; FAILED = 1;
} }
enum CoreState { enum CoreState {
STOPPED = 0; STOPPED = 0;
STARTING = 1; STARTING = 1;
STARTED = 2; STARTED = 2;
STOPPING = 3; STOPPING = 3;
} }
enum MessageType { enum MessageType {
EMPTY=0; EMPTY=0;
EMPTY_CONFIGURATION = 1; EMPTY_CONFIGURATION = 1;
START_COMMAND_SERVER = 2; START_COMMAND_SERVER = 2;
CREATE_SERVICE = 3; CREATE_SERVICE = 3;
START_SERVICE = 4; START_SERVICE = 4;
UNEXPECTED_ERROR = 5; UNEXPECTED_ERROR = 5;
ALREADY_STARTED = 6; ALREADY_STARTED = 6;
ALREADY_STOPPED = 7; ALREADY_STOPPED = 7;
INSTANCE_NOT_FOUND = 8; INSTANCE_NOT_FOUND = 8;
INSTANCE_NOT_STOPPED = 9; INSTANCE_NOT_STOPPED = 9;
INSTANCE_NOT_STARTED = 10; INSTANCE_NOT_STARTED = 10;
ERROR_BUILDING_CONFIG = 11; ERROR_BUILDING_CONFIG = 11;
ERROR_PARSING_CONFIG = 12; ERROR_PARSING_CONFIG = 12;
ERROR_READING_CONFIG = 13; ERROR_READING_CONFIG = 13;
} }
message CoreInfoResponse { message CoreInfoResponse {
CoreState core_state = 1; CoreState core_state = 1;
MessageType message_type = 2; MessageType message_type = 2;
string message = 3; string message = 3;
} }
message StartRequest { message StartRequest {
string config_path = 1; string config_path = 1;
string config_content = 2; // Optional if configPath is not provided. string config_content = 2; // Optional if configPath is not provided.
bool disable_memory_limit = 3; bool disable_memory_limit = 3;
bool delay_start = 4; bool delay_start = 4;
} }
message SetupRequest { message SetupRequest {
string base_path = 1; string base_path = 1;
string working_path = 2; string working_path = 2;
string temp_path = 3; string temp_path = 3;
} }
message Response { message Response {
ResponseCode response_code = 1; ResponseCode response_code = 1;
string message = 2; string message = 2;
} }
message HelloRequest { message HelloRequest {
string name = 1; string name = 1;
} }
message HelloResponse { message HelloResponse {
string message = 1; string message = 1;
} }
message Empty { message Empty {
} }
message SystemInfo { message SystemInfo {
int64 memory = 1; int64 memory = 1;
int32 goroutines = 2; int32 goroutines = 2;
int32 connections_in = 3; int32 connections_in = 3;
int32 connections_out = 4; int32 connections_out = 4;
bool traffic_available = 5; bool traffic_available = 5;
int64 uplink = 6; int64 uplink = 6;
int64 downlink = 7; int64 downlink = 7;
int64 uplink_total = 8; int64 uplink_total = 8;
int64 downlink_total = 9; int64 downlink_total = 9;
} }
message OutboundGroupItem { message OutboundGroupItem {
string tag = 1; string tag = 1;
string type = 2; string type = 2;
int64 url_test_time = 3; int64 url_test_time = 3;
int32 url_test_delay = 4; int32 url_test_delay = 4;
} }
message OutboundGroup { message OutboundGroup {
string tag = 1; string tag = 1;
string type = 2; string type = 2;
string selected=3; string selected=3;
repeated OutboundGroupItem items = 4; repeated OutboundGroupItem items = 4;
} }
message OutboundGroupList{ message OutboundGroupList{
repeated OutboundGroup items = 1; repeated OutboundGroup items = 1;
} }
message WarpAccount { message WarpAccount {
string account_id = 1; string account_id = 1;
string access_token = 2; string access_token = 2;
} }
message WarpWireguardConfig { message WarpWireguardConfig {
string private_key = 1; string private_key = 1;
string local_address_ipv4 = 2; string local_address_ipv4 = 2;
string local_address_ipv6 = 3; string local_address_ipv6 = 3;
string peer_public_key = 4; string peer_public_key = 4;
} }
message WarpGenerationResponse { message WarpGenerationResponse {
WarpAccount account = 1; WarpAccount account = 1;
string log = 2; string log = 2;
WarpWireguardConfig config = 3; WarpWireguardConfig config = 3;
} }
message SystemProxyStatus { message SystemProxyStatus {
bool available = 1; bool available = 1;
bool enabled = 2; bool enabled = 2;
} }
message ParseRequest { message ParseRequest {
string content = 1; string content = 1;
bool debug = 2; bool debug = 2;
} }
message ParseResponse { message ParseResponse {
ResponseCode response_code = 1; ResponseCode response_code = 1;
string content = 2; string content = 2;
string message = 3; string message = 3;
} }
message ChangeConfigOptionsRequest { message ChangeConfigOptionsRequest {
string config_options_json = 1; string config_options_json = 1;
} }
message GenerateConfigRequest { message GenerateConfigRequest {
string path = 1; string path = 1;
string temp_path = 2; string temp_path = 2;
bool debug = 3; bool debug = 3;
} }
message GenerateConfigResponse { message GenerateConfigResponse {
string config_content = 1; string config_content = 1;
} }
message SelectOutboundRequest { message SelectOutboundRequest {
string group_tag = 1; string group_tag = 1;
string outbound_tag = 2; string outbound_tag = 2;
} }
message UrlTestRequest { message UrlTestRequest {
string group_tag = 1; string group_tag = 1;
} }
message GenerateWarpConfigRequest { message GenerateWarpConfigRequest {
string license_key = 1; string license_key = 1;
string account_id = 2; string account_id = 2;
string access_token = 3; string access_token = 3;
} }
message SetSystemProxyEnabledRequest { message SetSystemProxyEnabledRequest {
bool is_enabled = 1; bool is_enabled = 1;
} }
enum LogLevel { enum LogLevel {
DEBUG = 0; DEBUG = 0;
INFO = 1; INFO = 1;
WARNING = 2; WARNING = 2;
ERROR = 3; ERROR = 3;
FATAL = 4; FATAL = 4;
} }
enum LogType { enum LogType {
CORE = 0; CORE = 0;
SERVICE = 1; SERVICE = 1;
CONFIG = 2; CONFIG = 2;
} }
message LogMessage { message LogMessage {
LogLevel level = 1; LogLevel level = 1;
LogType type = 2; LogType type = 2;
string message = 3; string message = 3;
} }
message StopRequest{ message StopRequest{
} }
service Hiddify { service Hiddify {
rpc SayHello (HelloRequest) returns (HelloResponse); rpc SayHello (HelloRequest) returns (HelloResponse);
rpc SayHelloStream (stream HelloRequest) returns (stream HelloResponse); rpc SayHelloStream (stream HelloRequest) returns (stream HelloResponse);
rpc Start (StartRequest) returns (CoreInfoResponse); rpc Start (StartRequest) returns (CoreInfoResponse);
rpc CoreInfoListener (stream StopRequest) returns (stream CoreInfoResponse); rpc CoreInfoListener (stream StopRequest) returns (stream CoreInfoResponse);
rpc OutboundsInfo (stream StopRequest) returns (stream OutboundGroupList); rpc OutboundsInfo (stream StopRequest) returns (stream OutboundGroupList);
rpc MainOutboundsInfo (stream StopRequest) returns (stream OutboundGroupList); rpc MainOutboundsInfo (stream StopRequest) returns (stream OutboundGroupList);
rpc GetSystemInfo (stream StopRequest) returns (stream SystemInfo); rpc GetSystemInfo (stream StopRequest) returns (stream SystemInfo);
rpc Setup (SetupRequest) returns (Response); rpc Setup (SetupRequest) returns (Response);
rpc Parse (ParseRequest) returns (ParseResponse); rpc Parse (ParseRequest) returns (ParseResponse);
//rpc ChangeConfigOptions (ChangeConfigOptionsRequest) returns (CoreInfoResponse); //rpc ChangeConfigOptions (ChangeConfigOptionsRequest) returns (CoreInfoResponse);
//rpc GenerateConfig (GenerateConfigRequest) returns (GenerateConfigResponse); //rpc GenerateConfig (GenerateConfigRequest) returns (GenerateConfigResponse);
rpc StartService (StartRequest) returns (CoreInfoResponse); rpc StartService (StartRequest) returns (CoreInfoResponse);
rpc Stop (Empty) returns (CoreInfoResponse); rpc Stop (Empty) returns (CoreInfoResponse);
rpc Restart (StartRequest) returns (CoreInfoResponse); rpc Restart (StartRequest) returns (CoreInfoResponse);
rpc SelectOutbound (SelectOutboundRequest) returns (Response); rpc SelectOutbound (SelectOutboundRequest) returns (Response);
rpc UrlTest (UrlTestRequest) returns (Response); rpc UrlTest (UrlTestRequest) returns (Response);
rpc GenerateWarpConfig (GenerateWarpConfigRequest) returns (WarpGenerationResponse); rpc GenerateWarpConfig (GenerateWarpConfigRequest) returns (WarpGenerationResponse);
rpc GetSystemProxyStatus (Empty) returns (SystemProxyStatus); rpc GetSystemProxyStatus (Empty) returns (SystemProxyStatus);
rpc SetSystemProxyEnabled (SetSystemProxyEnabledRequest) returns (Response); rpc SetSystemProxyEnabled (SetSystemProxyEnabledRequest) returns (Response);
rpc LogListener (stream StopRequest) returns (stream LogMessage); rpc LogListener (stream StopRequest) returns (stream LogMessage);
} }

View File

@@ -1,163 +1,163 @@
package v2 package v2
import ( import (
"context" "context"
"time" "time"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"github.com/sagernet/sing-box/experimental/libbox" "github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/observable"
) )
var systemInfoObserver = observable.Observer[pb.SystemInfo]{} var systemInfoObserver = observable.Observer[pb.SystemInfo]{}
var outboundsInfoObserver = observable.Observer[pb.OutboundGroupList]{} var outboundsInfoObserver = observable.Observer[pb.OutboundGroupList]{}
var mainOutboundsInfoObserver = observable.Observer[pb.OutboundGroupList]{} var mainOutboundsInfoObserver = observable.Observer[pb.OutboundGroupList]{}
var ( var (
statusClient *libbox.CommandClient statusClient *libbox.CommandClient
groupClient *libbox.CommandClient groupClient *libbox.CommandClient
groupInfoOnlyClient *libbox.CommandClient groupInfoOnlyClient *libbox.CommandClient
) )
func (s *server) GetSystemInfo(stream pb.Hiddify_GetSystemInfoServer) error { func (s *server) GetSystemInfo(stream pb.Hiddify_GetSystemInfoServer) error {
if statusClient == nil { if statusClient == nil {
statusClient = libbox.NewCommandClient( statusClient = libbox.NewCommandClient(
&CommandClientHandler{}, &CommandClientHandler{},
&libbox.CommandClientOptions{ &libbox.CommandClientOptions{
Command: libbox.CommandStatus, Command: libbox.CommandStatus,
StatusInterval: 1000000000, //1000ms debounce StatusInterval: 1000000000, //1000ms debounce
}, },
) )
defer func() { defer func() {
statusClient.Disconnect() statusClient.Disconnect()
statusClient = nil statusClient = nil
}() }()
statusClient.Connect() statusClient.Connect()
} }
sub, _, _ := systemInfoObserver.Subscribe() sub, _, _ := systemInfoObserver.Subscribe()
stopch := make(chan int) stopch := make(chan int)
go func() { go func() {
stream.Recv() stream.Recv()
close(stopch) close(stopch)
}() }()
for { for {
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
break break
case <-stopch: case <-stopch:
break break
case info := <-sub: case info := <-sub:
stream.Send(&info) stream.Send(&info)
case <-time.After(1000 * time.Millisecond): case <-time.After(1000 * time.Millisecond):
} }
} }
} }
func (s *server) OutboundsInfo(stream pb.Hiddify_OutboundsInfoServer) error { func (s *server) OutboundsInfo(stream pb.Hiddify_OutboundsInfoServer) error {
if groupClient == nil { if groupClient == nil {
groupClient = libbox.NewCommandClient( groupClient = libbox.NewCommandClient(
&CommandClientHandler{}, &CommandClientHandler{},
&libbox.CommandClientOptions{ &libbox.CommandClientOptions{
Command: libbox.CommandGroup, Command: libbox.CommandGroup,
StatusInterval: 500000000, //500ms debounce StatusInterval: 500000000, //500ms debounce
}, },
) )
defer func() { defer func() {
groupClient.Disconnect() groupClient.Disconnect()
groupClient = nil groupClient = nil
}() }()
groupClient.Connect() groupClient.Connect()
} }
sub, _, _ := outboundsInfoObserver.Subscribe() sub, _, _ := outboundsInfoObserver.Subscribe()
stopch := make(chan int) stopch := make(chan int)
go func() { go func() {
stream.Recv() stream.Recv()
close(stopch) close(stopch)
}() }()
for { for {
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
break break
case <-stopch: case <-stopch:
break break
case info := <-sub: case info := <-sub:
stream.Send(&info) stream.Send(&info)
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
} }
} }
} }
func (s *server) MainOutboundsInfo(stream pb.Hiddify_MainOutboundsInfoServer) error { func (s *server) MainOutboundsInfo(stream pb.Hiddify_MainOutboundsInfoServer) error {
if groupInfoOnlyClient == nil { if groupInfoOnlyClient == nil {
groupInfoOnlyClient = libbox.NewCommandClient( groupInfoOnlyClient = libbox.NewCommandClient(
&CommandClientHandler{}, &CommandClientHandler{},
&libbox.CommandClientOptions{ &libbox.CommandClientOptions{
Command: libbox.CommandGroupInfoOnly, Command: libbox.CommandGroupInfoOnly,
StatusInterval: 500000000, //500ms debounce StatusInterval: 500000000, //500ms debounce
}, },
) )
defer func() { defer func() {
groupInfoOnlyClient.Disconnect() groupInfoOnlyClient.Disconnect()
groupInfoOnlyClient = nil groupInfoOnlyClient = nil
}() }()
groupInfoOnlyClient.Connect() groupInfoOnlyClient.Connect()
} }
sub, _, _ := mainOutboundsInfoObserver.Subscribe() sub, _, _ := mainOutboundsInfoObserver.Subscribe()
stopch := make(chan int) stopch := make(chan int)
go func() { go func() {
stream.Recv() stream.Recv()
close(stopch) close(stopch)
}() }()
for { for {
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
break break
case <-stopch: case <-stopch:
break break
case info := <-sub: case info := <-sub:
stream.Send(&info) stream.Send(&info)
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
} }
} }
} }
// Implement the SelectOutbound method // Implement the SelectOutbound method
func (s *server) SelectOutbound(ctx context.Context, in *pb.SelectOutboundRequest) (*pb.Response, error) { func (s *server) SelectOutbound(ctx context.Context, in *pb.SelectOutboundRequest) (*pb.Response, error) {
err := libbox.NewStandaloneCommandClient().SelectOutbound(in.GroupTag, in.OutboundTag) err := libbox.NewStandaloneCommandClient().SelectOutbound(in.GroupTag, in.OutboundTag)
if err != nil { if err != nil {
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_FAILED, ResponseCode: pb.ResponseCode_FAILED,
Message: err.Error(), Message: err.Error(),
}, err }, err
} }
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_OK, ResponseCode: pb.ResponseCode_OK,
Message: "", Message: "",
}, nil }, nil
} }
// Implement the UrlTest method // Implement the UrlTest method
func (s *server) UrlTest(ctx context.Context, in *pb.UrlTestRequest) (*pb.Response, error) { func (s *server) UrlTest(ctx context.Context, in *pb.UrlTestRequest) (*pb.Response, error) {
err := libbox.NewStandaloneCommandClient().URLTest(in.GroupTag) err := libbox.NewStandaloneCommandClient().URLTest(in.GroupTag)
if err != nil { if err != nil {
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_FAILED, ResponseCode: pb.ResponseCode_FAILED,
Message: err.Error(), Message: err.Error(),
}, err }, err
} }
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_OK, ResponseCode: pb.ResponseCode_OK,
Message: "", Message: "",
}, nil }, nil
} }

View File

@@ -1,44 +1,44 @@
package v2 package v2
import ( import (
"time" "time"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/observable"
) )
var coreInfoObserver = observable.Observer[pb.CoreInfoResponse]{} var coreInfoObserver = observable.Observer[pb.CoreInfoResponse]{}
var CoreState = pb.CoreState_STOPPED var 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 {
CoreState = state CoreState = state
info := pb.CoreInfoResponse{ info := pb.CoreInfoResponse{
CoreState: state, CoreState: state,
MessageType: msgType, MessageType: msgType,
Message: message, Message: message,
} }
coreInfoObserver.Emit(info) coreInfoObserver.Emit(info)
return info return info
} }
func (s *server) CoreInfoListener(stream pb.Hiddify_CoreInfoListenerServer) error { func (s *server) CoreInfoListener(stream pb.Hiddify_CoreInfoListenerServer) error {
coreSub, _, _ := coreInfoObserver.Subscribe() coreSub, _, _ := coreInfoObserver.Subscribe()
defer coreInfoObserver.UnSubscribe(coreSub) defer coreInfoObserver.UnSubscribe(coreSub)
stopch := make(chan int) stopch := make(chan int)
go func() { go func() {
stream.Recv() stream.Recv()
close(stopch) close(stopch)
}() }()
for { for {
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
break break
case <-stopch: case <-stopch:
break break
case info := <-coreSub: case info := <-coreSub:
stream.Send(&info) stream.Send(&info)
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
} }
} }
} }

View File

@@ -1,232 +1,232 @@
package v2 package v2
import ( import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"github.com/hiddify/libcore/config" "github.com/hiddify/libcore/config"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"github.com/sagernet/sing-box/experimental/libbox" "github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
) )
var Box *libbox.BoxService var Box *libbox.BoxService
var configOptions *config.ConfigOptions var configOptions *config.ConfigOptions
var activeConfigPath *string var activeConfigPath *string
var logFactory *log.Factory var logFactory *log.Factory
func StopAndAlert(msgType pb.MessageType, message string) { func StopAndAlert(msgType pb.MessageType, message string) {
SetCoreStatus(pb.CoreState_STOPPED, msgType, message) SetCoreStatus(pb.CoreState_STOPPED, msgType, message)
config.DeactivateTunnelService() config.DeactivateTunnelService()
// if commandServer != nil { // if commandServer != nil {
// commandServer.SetService(nil) // commandServer.SetService(nil)
// } // }
if Box != nil { if Box != nil {
Box.Close() Box.Close()
Box = nil Box = nil
} }
// if commandServer != nil { // if commandServer != nil {
// commandServer.Close() // commandServer.Close()
// } // }
} }
func (s *server) Start(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) { func (s *server) Start(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
defer config.DeferPanicToError("start", func(err error) { defer config.DeferPanicToError("start", func(err error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error()) Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error()) StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
}) })
if CoreState != pb.CoreState_STOPPED { if CoreState != pb.CoreState_STOPPED {
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_INSTANCE_NOT_STOPPED, MessageType: pb.MessageType_INSTANCE_NOT_STOPPED,
}, fmt.Errorf("instance not stopped") }, fmt.Errorf("instance not stopped")
} }
SetCoreStatus(pb.CoreState_STARTING, pb.MessageType_EMPTY, "") SetCoreStatus(pb.CoreState_STARTING, pb.MessageType_EMPTY, "")
libbox.SetMemoryLimit(!in.DisableMemoryLimit) libbox.SetMemoryLimit(!in.DisableMemoryLimit)
resp, err := s.StartService(ctx, in) resp, err := s.StartService(ctx, in)
return resp, err return resp, err
} }
// Implement the StartService method // Implement the StartService method
func (s *server) StartService(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) { func (s *server) StartService(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
content := in.ConfigContent content := in.ConfigContent
if content != "" { if content != "" {
fileContent, err := os.ReadFile(*activeConfigPath) fileContent, err := os.ReadFile(*activeConfigPath)
if err != nil { if err != nil {
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_READING_CONFIG, err.Error()) resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_READING_CONFIG, err.Error())
return &resp, err return &resp, err
} }
content = string(fileContent) content = string(fileContent)
} }
parsedContent, err := parseConfig(content) parsedContent, err := parseConfig(content)
if err != nil { if err != nil {
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_PARSING_CONFIG, err.Error()) resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_PARSING_CONFIG, err.Error())
return &resp, err return &resp, err
} }
var patchedOptions *option.Options var patchedOptions *option.Options
patchedOptions, err = config.BuildConfig(*configOptions, parsedContent) patchedOptions, err = config.BuildConfig(*configOptions, parsedContent)
if err != nil { if err != nil {
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_BUILDING_CONFIG, err.Error()) resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_ERROR_BUILDING_CONFIG, err.Error())
return &resp, err return &resp, err
} }
config.SaveCurrentConfig(filepath.Join(sWorkingPath, "current-config.json"), *patchedOptions) config.SaveCurrentConfig(filepath.Join(sWorkingPath, "current-config.json"), *patchedOptions)
// err = startCommandServer(*logFactory) // err = startCommandServer(*logFactory)
// if err != nil { // if err != nil {
// resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_COMMAND_SERVER, err.Error()) // resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_COMMAND_SERVER, err.Error())
// return &resp, err // return &resp, err
// } // }
instance, err := NewService(*patchedOptions) instance, err := NewService(*patchedOptions)
if err != nil { if err != nil {
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_CREATE_SERVICE, err.Error()) resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_CREATE_SERVICE, err.Error())
return &resp, err return &resp, err
} }
if in.DelayStart { if in.DelayStart {
<-time.After(250 * time.Millisecond) <-time.After(250 * time.Millisecond)
} }
err = instance.Start() err = instance.Start()
if err != nil { if err != nil {
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_SERVICE, err.Error()) resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_START_SERVICE, err.Error())
return &resp, err return &resp, err
} }
Box = instance Box = instance
// commandServer.SetService(box) // commandServer.SetService(box)
resp := SetCoreStatus(pb.CoreState_STARTED, pb.MessageType_EMPTY, "") resp := SetCoreStatus(pb.CoreState_STARTED, pb.MessageType_EMPTY, "")
return &resp, nil return &resp, nil
} }
func (s *server) Parse(ctx context.Context, in *pb.ParseRequest) (*pb.ParseResponse, error) { func (s *server) Parse(ctx context.Context, in *pb.ParseRequest) (*pb.ParseResponse, error) {
defer config.DeferPanicToError("parse", func(err error) { defer config.DeferPanicToError("parse", func(err error) {
Log(pb.LogLevel_FATAL, pb.LogType_CONFIG, err.Error()) Log(pb.LogLevel_FATAL, pb.LogType_CONFIG, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error()) StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
}) })
config, err := config.ParseConfigContent(in.Content, true) config, err := config.ParseConfigContent(in.Content, true)
if err != nil { if err != nil {
return &pb.ParseResponse{ return &pb.ParseResponse{
ResponseCode: pb.ResponseCode_FAILED, ResponseCode: pb.ResponseCode_FAILED,
Message: err.Error(), Message: err.Error(),
}, err }, err
} }
return &pb.ParseResponse{ return &pb.ParseResponse{
ResponseCode: pb.ResponseCode_OK, ResponseCode: pb.ResponseCode_OK,
Content: string(config), Content: string(config),
Message: "", Message: "",
}, err }, err
} }
// func (s *server) ChangeConfigOptions(ctx context.Context, in *pb.ChangeConfigOptionsRequest) (*pb.CoreInfoResponse, error) { // func (s *server) ChangeConfigOptions(ctx context.Context, in *pb.ChangeConfigOptionsRequest) (*pb.CoreInfoResponse, error) {
// // Implement your change config options logic // // Implement your change config options logic
// // Return a CoreInfoResponse // // Return a CoreInfoResponse
// } // }
// func (s *server) GenerateConfig(ctx context.Context, in *pb.GenerateConfigRequest) (*pb.GenerateConfigResponse, error) { // func (s *server) GenerateConfig(ctx context.Context, in *pb.GenerateConfigRequest) (*pb.GenerateConfigResponse, error) {
// defer config.DeferPanicToError("generateConfig", func(err error) { // defer config.DeferPanicToError("generateConfig", func(err error) {
// Log(pb.LogLevel_FATAL, pb.LogType_CONFIG, err.Error()) // Log(pb.LogLevel_FATAL, pb.LogType_CONFIG, err.Error())
// StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error()) // StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
// }) // })
// config, err := generateConfigFromFile(C.GoString(path), *configOptions) // config, err := generateConfigFromFile(C.GoString(path), *configOptions)
// if err != nil { // if err != nil {
// return C.CString("error" + err.Error()) // return C.CString("error" + err.Error())
// } // }
// return C.CString(config) // return C.CString(config)
// } // }
// Implement the Stop method // Implement the Stop method
func (s *server) Stop(ctx context.Context, empty *pb.Empty) (*pb.CoreInfoResponse, error) { func (s *server) Stop(ctx context.Context, empty *pb.Empty) (*pb.CoreInfoResponse, error) {
defer config.DeferPanicToError("stop", func(err error) { defer config.DeferPanicToError("stop", func(err error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error()) Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error()) StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
}) })
config.DeactivateTunnelService() config.DeactivateTunnelService()
if CoreState != pb.CoreState_STARTED { if CoreState != pb.CoreState_STARTED {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, "Core is not started") Log(pb.LogLevel_FATAL, pb.LogType_CORE, "Core is not started")
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_INSTANCE_NOT_STARTED, MessageType: pb.MessageType_INSTANCE_NOT_STARTED,
Message: "instance is not started", Message: "instance is not started",
}, fmt.Errorf("instance not started") }, fmt.Errorf("instance not started")
} }
if Box == nil { if Box == nil {
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_INSTANCE_NOT_FOUND, MessageType: pb.MessageType_INSTANCE_NOT_FOUND,
Message: "instance is not found", Message: "instance is not found",
}, fmt.Errorf("instance not found") }, fmt.Errorf("instance not found")
} }
SetCoreStatus(pb.CoreState_STOPPING, pb.MessageType_EMPTY, "") SetCoreStatus(pb.CoreState_STOPPING, pb.MessageType_EMPTY, "")
// commandServer.SetService(nil) // commandServer.SetService(nil)
err := Box.Close() err := Box.Close()
if err != nil { if err != nil {
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_UNEXPECTED_ERROR, MessageType: pb.MessageType_UNEXPECTED_ERROR,
Message: "Error while stopping the service.", Message: "Error while stopping the service.",
}, fmt.Errorf("Error while stopping the service.") }, fmt.Errorf("Error while stopping the service.")
} }
Box = nil Box = nil
// err = commandServer.Close() // err = commandServer.Close()
// if err != nil { // if err != nil {
// return &pb.CoreInfoResponse{ // return &pb.CoreInfoResponse{
// CoreState: CoreState, // CoreState: CoreState,
// MessageType: pb.MessageType_UNEXPECTED_ERROR, // MessageType: pb.MessageType_UNEXPECTED_ERROR,
// Message: "Error while Closing the comand server.", // Message: "Error while Closing the comand server.",
// }, fmt.Errorf("Error while Closing the comand server.") // }, fmt.Errorf("Error while Closing the comand server.")
// } // }
// commandServer = nil // commandServer = nil
resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_EMPTY, "") resp := SetCoreStatus(pb.CoreState_STOPPED, pb.MessageType_EMPTY, "")
return &resp, nil return &resp, nil
} }
func (s *server) Restart(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) { func (s *server) Restart(ctx context.Context, in *pb.StartRequest) (*pb.CoreInfoResponse, error) {
defer config.DeferPanicToError("restart", func(err error) { defer config.DeferPanicToError("restart", func(err error) {
Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error()) Log(pb.LogLevel_FATAL, pb.LogType_CORE, err.Error())
StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error()) StopAndAlert(pb.MessageType_UNEXPECTED_ERROR, err.Error())
}) })
log.Debug("[Service] Restarting") log.Debug("[Service] Restarting")
if CoreState != pb.CoreState_STARTED { if CoreState != pb.CoreState_STARTED {
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_INSTANCE_NOT_STARTED, MessageType: pb.MessageType_INSTANCE_NOT_STARTED,
Message: "instance is not started", Message: "instance is not started",
}, fmt.Errorf("instance not started") }, fmt.Errorf("instance not started")
} }
if Box == nil { if Box == nil {
return &pb.CoreInfoResponse{ return &pb.CoreInfoResponse{
CoreState: CoreState, CoreState: CoreState,
MessageType: pb.MessageType_INSTANCE_NOT_FOUND, MessageType: pb.MessageType_INSTANCE_NOT_FOUND,
Message: "instance is not found", Message: "instance is not found",
}, fmt.Errorf("instance not found") }, fmt.Errorf("instance not found")
} }
resp, err := s.Stop(ctx, &pb.Empty{}) resp, err := s.Stop(ctx, &pb.Empty{})
if err != nil { if err != nil {
return resp, err return resp, err
} }
SetCoreStatus(pb.CoreState_STARTING, pb.MessageType_EMPTY, "") SetCoreStatus(pb.CoreState_STARTING, pb.MessageType_EMPTY, "")
<-time.After(250 * time.Millisecond) <-time.After(250 * time.Millisecond)
libbox.SetMemoryLimit(!in.DisableMemoryLimit) libbox.SetMemoryLimit(!in.DisableMemoryLimit)
resp, gErr := s.StartService(ctx, in) resp, gErr := s.StartService(ctx, in)
return resp, gErr return resp, gErr
} }

View File

@@ -1,62 +1,62 @@
package main package main
import ( import (
"context" "context"
"log" "log"
"time" "time"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
const ( const (
address = "localhost:50051" address = "localhost:50051"
defaultName = "world" defaultName = "world"
) )
func main() { func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure()) conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil { if err != nil {
log.Fatalf("did not connect: %v", err) log.Fatalf("did not connect: %v", err)
} }
defer conn.Close() defer conn.Close()
c := pb.NewHiddifyClient(conn) c := pb.NewHiddifyClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel() defer cancel()
// SayHello // SayHello
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: defaultName}) r, err := c.SayHello(ctx, &pb.HelloRequest{Name: defaultName})
if err != nil { if err != nil {
log.Fatalf("could not greet: %v", err) log.Fatalf("could not greet: %v", err)
} }
log.Printf("Greeting: %s", r.Message) log.Printf("Greeting: %s", r.Message)
// SayHelloStream // SayHelloStream
stream, err := c.SayHelloStream(ctx) stream, err := c.SayHelloStream(ctx)
if err != nil { if err != nil {
log.Fatalf("could not stream: %v", err) log.Fatalf("could not stream: %v", err)
} }
names := []string{"Alice", "Bob", "Charlie"} names := []string{"Alice", "Bob", "Charlie"}
for _, name := range names { for _, name := range names {
err := stream.Send(&pb.HelloRequest{Name: name}) err := stream.Send(&pb.HelloRequest{Name: name})
if err != nil { if err != nil {
log.Fatalf("could not send: %v", err) log.Fatalf("could not send: %v", err)
} }
r, err := stream.Recv() r, err := stream.Recv()
if err != nil { if err != nil {
log.Fatalf("could not receive: %v", err) log.Fatalf("could not receive: %v", err)
} }
log.Printf("Received1: %s", r.Message) log.Printf("Received1: %s", r.Message)
r2, err2 := stream.Recv() r2, err2 := stream.Recv()
if err2 != nil { if err2 != nil {
log.Fatalf("could not receive2: %v", err2) log.Fatalf("could not receive2: %v", err2)
} }
log.Printf("Received: %s", r2.Message) log.Printf("Received: %s", r2.Message)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} }
} }

View File

@@ -1,18 +1,18 @@
package main package main
import ( import (
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
v2 "github.com/hiddify/libcore/v2" v2 "github.com/hiddify/libcore/v2"
) )
func main() { func main() {
// defer C.free(unsafe.Pointer(port)) // defer C.free(unsafe.Pointer(port))
v2.StartGrpcServerGo("127.0.0.1:50051") v2.StartGrpcServerGo("127.0.0.1:50051")
sigChan := make(chan os.Signal, 1) sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan <-sigChan
} }

View File

@@ -1,49 +1,49 @@
package v2 package v2
/* /*
#include "stdint.h" #include "stdint.h"
*/ */
import "C" import "C"
import ( import (
"log" "log"
"net" "net"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type server struct { type server struct {
pb.UnimplementedHiddifyServer pb.UnimplementedHiddifyServer
} }
//export StartGrpcServer //export StartGrpcServer
func StartGrpcServer(listenAddress *C.char) (CErr *C.char) { func StartGrpcServer(listenAddress *C.char) (CErr *C.char) {
//Example Listen Address: "127.0.0.1:50051" //Example Listen Address: "127.0.0.1:50051"
err := StartGrpcServerGo(C.GoString(listenAddress)) err := StartGrpcServerGo(C.GoString(listenAddress))
if err != nil { if err != nil {
return C.CString(err.Error()) return C.CString(err.Error())
} }
return nil return nil
} }
func StartGrpcServerGo(listenAddressG string) error { func StartGrpcServerGo(listenAddressG string) error {
//Example Listen Address: "127.0.0.1:50051" //Example Listen Address: "127.0.0.1:50051"
// defer C.free(unsafe.Pointer(CErr)) // free the C string when it's no longer needed // defer C.free(unsafe.Pointer(CErr)) // free the C string when it's no longer needed
// defer C.free(unsafe.Pointer(listenAddress)) // free the C string when it's no longer needed // defer C.free(unsafe.Pointer(listenAddress)) // free the C string when it's no longer needed
lis, err := net.Listen("tcp", listenAddressG) lis, err := net.Listen("tcp", listenAddressG)
if err != nil { if err != nil {
log.Printf("failed to listen: %v", err) log.Printf("failed to listen: %v", err)
return err return err
} }
s := grpc.NewServer() s := grpc.NewServer()
pb.RegisterHiddifyServer(s, &server{}) pb.RegisterHiddifyServer(s, &server{})
log.Printf("Server listening on %s", listenAddressG) log.Printf("Server listening on %s", listenAddressG)
go func() { go func() {
if err := s.Serve(lis); err != nil { if err := s.Serve(lis); err != nil {
log.Printf("failed to serve: %v", err) log.Printf("failed to serve: %v", err)
} }
}() }()
return nil return nil
} }

View File

@@ -1,36 +1,36 @@
package v2 package v2
import ( import (
"context" "context"
"log" "log"
"time" "time"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
) )
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
return &pb.HelloResponse{Message: "Hello, " + in.Name}, nil return &pb.HelloResponse{Message: "Hello, " + in.Name}, nil
} }
func (s *server) SayHelloStream(stream pb.Hiddify_SayHelloStreamServer) error { func (s *server) SayHelloStream(stream pb.Hiddify_SayHelloStreamServer) error {
for { for {
req, err := stream.Recv() req, err := stream.Recv()
if err != nil { if err != nil {
log.Printf("stream.Recv() failed: %v", err) log.Printf("stream.Recv() failed: %v", err)
break break
} }
log.Printf("Received: %v", req.Name) log.Printf("Received: %v", req.Name)
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
err = stream.Send(&pb.HelloResponse{Message: "Hello, " + req.Name}) err = stream.Send(&pb.HelloResponse{Message: "Hello, " + req.Name})
if err != nil { if err != nil {
log.Printf("stream.Send() failed: %v", err) log.Printf("stream.Send() failed: %v", err)
break break
} }
err = stream.Send(&pb.HelloResponse{Message: "Hello again, " + req.Name}) err = stream.Send(&pb.HelloResponse{Message: "Hello again, " + req.Name})
if err != nil { if err != nil {
log.Printf("stream.Send() failed: %v", err) log.Printf("stream.Send() failed: %v", err)
break break
} }
} }
return nil return nil
} }

View File

@@ -1,41 +1,41 @@
package v2 package v2
import ( import (
"time" "time"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/common/observable"
) )
var logObserver = observable.Observer[pb.LogMessage]{} var logObserver = observable.Observer[pb.LogMessage]{}
func Log(level pb.LogLevel, typ pb.LogType, message string) { func Log(level pb.LogLevel, typ pb.LogType, message string) {
logObserver.Emit(pb.LogMessage{ logObserver.Emit(pb.LogMessage{
Level: level, Level: level,
Type: typ, Type: typ,
Message: message, Message: message,
}) })
} }
func (s *server) LogListener(stream pb.Hiddify_LogListenerServer) error { func (s *server) LogListener(stream pb.Hiddify_LogListenerServer) error {
logSub, _, _ := logObserver.Subscribe() logSub, _, _ := logObserver.Subscribe()
defer logObserver.UnSubscribe(logSub) defer logObserver.UnSubscribe(logSub)
stopch := make(chan int) stopch := make(chan int)
go func() { go func() {
stream.Recv() stream.Recv()
close(stopch) close(stopch)
}() }()
for { for {
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
break break
case <-stopch: case <-stopch:
break break
case info := <-logSub: case info := <-logSub:
stream.Send(&info) stream.Send(&info)
case <-time.After(500 * time.Millisecond): case <-time.After(500 * time.Millisecond):
} }
} }
} }

View File

@@ -1,65 +1,65 @@
package v2 package v2
import ( import (
"context" "context"
"github.com/hiddify/libcore/config" "github.com/hiddify/libcore/config"
pb "github.com/hiddify/libcore/hiddifyrpc" pb "github.com/hiddify/libcore/hiddifyrpc"
"github.com/sagernet/sing-box/experimental/libbox" "github.com/sagernet/sing-box/experimental/libbox"
) )
func (s *server) GenerateWarpConfig(ctx context.Context, in *pb.GenerateWarpConfigRequest) (*pb.WarpGenerationResponse, error) { func (s *server) GenerateWarpConfig(ctx context.Context, in *pb.GenerateWarpConfigRequest) (*pb.WarpGenerationResponse, error) {
account, log, wg, err := config.GenerateWarpInfo(in.LicenseKey, in.AccountId, in.AccessToken) account, log, wg, err := config.GenerateWarpInfo(in.LicenseKey, in.AccountId, in.AccessToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &pb.WarpGenerationResponse{ return &pb.WarpGenerationResponse{
Account: &pb.WarpAccount{ Account: &pb.WarpAccount{
AccountId: account.AccountID, AccountId: account.AccountID,
AccessToken: account.AccessToken, AccessToken: account.AccessToken,
}, },
Config: &pb.WarpWireguardConfig{ Config: &pb.WarpWireguardConfig{
PrivateKey: wg.PrivateKey, PrivateKey: wg.PrivateKey,
LocalAddressIpv4: wg.LocalAddressIPv4, LocalAddressIpv4: wg.LocalAddressIPv4,
LocalAddressIpv6: wg.LocalAddressIPv6, LocalAddressIpv6: wg.LocalAddressIPv6,
PeerPublicKey: wg.PeerPublicKey, PeerPublicKey: wg.PeerPublicKey,
}, },
Log: log, Log: log,
}, nil }, nil
} }
// Implement the GetSystemProxyStatus method // Implement the GetSystemProxyStatus method
func (s *server) GetSystemProxyStatus(ctx context.Context, empty *pb.Empty) (*pb.SystemProxyStatus, error) { func (s *server) GetSystemProxyStatus(ctx context.Context, empty *pb.Empty) (*pb.SystemProxyStatus, error) {
status, err := libbox.NewStandaloneCommandClient().GetSystemProxyStatus() status, err := libbox.NewStandaloneCommandClient().GetSystemProxyStatus()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &pb.SystemProxyStatus{ return &pb.SystemProxyStatus{
Available: status.Available, Available: status.Available,
Enabled: status.Enabled, Enabled: status.Enabled,
}, nil }, nil
} }
// Implement the SetSystemProxyEnabled method // Implement the SetSystemProxyEnabled method
func (s *server) SetSystemProxyEnabled(ctx context.Context, in *pb.SetSystemProxyEnabledRequest) (*pb.Response, error) { func (s *server) SetSystemProxyEnabled(ctx context.Context, in *pb.SetSystemProxyEnabledRequest) (*pb.Response, error) {
err := libbox.NewStandaloneCommandClient().SetSystemProxyEnabled(in.IsEnabled) err := libbox.NewStandaloneCommandClient().SetSystemProxyEnabled(in.IsEnabled)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err != nil { if err != nil {
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_FAILED, ResponseCode: pb.ResponseCode_FAILED,
Message: err.Error(), Message: err.Error(),
}, err }, err
} }
return &pb.Response{ return &pb.Response{
ResponseCode: pb.ResponseCode_OK, ResponseCode: pb.ResponseCode_OK,
Message: "", Message: "",
}, nil }, nil
} }