fix(deps): migrate to chromedp 0.19 and drop the cdproto replace

Update chromedp to v0.19.1 and cdproto to v0.157.4, port the browser, reader and login code to the generic Action API, group both modules in Renovate, and move the Docker build to Go 1.27.
This commit is contained in:
2026-10-05 10:32:22 +01:00
parent 5d945156ba
commit 956aa3c0cb
9 changed files with 67 additions and 91 deletions
+3 -5
View File
@@ -66,7 +66,7 @@ func (b *Browser) ensureAllocator() (context.Context, error) {
b.allocCtx, b.allocCancel = chromedp.NewExecAllocator(context.Background(), opts...)
browserCtx, cancel := chromedp.NewContext(b.allocCtx)
if err := chromedp.Run(browserCtx, chromedp.Navigate("about:blank")); err != nil {
if err := chromedp.Do(browserCtx, chromedp.Navigate("about:blank")); err != nil {
b.startErr = fmt.Errorf("warm up browser: %w", err)
cancel()
return
@@ -144,10 +144,8 @@ func (b *Browser) NewContext(parentCtx context.Context, statePath string) (conte
// the first step of a fresh login (otherwise stale session cookies from
// a previous attempt can mask the new login's success).
func ClearAllCookies(ctx context.Context) error {
return chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
target := chromedp.FromContext(ctx).Target
return network.ClearBrowserCookies().Do(cdp.WithExecutor(ctx, target))
}))
_, err := chromedp.Call(ctx, network.ClearBrowserCookies, cdp.Empty{})
return err
}
// NewHeadedContext spawns a *separate* Chromium process with a visible
+18 -25
View File
@@ -7,7 +7,6 @@ import (
"net/url"
"os"
"strings"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/network"
@@ -76,10 +75,8 @@ func ApplyStorageStateCookies(ctx context.Context, statePath string) error {
return nil
}
return chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
target := chromedp.FromContext(ctx).Target
return network.SetCookies(cookies).Do(cdp.WithExecutor(ctx, target))
}))
_, err = chromedp.Call(ctx, network.SetCookies, network.SetCookiesParams{Cookies: cookies})
return err
}
// ApplyLocalStorageOnCurrentOrigin sets every localStorage entry that
@@ -99,8 +96,8 @@ func ApplyLocalStorageOnCurrentOrigin(ctx context.Context, statePath string) err
return nil
}
var currentURL string
if err := chromedp.Run(ctx, chromedp.Location(&currentURL)); err != nil {
currentURL, err := chromedp.Run(ctx, chromedp.Location())
if err != nil {
return fmt.Errorf("read current origin: %w", err)
}
currentOrigin := normalizeOrigin(currentURL)
@@ -123,7 +120,8 @@ func ApplyLocalStorageOnCurrentOrigin(ctx context.Context, statePath string) err
return nil
}
return chromedp.Run(ctx, chromedp.Evaluate(buildLocalStorageScript(entries), nil))
_, err = chromedp.Run(ctx, chromedp.Evaluate[chromedp.Void](buildLocalStorageScript(entries)))
return err
}
// CaptureStorageState reads every cookie set on the current target and
@@ -138,8 +136,8 @@ func CaptureStorageState(ctx context.Context, statePath string) error {
}
// 2) Current origin + localStorage
var currentURL string
if err := chromedp.Run(ctx, chromedp.Location(&currentURL)); err != nil {
currentURL, err := chromedp.Run(ctx, chromedp.Location())
if err != nil {
return fmt.Errorf("read location: %w", err)
}
origin := normalizeOrigin(currentURL)
@@ -182,14 +180,11 @@ func CaptureStorageState(ctx context.Context, statePath string) error {
// captureCDPCookies asks the current target for its full cookie list via
// the network domain.
func captureCDPCookies(ctx context.Context) ([]*network.Cookie, error) {
var out []*network.Cookie
err := chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
target := chromedp.FromContext(ctx).Target
var err error
out, err = network.GetCookies().Do(cdp.WithExecutor(ctx, target))
return err
}))
return out, err
res, err := chromedp.Call(ctx, network.GetCookies, network.GetCookiesParams{})
if err != nil {
return nil, err
}
return res.Cookies, nil
}
// captureLocalStorage serialises the current page's localStorage to a
@@ -200,8 +195,8 @@ func captureLocalStorage(ctx context.Context, origin string) (json.RawMessage, e
return nil, nil
}
const script = `JSON.stringify(Object.fromEntries(Object.keys(localStorage).map(function(k){return [k, localStorage.getItem(k)];})))`
var lsJSON string
if err := chromedp.Run(ctx, chromedp.Evaluate(script, &lsJSON)); err != nil {
lsJSON, err := chromedp.Run(ctx, chromedp.Evaluate[string](script))
if err != nil {
return nil, err
}
if lsJSON == "" || lsJSON == "[]" || lsJSON == "{}" {
@@ -257,9 +252,8 @@ type onDiskCookie struct {
}
// onDiskToCookieParam converts our on-disk shape into a CDP
// network.CookieParam. Session cookies (Expires <= 0) get a nil Expires
// pointer; persistent cookies get a *cdp.TimeSinceEpoch built from the
// Unix timestamp.
// network.CookieParam. Session cookies (Expires <= 0) keep a zero Expires;
// persistent cookies get a cdp.TimeSinceEpoch built from the Unix timestamp.
func onDiskToCookieParam(c onDiskCookie) *network.CookieParam {
p := &network.CookieParam{
Name: c.Name,
@@ -271,8 +265,7 @@ func onDiskToCookieParam(c onDiskCookie) *network.CookieParam {
SameSite: parseSameSite(c.SameSite),
}
if c.Expires > 0 {
t := cdp.TimeSinceEpoch(time.Unix(int64(c.Expires), 0))
p.Expires = &t
p.Expires = cdp.TimeSinceEpoch(float64(int64(c.Expires)))
}
return p
}
+6 -7
View File
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
)
@@ -13,8 +12,8 @@ import (
// a separate chromedp.Run so a slow extraction does not re-do navigation.
func (r *Reader) extractHTML(ctx context.Context, selector string) (string, error) {
if selector == "" {
var html string
if err := chromedp.Run(ctx, chromedp.OuterHTML("html", &html, chromedp.ByQuery)); err != nil {
html, err := chromedp.Run(ctx, chromedp.OuterHTML(chromedp.CSS("html")))
if err != nil {
return "", fmt.Errorf("read document: %w", err)
}
return html, nil
@@ -22,16 +21,16 @@ func (r *Reader) extractHTML(ctx context.Context, selector string) (string, erro
// Verify the selector matches at least one node; an empty result is
// almost always a caller mistake, not an empty page.
var nodes []*cdp.Node
if err := chromedp.Run(ctx, chromedp.Nodes(selector, &nodes, chromedp.ByQuery)); err != nil {
nodes, err := chromedp.Run(ctx, chromedp.Nodes(chromedp.CSS(selector)))
if err != nil {
return "", fmt.Errorf("locate %q: %w", selector, err)
}
if len(nodes) == 0 {
return "", fmt.Errorf("selector %q matched no elements", selector)
}
var html string
if err := chromedp.Run(ctx, chromedp.OuterHTML(selector, &html, chromedp.ByQuery)); err != nil {
html, err := chromedp.Run(ctx, chromedp.OuterHTML(chromedp.CSS(selector)))
if err != nil {
return "", fmt.Errorf("extract %q: %w", selector, err)
}
return html, nil
+11 -8
View File
@@ -94,12 +94,12 @@ func (r *Reader) Read(parentCtx context.Context, opts ReadOptions) (*ReadResult,
// Build the action chain. Cookies were already applied by NewContext
// (if a session exists). localStorage is applied post-navigation
// because it is origin-scoped.
actions := []chromedp.Action{
actions := []chromedp.Action[chromedp.Void]{
chromedp.Navigate(opts.URL),
chromedp.WaitReady("body"),
}
if statePath != "" {
actions = append(actions, chromedp.ActionFunc(func(ctx context.Context) error {
actions = append(actions, chromedp.Func(func(ctx context.Context, _ *chromedp.Target) error {
return browser.ApplyLocalStorageOnCurrentOrigin(ctx, statePath)
}))
}
@@ -109,14 +109,17 @@ func (r *Reader) Read(parentCtx context.Context, opts ReadOptions) (*ReadResult,
chromedp.Sleep(500*time.Millisecond),
)
if opts.WaitSelector != "" {
actions = append(actions, chromedp.WaitVisible(opts.WaitSelector, chromedp.ByQuery))
actions = append(actions, chromedp.WaitVisible(chromedp.CSS(opts.WaitSelector)))
}
actions = append(actions,
chromedp.Title(&title),
chromedp.Location(&finalURL),
)
actions = append(actions, chromedp.Func(func(ctx context.Context, t *chromedp.Target) (err error) {
if title, err = chromedp.Title()(ctx, t); err != nil {
return err
}
finalURL, err = chromedp.Location()(ctx, t)
return err
}))
if err := chromedp.Run(cdpCtx, actions...); err != nil {
if err := chromedp.Do(cdpCtx, actions...); err != nil {
return nil, fmt.Errorf("navigate: %w", err)
}
+10 -14
View File
@@ -8,7 +8,6 @@ import (
"strings"
"time"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/chromedp"
"github.com/lukaszraczylo/mcp-readitall/internal/browser"
"github.com/modelcontextprotocol/go-sdk/mcp"
@@ -78,11 +77,9 @@ func RegisterLogin(s *mcp.Server, pw *browser.Browser, sess *browser.SessionStor
return nil, LoginOutput{}, fmt.Errorf("clear cookies: %w", err)
}
var title string
if err := chromedp.Run(browserCtx,
if err := chromedp.Do(browserCtx,
chromedp.Navigate(in.URL),
chromedp.WaitReady("body"),
chromedp.Title(&title),
); err != nil {
return nil, LoginOutput{}, fmt.Errorf("navigate: %w", err)
}
@@ -146,8 +143,8 @@ func waitForLogin(ctx context.Context, pageCtx context.Context, in LoginInput, t
check := func() (bool, error) {
if in.SuccessURL != "" {
var current string
if err := chromedp.Run(pageCtx, chromedp.Location(&current)); err != nil {
current, err := chromedp.Run(pageCtx, chromedp.Location())
if err != nil {
return false, err
}
if strings.Contains(current, in.SuccessURL) {
@@ -155,16 +152,15 @@ func waitForLogin(ctx context.Context, pageCtx context.Context, in LoginInput, t
}
}
if in.SuccessSelector != "" {
var nodes []*cdp.Node
if err := chromedp.Run(pageCtx, chromedp.Nodes(in.SuccessSelector, &nodes, chromedp.ByQuery)); err != nil {
nodes, err := chromedp.Run(pageCtx, chromedp.Nodes(chromedp.CSS(in.SuccessSelector)))
if err != nil {
return false, err
}
if len(nodes) > 0 {
var visible bool
if err := chromedp.Run(pageCtx, chromedp.Evaluate(
visible, err := chromedp.Run(pageCtx, chromedp.Evaluate[bool](
`(()=>{const e=document.querySelector(`+jsQuote(in.SuccessSelector)+`);return !!e && e.getBoundingClientRect().height>0;})()`,
&visible,
)); err != nil {
))
if err != nil {
return false, err
}
if visible {
@@ -205,8 +201,8 @@ func waitForLogin(ctx context.Context, pageCtx context.Context, in LoginInput, t
// currentLocation returns the page's current URL or "" if the query
// fails (e.g. the browser has already been torn down).
func currentLocation(ctx context.Context) string {
var s string
if err := chromedp.Run(ctx, chromedp.Location(&s)); err != nil {
s, err := chromedp.Run(ctx, chromedp.Location())
if err != nil {
return ""
}
return s