148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"fn-registry/functions/browser"
|
|
)
|
|
|
|
// runBrowserOp executes a browser CDP operation by connecting to Chrome's
|
|
// remote debugging endpoint, dispatching the op, and closing the connection.
|
|
//
|
|
// Supported ops (passed via args.op):
|
|
// - list_tabs → []CdpTab
|
|
// - navigate(url) → ok
|
|
// - click_text(text, tag?, exact?) → ok
|
|
// - evaluate(expression) → string
|
|
// - screenshot(filename?) → path saved
|
|
// - type_text(selector, text) → ok
|
|
// - get_html(selector?) → html
|
|
//
|
|
// host + port come from manifest. Defaults: 127.0.0.1:9223.
|
|
func runBrowserOp(cap *Capability, op string, args map[string]any) (any, int, error) {
|
|
host := cap.ChromeCDPHost
|
|
if host == "" {
|
|
host = "127.0.0.1"
|
|
}
|
|
port := cap.ChromeCDPPort
|
|
if port == 0 {
|
|
port = 9223
|
|
}
|
|
|
|
// list_tabs uses HTTP only — no websocket.
|
|
if op == "list_tabs" {
|
|
tabs, err := browser.CdpListTabs(host, port)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"tabs": tabs}, 0, nil
|
|
}
|
|
|
|
// launch_chrome bootstraps Chrome with --remote-debugging-port.
|
|
// Idempotent: if the port is already serving CDP, ChromeLaunch's
|
|
// waitCDPReady connects to the existing process and returns success.
|
|
if op == "launch_chrome" {
|
|
opts := browser.ChromeLaunchOpts{Port: port}
|
|
if hl, ok := args["headless"].(bool); ok {
|
|
opts.Headless = hl
|
|
}
|
|
if udd, ok := args["user_data_dir"].(string); ok && udd != "" {
|
|
opts.UserDataDir = udd
|
|
}
|
|
if cp, ok := args["chrome_path"].(string); ok && cp != "" {
|
|
opts.ChromePath = cp
|
|
}
|
|
pid, err := browser.ChromeLaunch(opts)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"pid": pid, "port": port}, 0, nil
|
|
}
|
|
|
|
// Operations that need a websocket connection.
|
|
conn, err := browser.CdpConnectHost(host, port)
|
|
if err != nil {
|
|
return nil, -1, fmt.Errorf("cdp connect %s:%d: %w", host, port, err)
|
|
}
|
|
defer browser.CdpClose(conn, 0)
|
|
|
|
switch op {
|
|
case "navigate":
|
|
url, _ := args["url"].(string)
|
|
if url == "" {
|
|
return nil, -1, fmt.Errorf("navigate: url required")
|
|
}
|
|
if err := browser.CdpNavigate(conn, url); err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"ok": true, "url": url}, 0, nil
|
|
|
|
case "click_text":
|
|
text, _ := args["text"].(string)
|
|
if text == "" {
|
|
return nil, -1, fmt.Errorf("click_text: text required")
|
|
}
|
|
tag, _ := args["tag"].(string)
|
|
exact, _ := args["exact"].(bool)
|
|
opts := browser.FindByTextOpts{Tag: tag, Exact: exact}
|
|
if err := browser.CdpClickText(conn, text, opts); err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"ok": true, "clicked": text}, 0, nil
|
|
|
|
case "evaluate":
|
|
expr, _ := args["expression"].(string)
|
|
if expr == "" {
|
|
return nil, -1, fmt.Errorf("evaluate: expression required")
|
|
}
|
|
val, err := browser.CdpEvaluate(conn, expr)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"value": val}, 0, nil
|
|
|
|
case "screenshot":
|
|
dir := cap.ScreenshotDir
|
|
if dir == "" {
|
|
dir = filepath.Join("local_files", "screenshots")
|
|
}
|
|
fname, _ := args["filename"].(string)
|
|
if fname == "" {
|
|
fname = fmt.Sprintf("shot_%d.png", time.Now().UnixNano())
|
|
}
|
|
fname = strings.ReplaceAll(fname, "..", "_")
|
|
out := filepath.Join(dir, fname)
|
|
opts := browser.CdpScreenshotOpts{Format: "png"}
|
|
if fp, ok := args["full_page"].(bool); ok {
|
|
opts.FullPage = fp
|
|
}
|
|
if err := browser.CdpScreenshot(conn, out, opts); err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"path": out}, 0, nil
|
|
|
|
case "type_text":
|
|
text, _ := args["text"].(string)
|
|
if text == "" {
|
|
return nil, -1, fmt.Errorf("type_text: text required (will be typed into focused element)")
|
|
}
|
|
if err := browser.CdpTypeText(conn, text); err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"ok": true}, 0, nil
|
|
|
|
case "get_html":
|
|
html, err := browser.CdpGetHTML(conn)
|
|
if err != nil {
|
|
return nil, -1, err
|
|
}
|
|
return map[string]any{"html": html}, 0, nil
|
|
|
|
default:
|
|
return nil, -1, fmt.Errorf("browser op %q not supported (list_tabs|navigate|click_text|evaluate|screenshot|type_text|get_html)", op)
|
|
}
|
|
}
|