diff --git a/cmd/client/main.go b/cmd/client/main.go index 4071d4f..80982f0 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -26,13 +26,13 @@ import ( // manualCaptchaSolver bridges the vkauth.ManualSolveFunc contract to the // local captcha bouncer (internal/client/captcha/manual). -func manualCaptchaSolver(_ context.Context, e *captcha.Error, d net.Dialer) (string, string, error) { +func manualCaptchaSolver(ctx context.Context, e *captcha.Error, d net.Dialer) (string, string, error) { if e.RedirectURI != "" { - t, err := manualcaptcha.SolveViaProxy(e.RedirectURI, d) + t, err := manualcaptcha.SolveViaProxy(ctx, e.RedirectURI, d) return t, "", err } if e.CaptchaImg != "" { - k, err := manualcaptcha.SolveViaHTTP(e.CaptchaImg) + k, err := manualcaptcha.SolveViaHTTP(ctx, e.CaptchaImg) return "", k, err } return "", "", fmt.Errorf("no redirect_uri or captcha_img") diff --git a/notes/GLOSSARY.md b/docs/glossary.md similarity index 100% rename from notes/GLOSSARY.md rename to docs/glossary.md diff --git a/internal/client/captcha/manual/manual.go b/internal/client/captcha/manual/manual.go index d6d9a9c..7ba07a1 100644 --- a/internal/client/captcha/manual/manual.go +++ b/internal/client/captcha/manual/manual.go @@ -541,13 +541,24 @@ func startCaptchaServer(srv *http.Server, logPrefix string) error { } // runCaptchaServerAndWait triggers the browser, and waiting gracefully for the solution token. -func runCaptchaServerAndWait(handler http.Handler, captchaURL string, keyCh <-chan string, logPrefix string) (string, error) { +// Returns ctx.Err() if ctx fires before a key arrives; in both cases the HTTP server is shut down. +func runCaptchaServerAndWait(ctx context.Context, handler http.Handler, captchaURL string, keyCh <-chan string, logPrefix string) (string, error) { srv := &http.Server{Handler: handler} if err := startCaptchaServer(srv, logPrefix); err != nil { return "", err } + defer func() { + // Best-effort shutdown. On iSH SetDeadline is a no-op and Shutdown may + // time out while listeners drain; we still propagate the result. + shutCtx, shutCancel := context.WithTimeout(context.Background(), 3*time.Second) + defer shutCancel() + if err := srv.Shutdown(shutCtx); err != nil { + Log.Warnf("%s: shutdown warning: %v", logPrefix, err) + } + }() + fmt.Println("\n==============================================") fmt.Println("ACTION REQUIRED: MANUAL CAPTCHA SOLVING NEEDED") fmt.Println("If your browser didn't open automatically,") @@ -558,19 +569,12 @@ func runCaptchaServerAndWait(handler http.Handler, captchaURL string, keyCh <-ch Log.Infof("[%s] Opening browser...", logPrefix) openBrowser(captchaURL) - key := <-keyCh - - // Best-effort shutdown: the token is already received, so even if - // Shutdown times out (e.g. because ishConn.SetDeadline is a no-op - // on iSH and active connections can't be force-closed), we still - // return the token successfully. - shutCtx, shutCancel := context.WithTimeout(context.Background(), 3*time.Second) - defer shutCancel() - if err := srv.Shutdown(shutCtx); err != nil { - Log.Warnf("%s: shutdown warning (token already received): %v", logPrefix, err) + select { + case key := <-keyCh: + return key, nil + case <-ctx.Done(): + return "", ctx.Err() } - - return key, nil } // notifyKey pushes the key string to the given channel without blocking @@ -586,7 +590,7 @@ func notifyKey(keyCh chan<- string, key string) { // SolveViaHTTP serves a tiny HTML page asking the user to solve the // pictured CAPTCHA, opens it in the browser, and blocks until the key // is posted back. -func SolveViaHTTP(captchaImg string) (string, error) { +func SolveViaHTTP(ctx context.Context, captchaImg string) (string, error) { keyCh := make(chan string, 1) mux := http.NewServeMux() @@ -614,7 +618,7 @@ button{font-size:24px;padding:12px 32px;margin-top:12px;cursor:pointer} _, _ = fmt.Fprint(w, `