Files
Local-Cursor/internal/client/lifecycle.go

323 lines
10 KiB
Go
Raw Normal View History

package client
import (
"context"
"errors"
"fmt"
"strings"
"time"
"cursor/internal/cursor"
"cursor/internal/logger"
"cursor/internal/mitm"
"cursor/internal/netproxy"
localruntime "cursor/internal/runtime"
"github.com/wailsapp/wails/v3/pkg/application"
)
// ProxyState 定义了当前模块中的 ProxyState 类型。
type ProxyState struct {
// ListenAddr 保留旧字段兼容前端缓存,实际值等于 proxyListenAddr。
ListenAddr string `json:"listenAddr"`
// Running 保留旧字段兼容前端缓存,实际值等于 proxyRunning。
Running bool `json:"running"`
// BackendListenAddr 表示嵌入式 backend 监听地址。
BackendListenAddr string `json:"backendListenAddr"`
// BackendRunning 表示嵌入式 backend 是否已启动。
BackendRunning bool `json:"backendRunning"`
// ProxyListenAddr 表示 MITM 代理监听地址。
ProxyListenAddr string `json:"proxyListenAddr"`
// ProxyRunning 表示 MITM 代理是否已启动。
ProxyRunning bool `json:"proxyRunning"`
// CLIHTTPSListenAddr 表示 CLI 专用 HTTPS+HTTP/2 监听地址。
CLIHTTPSListenAddr string `json:"cliHttpsListenAddr"`
// CLIHTTPSRunning 表示 CLI 专用 HTTPS 监听器是否已启动。
CLIHTTPSRunning bool `json:"cliHttpsRunning"`
// CursorSettingsApplied 表示宿主代理设置是否已注入。
CursorSettingsApplied bool `json:"cursorSettingsApplied"`
// NetProxySource 表示当前出站网络代理来源:system/env/direct。
NetProxySource string `json:"netProxySource"`
// NetProxyActive 表示当前出站网络代理是否启用。
NetProxyActive bool `json:"netProxyActive"`
// NetProxyUsingSystem 表示当前出站网络代理是否来自操作系统代理。
NetProxyUsingSystem bool `json:"netProxyUsingSystem"`
// NetProxyUsingEnv 表示当前出站网络代理是否来自环境变量。
NetProxyUsingEnv bool `json:"netProxyUsingEnv"`
// NetProxyHTTP 表示当前 HTTP 代理地址,已移除凭据。
NetProxyHTTP string `json:"netProxyHttp"`
// NetProxyHTTPS 表示当前 HTTPS 代理地址,已移除凭据。
NetProxyHTTPS string `json:"netProxyHttps"`
// NetProxyPACIgnored 表示检测到 PAC/自动代理但本轮按直连处理。
NetProxyPACIgnored bool `json:"netProxyPacIgnored"`
// NetProxyDescription 表示当前出站网络代理摘要,已移除凭据。
NetProxyDescription string `json:"netProxyDescription"`
// LastError 表示当前声明中的 LastError。
LastError string `json:"lastError"`
}
// StartProxy 用于处理与 StartProxy 相关的逻辑。
func (s *ProxyService) StartProxy() (ProxyState, error) {
if s == nil {
return ProxyState{}, fmt.Errorf("服务未初始化")
}
s.lifecycleMu.Lock()
defer s.lifecycleMu.Unlock()
if s.backend() != nil && s.backend().IsRunning() && s.proxyServer() != nil && s.proxyServer().IsRunning() {
return s.GetState(), nil
}
startedBackend, startedProxy, settingsAttempted := false, false, false
rollback := func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if startedProxy && s.proxyServer() != nil {
_ = s.proxyServer().Stop(ctx)
}
if settingsAttempted {
if err := s.ClearCursorSettings(); err != nil {
logger.Error("startup rollback failed", "error", err)
}
}
if startedBackend && s.backend() != nil {
_ = s.backend().Stop(ctx)
}
}
logger.Infof("start service requested config_path=%s logs_root=%s", s.configPath, s.logsRoot)
fail := func(step string, err error) (ProxyState, error) {
rollback()
logger.Errorf("start service failed step=%s err=%v", step, err)
s.setLastError(err)
s.emitState()
return s.GetState(), err
}
cfg, err := s.LoadUserConfig()
if err != nil {
return fail("load_user_config", err)
}
if err := s.ensureBackendHost(); err != nil {
return fail("ensure_backend_host", err)
}
if !s.backend().IsRunning() {
logger.Infof("starting embedded backend listen_addr=%s", s.backend().ListenAddr())
if err := s.backend().Start(); err != nil {
return fail("start_backend", err)
}
startedBackend = true
} else {
logger.Infof("embedded backend already running listen_addr=%s", s.backend().ListenAddr())
}
healthCtx, healthCancel := context.WithTimeout(context.Background(), backendReadyTimeout)
defer healthCancel()
if err := s.waitForBackend(healthCtx); err != nil {
return fail("wait_backend_ready", err)
}
logger.Infof("embedded backend ready listen_addr=%s", s.backend().ListenAddr())
if err := s.ensureProxy(cfg); err != nil {
return fail("ensure_proxy", err)
}
if s.proxyServer() != nil && !s.proxyServer().IsRunning() {
logger.Infof("starting mitm proxy listen_addr=%s", s.proxyServer().Snapshot().ListenAddr)
if err := s.proxyServer().Start(); err != nil {
return fail("start_mitm_proxy", err)
}
startedProxy = true
}
settingsAttempted = true
if err := s.ApplyCursorSettings(); err != nil {
return fail("apply_cursor_settings", err)
}
// 启动时注入账号信息
if err := cursor.InjectCursorUserInfo(localruntime.InjectAccountEmail, localruntime.InjectAuthToken); err != nil {
logger.Errorf("injectCursorUserInfo failed: %v", err)
// 不阻断启动,仅记录日志
}
s.setLastError(nil)
s.emitState()
state := s.GetState()
logger.Infof(
"start service completed backend_listen_addr=%s proxy_listen_addr=%s cursor_settings_applied=%t",
state.BackendListenAddr,
state.ProxyListenAddr,
state.CursorSettingsApplied,
)
return state, nil
}
// StopProxy 用于处理与 StopProxy 相关的逻辑。
// 各清理步骤失败时记录错误但继续执行后续步骤,保证 backend 最终被停止,
// 避免"Cursor 配置已清理但 backend 仍在运行"的不一致状态。
func (s *ProxyService) StopProxy() (ProxyState, error) {
if s == nil {
return ProxyState{}, nil
}
s.lifecycleMu.Lock()
defer s.lifecycleMu.Unlock()
logger.Infof("stop service requested")
var stopErrors []error
fail := func(step string, err error) {
if err == nil || errors.Is(err, context.Canceled) {
return
}
logger.Errorf("stop service failed step=%s err=%v", step, err)
stopErrors = append(stopErrors, fmt.Errorf("%s: %w", step, err))
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if s.proxyServer() != nil && s.proxyServer().IsRunning() {
logger.Infof("stopping mitm proxy listen_addr=%s", s.proxyServer().Snapshot().ListenAddr)
fail("stop_mitm_proxy", s.proxyServer().Stop(ctx))
}
fail("clear_cursor_settings", s.ClearCursorSettings())
if s.backend() != nil {
logger.Infof("stopping embedded backend listen_addr=%s", s.backend().ListenAddr())
fail("stop_backend", s.backend().Stop(ctx))
}
if len(stopErrors) > 0 {
stopErr := errors.Join(stopErrors...)
s.setLastError(stopErr)
s.emitState()
return s.GetState(), stopErr
}
s.setLastError(nil)
s.emitState()
state := s.GetState()
logger.Infof(
"stop service completed backend_running=%t proxy_running=%t cursor_settings_applied=%t",
state.BackendRunning,
state.ProxyRunning,
state.CursorSettingsApplied,
)
return state, nil
}
// GetState 用于处理与 GetState 相关的逻辑。
func (s *ProxyService) GetState() ProxyState {
var proxySnap mitm.Snapshot
if s.proxyServer() != nil {
proxySnap = s.proxyServer().Snapshot()
}
s.mu.RLock()
lastError := s.lastError
cursorSettingsApplied := s.cursorSettingsApplied
s.mu.RUnlock()
backendListenAddr := ""
backendRunning := false
cliHTTPSListenAddr := ""
cliHTTPSRunning := false
if s.backend() != nil {
backendListenAddr = s.backend().ListenAddr()
backendRunning = s.backend().IsRunning()
cliHTTPSListenAddr = s.backend().CLIHTTPSListenAddr()
cliHTTPSRunning = s.backend().CLIHTTPSRunning()
}
netProxy := netproxy.CurrentStatus()
return ProxyState{
ListenAddr: proxySnap.ListenAddr,
Running: proxySnap.Running,
BackendListenAddr: backendListenAddr,
BackendRunning: backendRunning,
ProxyListenAddr: proxySnap.ListenAddr,
ProxyRunning: proxySnap.Running,
CLIHTTPSListenAddr: cliHTTPSListenAddr,
CLIHTTPSRunning: cliHTTPSRunning,
CursorSettingsApplied: cursorSettingsApplied,
NetProxySource: netProxy.Source,
NetProxyActive: netProxy.Active,
NetProxyUsingSystem: netProxy.UsingSystemProxy,
NetProxyUsingEnv: netProxy.UsingEnvProxy,
NetProxyHTTP: netProxy.HTTPProxy,
NetProxyHTTPS: netProxy.HTTPSProxy,
NetProxyPACIgnored: netProxy.PACIgnored,
NetProxyDescription: netProxy.Description,
LastError: lastError,
}
}
// ClearLastError 用于处理与 ClearLastError 相关的逻辑。
func (s *ProxyService) ClearLastError() ProxyState {
s.setLastError(nil)
s.emitState()
return s.GetState()
}
// SetBaseURL 用于处理与 SetBaseURL 相关的逻辑。
func (s *ProxyService) SetBaseURL(baseURL string) (ProxyState, error) {
_ = strings.TrimSpace(baseURL)
err := fmt.Errorf("backend/proxy 地址已固定,不再支持直接修改 baseURL")
s.setLastError(err)
s.emitState()
return s.GetState(), err
}
// setLastError 用于处理与 setLastError 相关的逻辑。
func (s *ProxyService) setLastError(err error) {
s.mu.Lock()
defer s.mu.Unlock()
if err == nil {
s.lastError = ""
return
}
msg := strings.TrimSpace(err.Error())
if msg == "" {
msg = "unknown error"
}
s.lastError = msg
}
// emitState 用于处理与 emitState 相关的逻辑。
func (s *ProxyService) emitState() {
app := application.Get()
if app == nil {
return
}
state := s.GetState()
if state.Running {
state.LastError = ""
}
app.Event.Emit("proxy:state", state)
}
// ShutdownForQuit 用于处理与 ShutdownForQuit 相关的逻辑。
func (s *ProxyService) ShutdownForQuit() {
s.lifecycleMu.Lock()
defer s.lifecycleMu.Unlock()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
var finalErr error
if s.proxyServer() != nil {
if err := s.proxyServer().Stop(ctx); err != nil && !errors.Is(err, context.Canceled) {
finalErr = err
}
}
s.mu.RLock()
applied := s.cursorSettingsApplied
s.mu.RUnlock()
if applied {
if err := s.ClearCursorSettings(); err != nil {
finalErr = errors.Join(finalErr, err)
}
}
if s.backend() != nil {
if err := s.backend().Stop(ctx); err != nil && !errors.Is(err, context.Canceled) {
finalErr = errors.Join(finalErr, err)
}
}
if finalErr != nil {
s.setLastError(finalErr)
}
}
func (s *ProxyService) setCursorSettingsApplied(applied bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.cursorSettingsApplied = applied
}