Release 2.7.3.1: harden proxy recovery, updates and local API.

Restore orphaned system proxy after crash, require update SHA-256, add macOS /api auth token, fix UDP ping false positives, HTTPS-only subscriptions, and keep the UI responsive during connect.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
M4
2026-07-29 18:48:54 +03:00
co-authored by Cursor
parent dc700f2bac
commit 041cbb1250
32 changed files with 346 additions and 108 deletions
+2 -1
View File
@@ -58,10 +58,11 @@ func main() {
}()
}
ln, uiURL, err := apphost.ListenLocal()
ln, uiURL, token, err := apphost.ListenLocal()
if err != nil {
fatalf("listen: %v", err)
}
a.APIToken = token
srv := &http.Server{Handler: a.Handler()}
go func() {
_ = srv.Serve(ln)
+18 -6
View File
@@ -203,7 +203,7 @@ func (a *app) getState() (uiState, error) {
CoreReady: coreReady,
CorePath: corePath,
ConfigPath: a.cfgPath,
Profiles: cfg.ListProfiles(),
Profiles: config.RedactProfileList(cfg.ListProfiles()),
Version: update.DisplayVersion(),
Update: a.updateStatus,
Pings: append([]netcheck.Result(nil), a.pings...),
@@ -263,30 +263,36 @@ func (a *app) connect() error {
// If already connected to another profile, disconnects first.
func (a *app) connectProfile(name string) error {
a.mu.Lock()
defer a.mu.Unlock()
name = strings.TrimSpace(name)
st := a.mgr.Status()
if st.Connected {
if name == "" || st.Profile == name {
a.mu.Unlock()
return nil
}
a.mu.Unlock()
if err := a.mgr.Disconnect(); err != nil {
return err
}
a.mu.Lock()
}
if name != "" {
if err := a.mgr.SetActiveProfile(name); err != nil {
a.mu.Unlock()
return err
}
}
if p, err := a.mgr.Config().ActiveProfile(); err != nil {
a.mu.Unlock()
return err
} else if strings.TrimSpace(p.Proxy) == "" {
a.mu.Unlock()
return fmt.Errorf("сначала вставьте ссылку сервера")
}
a.mu.Unlock()
// Do not hold a.mu across EnsureCore/Connect — getState polling would freeze the UI.
if _, err := a.mgr.EnsureCore(""); err != nil {
return err
}
@@ -301,8 +307,6 @@ func (a *app) disconnect() error {
}
func (a *app) installCore() (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
paths, err := a.mgr.EnsureAllCores()
if err != nil {
return "", err
@@ -352,32 +356,40 @@ func (a *app) pingBest(autoConnect bool) (pingBestResult, error) {
out.BestMs = best.Ms
a.mu.Lock()
defer a.mu.Unlock()
st := a.mgr.Status()
alreadyBest := st.Connected && st.Profile == best.Name
if st.Connected && !alreadyBest {
a.mu.Unlock()
if err := a.mgr.Disconnect(); err != nil {
return out, err
}
a.mu.Lock()
}
if !alreadyBest {
if err := a.mgr.SetActiveProfile(best.Name); err != nil {
a.mu.Unlock()
return out, err
}
}
out.Selected = true
if !autoConnect {
a.mu.Unlock()
return out, nil
}
if alreadyBest {
a.mu.Unlock()
out.Connected = true
return out, nil
}
if p, err := a.mgr.Config().ActiveProfile(); err != nil {
a.mu.Unlock()
return out, err
} else if strings.TrimSpace(p.Proxy) == "" {
a.mu.Unlock()
return out, fmt.Errorf("пустая ссылка у лучшего сервера")
}
a.mu.Unlock()
if _, err := a.mgr.EnsureCore(""); err != nil {
return out, err
}