Files
Local-Cursor/internal/client/lifecycle.go
FuDie0915 188225fae6 Ð޸´ȫÏîĿÉó²éÎÊÌⲢ·¢²¼ v1.1.9
¸ÄÓñ¾»ú¶ÀÁ¢ CA£¬»ָ´ CLI TLS УÑ飬±£»¤ƾ¾ÝÓëÅäÖò¢·¢±£´档
Ð޸´ Cursor Êý¾ݡ¢»Ự·¾¶¡¢´úÀíÉúÃüÖÜÆڡ¢ģÐÍÇëÇóÓë Skills Îļþ²Ù×÷¡£
Ϊ¶ÀÁ¢ Tab ·þÎñÔö¼ӿͻ§¶˼øȨ£¬Éý¼¶ÒÀÀµÓ빤¾ßÁ´£¬ÍêÉƷ¢²¼ԪÐÅϢͬ²½¡£

Ñé֤£ºÁ½¸ö Go ģ¿éµIJâÊÔ/vet¡¢¹ؼüģ¿é race¡¢ǰ¶˲âÊÔ/Éú²ú¹¹½¨/Éó¼ơ¢
Go ¿ɴï©¶´ɨÃèÓë macOS/Linux ¿ÉÐÐģ¿齻²æ±àÒë¾ùͨ¹ý¡£
Windows ¸ôÀëÆô¶¯ͨ¹ý£»δÍê³ɵĵã»÷ðÑ̺ÍԭÉúƽ̨Ñé֤¼ûÉó²鱨¸档
2026-11-10 09:59:34 +08:00

323 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}