From a6dd1f7499ad6743f274a2913929321518ce9124 Mon Sep 17 00:00:00 2001 From: Regstar2 Date: Mon, 7 Sep 2026 10:31:17 +0300 Subject: [PATCH 1/4] fix(vk): tolerate Edge launcher process handoff --- backend/vk_webview_windows.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/backend/vk_webview_windows.go b/backend/vk_webview_windows.go index 9e3b40b1..f91f07dd 100644 --- a/backend/vk_webview_windows.go +++ b/backend/vk_webview_windows.go @@ -40,6 +40,7 @@ type vkEdgeSession struct { port int nextID int64 closed atomic.Bool + cdpClosed atomic.Bool http *http.Client userAgent string } @@ -160,9 +161,10 @@ func (s *vkEdgeSession) waitForPageTarget(ctx context.Context) (vkEdgeTarget, er for { select { case <-ctx.Done(): + if s.closed.Load() { + return vkEdgeTarget{}, errors.New("Microsoft Edge не предоставил управляемое окно VK после запуска") + } return vkEdgeTarget{}, errors.New("Microsoft Edge не открыл окно VK вовремя") - case <-s.waitCh: - return vkEdgeTarget{}, errors.New("Microsoft Edge завершился до открытия VK") default: } @@ -199,6 +201,7 @@ func (s *vkEdgeSession) call(ctx context.Context, method string, params map[stri _ = s.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) } if err := s.conn.WriteJSON(message); err != nil { + s.cdpClosed.Store(true) return nil, err } @@ -210,6 +213,7 @@ func (s *vkEdgeSession) call(ctx context.Context, method string, params map[stri } _, payload, err := s.conn.ReadMessage() if err != nil { + s.cdpClosed.Store(true) return nil, err } var response vkCDPResponse @@ -307,6 +311,9 @@ func (s *vkEdgeSession) navigate(ctx context.Context, targetURL string) error { } func (s *vkEdgeSession) exited() bool { + if s.conn != nil { + return s.cdpClosed.Load() + } return s.closed.Load() } From 09ecc38b433d819c628a345a87ce0ae8dd764964 Mon Sep 17 00:00:00 2001 From: Regstar2 Date: Mon, 7 Sep 2026 10:31:19 +0300 Subject: [PATCH 2/4] fix(vk): detect auth window closure through CDP --- backend/vk_login_session_windows.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/vk_login_session_windows.go b/backend/vk_login_session_windows.go index 9cb20162..af5d6d8d 100644 --- a/backend/vk_login_session_windows.go +++ b/backend/vk_login_session_windows.go @@ -50,8 +50,6 @@ func waitForLegacyVKSession(ctx context.Context, session *vkEdgeSession) (bool, select { case <-ctx.Done(): return false, context.Canceled - case <-session.waitCh: - return false, errors.New("окно авторизации VK закрыто") case <-time.After(vkEdgePollInterval): } From 4a8e7ed45536d3c3a67daf6e8ff6f79b65109fd0 Mon Sep 17 00:00:00 2001 From: Regstar2 Date: Mon, 7 Sep 2026 10:31:21 +0300 Subject: [PATCH 3/4] fix(vk): keep OAuth alive after Edge launcher exit --- backend/vk_calls_oauth_windows.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/vk_calls_oauth_windows.go b/backend/vk_calls_oauth_windows.go index d26cc5b9..d9264306 100644 --- a/backend/vk_calls_oauth_windows.go +++ b/backend/vk_calls_oauth_windows.go @@ -180,8 +180,6 @@ func (s *vkEdgeSession) obtainVKCallsTokenViaBrowser(ctx context.Context) (vkLeg select { case <-waitCtx.Done(): return vkLegacyToken{}, fmt.Errorf("VK Calls OAuth не вернул access token (%s)", lastDiag) - case <-s.waitCh: - return vkLegacyToken{}, fmt.Errorf("окно VK Calls OAuth закрыто (%s)", lastDiag) case <-time.After(vkEdgePollInterval): } From 20f5624ed6544cca5ed7063051156c237f816148 Mon Sep 17 00:00:00 2001 From: Regstar2 Date: Mon, 7 Sep 2026 10:31:23 +0300 Subject: [PATCH 4/4] test(vk): cover Edge launcher handoff regression --- backend/vk_webview_windows_test.go | 74 ++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 backend/vk_webview_windows_test.go diff --git a/backend/vk_webview_windows_test.go b/backend/vk_webview_windows_test.go new file mode 100644 index 00000000..8c37b839 --- /dev/null +++ b/backend/vk_webview_windows_test.go @@ -0,0 +1,74 @@ +//go:build windows + +package backend + +import ( + "context" + "net" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "time" + + "github.com/gorilla/websocket" +) + +func TestWaitForPageTargetAllowsEdgeLauncherHandoff(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/json/list" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`[{"type":"page","url":"https://vk.ru/","webSocketDebuggerUrl":"ws://127.0.0.1/devtools/page/1"}]`)) + })) + defer server.Close() + + parsed, err := url.Parse(server.URL) + if err != nil { + t.Fatal(err) + } + _, portText, err := net.SplitHostPort(parsed.Host) + if err != nil { + t.Fatal(err) + } + port, err := strconv.Atoi(portText) + if err != nil { + t.Fatal(err) + } + + session := &vkEdgeSession{ + waitCh: make(chan error, 1), + port: port, + http: server.Client(), + } + session.waitCh <- nil + session.closed.Store(true) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + target, err := session.waitForPageTarget(ctx) + if err != nil { + t.Fatalf("launcher handoff must not abort CDP discovery: %v", err) + } + if target.WebSocketDebuggerURL == "" { + t.Fatal("expected a page target after launcher handoff") + } +} + +func TestVKEdgeSessionUsesCDPStateAfterLauncherHandoff(t *testing.T) { + session := &vkEdgeSession{conn: &websocket.Conn{}} + session.closed.Store(true) + + if session.exited() { + t.Fatal("launcher exit must not mark an attached CDP browser as exited") + } + + session.cdpClosed.Store(true) + if !session.exited() { + t.Fatal("CDP closure must mark the browser session as exited") + } +}