mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 03:27:02 +08:00
281 lines
9.0 KiB
Go
281 lines
9.0 KiB
Go
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"`
|
|
// 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) {
|
|
logger.Infof("start service requested config_path=%s logs_root=%s", s.configPath, s.logsRoot)
|
|
fail := func(step string, err error) (ProxyState, error) {
|
|
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.backendHost.IsRunning() {
|
|
logger.Infof("starting embedded backend listen_addr=%s", s.backendHost.ListenAddr())
|
|
if err := s.backendHost.Start(); err != nil {
|
|
return fail("start_backend", err)
|
|
}
|
|
} else {
|
|
logger.Infof("embedded backend already running listen_addr=%s", s.backendHost.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.backendHost.ListenAddr())
|
|
if err := s.ensureProxy(cfg); err != nil {
|
|
return fail("ensure_proxy", err)
|
|
}
|
|
|
|
// 启动时注入账号信息
|
|
if err := cursor.InjectCursorUserInfo(localruntime.InjectAccountEmail, localruntime.InjectAuthToken); err != nil {
|
|
logger.Errorf("injectCursorUserInfo failed: %v", err)
|
|
// 不阻断启动,仅记录日志
|
|
}
|
|
|
|
if s.proxy != nil && !s.proxy.IsRunning() {
|
|
logger.Infof("starting mitm proxy listen_addr=%s", s.proxy.Snapshot().ListenAddr)
|
|
if err := s.proxy.Start(); err != nil {
|
|
return fail("start_mitm_proxy", err)
|
|
}
|
|
}
|
|
|
|
if err := s.ApplyCursorSettings(); err != nil {
|
|
stopCtx, stopCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer stopCancel()
|
|
if s.proxy != nil {
|
|
_ = s.proxy.Stop(stopCtx)
|
|
}
|
|
_ = s.backendHost.Stop(stopCtx)
|
|
startErr := fmt.Errorf("服务已启动,但注入 Cursor 配置失败: %w", err)
|
|
logger.Errorf("start service failed step=apply_cursor_settings err=%v", startErr)
|
|
s.setLastError(startErr)
|
|
s.emitState()
|
|
return s.GetState(), startErr
|
|
}
|
|
|
|
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 相关的逻辑。
|
|
func (s *ProxyService) StopProxy() (ProxyState, error) {
|
|
logger.Infof("stop service requested")
|
|
fail := func(step string, err error) (ProxyState, error) {
|
|
logger.Errorf("stop service failed step=%s err=%v", step, err)
|
|
s.setLastError(err)
|
|
s.emitState()
|
|
return s.GetState(), err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if s.proxy != nil && s.proxy.IsRunning() {
|
|
logger.Infof("stopping mitm proxy listen_addr=%s", s.proxy.Snapshot().ListenAddr)
|
|
if err := s.proxy.Stop(ctx); err != nil {
|
|
return fail("stop_mitm_proxy", err)
|
|
}
|
|
}
|
|
|
|
if err := s.ClearCursorSettings(); err != nil {
|
|
return fail("clear_cursor_settings", err)
|
|
}
|
|
if s.backendHost != nil {
|
|
logger.Infof("stopping embedded backend listen_addr=%s", s.backendHost.ListenAddr())
|
|
if err := s.backendHost.Stop(ctx); err != nil && !errors.Is(err, context.Canceled) {
|
|
return fail("stop_backend", err)
|
|
}
|
|
}
|
|
|
|
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.proxy != nil {
|
|
proxySnap = s.proxy.Snapshot()
|
|
}
|
|
s.mu.RLock()
|
|
lastError := s.lastError
|
|
cursorSettingsApplied := s.cursorSettingsApplied
|
|
s.mu.RUnlock()
|
|
backendListenAddr := ""
|
|
backendRunning := false
|
|
if s.backendHost != nil {
|
|
backendListenAddr = s.backendHost.ListenAddr()
|
|
backendRunning = s.backendHost.IsRunning()
|
|
}
|
|
netProxy := netproxy.CurrentStatus()
|
|
return ProxyState{
|
|
ListenAddr: proxySnap.ListenAddr,
|
|
Running: proxySnap.Running,
|
|
BackendListenAddr: backendListenAddr,
|
|
BackendRunning: backendRunning,
|
|
ProxyListenAddr: proxySnap.ListenAddr,
|
|
ProxyRunning: proxySnap.Running,
|
|
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() {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
var finalErr error
|
|
|
|
if s.proxy != nil {
|
|
if err := s.proxy.Stop(ctx); err != nil && !errors.Is(err, context.Canceled) {
|
|
finalErr = err
|
|
}
|
|
}
|
|
if err := s.ClearCursorSettings(); err != nil {
|
|
finalErr = errors.Join(finalErr, err)
|
|
}
|
|
if s.backendHost != nil {
|
|
if err := s.backendHost.Stop(ctx); err != nil && !errors.Is(err, context.Canceled) {
|
|
finalErr = errors.Join(finalErr, err)
|
|
}
|
|
}
|
|
if s.cursorAccount != nil {
|
|
s.cursorAccount.Shutdown()
|
|
}
|
|
if finalErr != nil {
|
|
s.setLastError(finalErr)
|
|
}
|
|
}
|
|
|
|
func (s *ProxyService) setCursorSettingsApplied(applied bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.cursorSettingsApplied = applied
|
|
}
|