Add SwiftUI + Network Extension (LibXray/hev), parse Clash Meta YAML from subscription URLs into vless/trojan/hy2 share links, keep the selected server on connect, and ship Navis-3.8.2.13.ipa with versioned changelog. Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
3.3 KiB
Go
152 lines
3.3 KiB
Go
// Package naviscore is a gomobile-friendly wrapper that runs Xray or Hysteria2
|
|
// in-process (macOS Manager semantics: local SOCKS/HTTP listeners).
|
|
//
|
|
// Build:
|
|
//
|
|
// gomobile bind -target=ios,iossimulator -o ios/Vendor/NavisCore.xcframework ./mobile/naviscore
|
|
package naviscore
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
libxrayxray "github.com/xtls/libxray/xray"
|
|
)
|
|
|
|
var (
|
|
mu sync.Mutex
|
|
mode string // "xray" | "hy2" | ""
|
|
hy2Stop func() error
|
|
workDir string
|
|
)
|
|
|
|
// SetWorkDir sets a writable directory for runtime config files (extension container).
|
|
func SetWorkDir(dir string) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
workDir = dir
|
|
}
|
|
|
|
// StartXrayJSON runs Xray from a full config JSON string (SOCKS/HTTP inbounds included).
|
|
func StartXrayJSON(configJSON string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if mode != "" {
|
|
return errors.New("core already running")
|
|
}
|
|
if err := libxrayxray.RunXrayFromJSON(configJSON); err != nil {
|
|
return err
|
|
}
|
|
mode = "xray"
|
|
return nil
|
|
}
|
|
|
|
// StartXrayFile runs Xray from a config.json path.
|
|
func StartXrayFile(configPath string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if mode != "" {
|
|
return errors.New("core already running")
|
|
}
|
|
if err := libxrayxray.RunXray(configPath); err != nil {
|
|
return err
|
|
}
|
|
mode = "xray"
|
|
return nil
|
|
}
|
|
|
|
// StartHy2YAML writes YAML to workDir and starts the Hysteria2 client library.
|
|
// If the hysteria embed is unavailable in this build, returns a clear error.
|
|
func StartHy2YAML(configYAML string) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if mode != "" {
|
|
return errors.New("core already running")
|
|
}
|
|
dir := workDir
|
|
if dir == "" {
|
|
dir = os.TempDir()
|
|
}
|
|
if err := os.MkdirAll(dir, 0o700); err != nil {
|
|
return err
|
|
}
|
|
path := filepath.Join(dir, "hy2.yaml")
|
|
if err := os.WriteFile(path, []byte(configYAML), 0o600); err != nil {
|
|
return err
|
|
}
|
|
stop, err := startHysteria2(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
hy2Stop = stop
|
|
mode = "hy2"
|
|
return nil
|
|
}
|
|
|
|
// Stop stops whichever core is running.
|
|
func Stop() error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
var err error
|
|
switch mode {
|
|
case "xray":
|
|
err = libxrayxray.StopXray()
|
|
case "hy2":
|
|
if hy2Stop != nil {
|
|
err = hy2Stop()
|
|
hy2Stop = nil
|
|
}
|
|
}
|
|
mode = ""
|
|
return err
|
|
}
|
|
|
|
// IsRunning reports whether a core is active.
|
|
func IsRunning() bool {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
switch mode {
|
|
case "xray":
|
|
return libxrayxray.GetXrayState()
|
|
case "hy2":
|
|
return hy2Stop != nil
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// WaitPort waits until TCP 127.0.0.1:port accepts connections.
|
|
func WaitPort(port int, timeoutMs int) error {
|
|
if port <= 0 {
|
|
return fmt.Errorf("bad port")
|
|
}
|
|
deadline := time.Now().Add(time.Duration(timeoutMs) * time.Millisecond)
|
|
addr := net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port))
|
|
for time.Now().Before(deadline) {
|
|
c, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
|
|
if err == nil {
|
|
_ = c.Close()
|
|
return nil
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
return fmt.Errorf("локальный прокси :%d не поднялся", port)
|
|
}
|
|
|
|
// Version returns a short build label.
|
|
func Version() string {
|
|
return "naviscore/xray+hy2"
|
|
}
|
|
|
|
// PingJSON is a tiny helper so Swift can verify the framework linked.
|
|
func PingJSON() string {
|
|
b, _ := json.Marshal(map[string]any{"ok": true, "version": Version()})
|
|
return string(b)
|
|
}
|