@hera-al/browser-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +74 -0
- package/dist/core/cdp.d.ts +124 -0
- package/dist/core/cdp.helpers.d.ts +14 -0
- package/dist/core/cdp.helpers.js +148 -0
- package/dist/core/cdp.js +309 -0
- package/dist/core/chrome.d.ts +21 -0
- package/dist/core/chrome.executables.d.ts +10 -0
- package/dist/core/chrome.executables.js +559 -0
- package/dist/core/chrome.js +257 -0
- package/dist/core/chrome.profile-decoration.d.ts +11 -0
- package/dist/core/chrome.profile-decoration.js +148 -0
- package/dist/core/constants.d.ts +9 -0
- package/dist/core/constants.js +9 -0
- package/dist/core/profiles.d.ts +31 -0
- package/dist/core/profiles.js +99 -0
- package/dist/core/target-id.d.ts +12 -0
- package/dist/core/target-id.js +21 -0
- package/dist/data-dir.d.ts +2 -0
- package/dist/data-dir.js +6 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +125 -0
- package/dist/playwright/pw-role-snapshot.d.ts +32 -0
- package/dist/playwright/pw-role-snapshot.js +337 -0
- package/dist/playwright/pw-session.d.ts +119 -0
- package/dist/playwright/pw-session.js +530 -0
- package/dist/playwright/pw-tools-core.activity.d.ts +22 -0
- package/dist/playwright/pw-tools-core.activity.js +47 -0
- package/dist/playwright/pw-tools-core.d.ts +9 -0
- package/dist/playwright/pw-tools-core.downloads.d.ts +35 -0
- package/dist/playwright/pw-tools-core.downloads.js +186 -0
- package/dist/playwright/pw-tools-core.interactions.d.ts +104 -0
- package/dist/playwright/pw-tools-core.interactions.js +404 -0
- package/dist/playwright/pw-tools-core.js +9 -0
- package/dist/playwright/pw-tools-core.responses.d.ts +14 -0
- package/dist/playwright/pw-tools-core.responses.js +91 -0
- package/dist/playwright/pw-tools-core.shared.d.ts +7 -0
- package/dist/playwright/pw-tools-core.shared.js +50 -0
- package/dist/playwright/pw-tools-core.snapshot.d.ts +65 -0
- package/dist/playwright/pw-tools-core.snapshot.js +144 -0
- package/dist/playwright/pw-tools-core.state.d.ts +47 -0
- package/dist/playwright/pw-tools-core.state.js +154 -0
- package/dist/playwright/pw-tools-core.storage.d.ts +48 -0
- package/dist/playwright/pw-tools-core.storage.js +76 -0
- package/dist/playwright/pw-tools-core.trace.d.ts +13 -0
- package/dist/playwright/pw-tools-core.trace.js +26 -0
- package/dist/server/browser-context.d.ts +29 -0
- package/dist/server/browser-context.js +137 -0
- package/dist/server/browser-server.d.ts +7 -0
- package/dist/server/browser-server.js +49 -0
- package/dist/server/routes/act.d.ts +4 -0
- package/dist/server/routes/act.js +176 -0
- package/dist/server/routes/basic.d.ts +4 -0
- package/dist/server/routes/basic.js +36 -0
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +16 -0
- package/dist/server/routes/snapshot.d.ts +4 -0
- package/dist/server/routes/snapshot.js +143 -0
- package/dist/server/routes/storage.d.ts +4 -0
- package/dist/server/routes/storage.js +117 -0
- package/dist/server/routes/tabs.d.ts +4 -0
- package/dist/server/routes/tabs.js +51 -0
- package/dist/server/standalone.d.ts +9 -0
- package/dist/server/standalone.js +42 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +58 -0
- package/package.json +66 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ensurePageState, getPageForTargetId } from "./pw-session.js";
|
|
2
|
+
export async function cookiesGetViaPlaywright(opts) {
|
|
3
|
+
const page = await getPageForTargetId(opts);
|
|
4
|
+
ensurePageState(page);
|
|
5
|
+
const cookies = await page.context().cookies();
|
|
6
|
+
return { cookies };
|
|
7
|
+
}
|
|
8
|
+
export async function cookiesSetViaPlaywright(opts) {
|
|
9
|
+
const page = await getPageForTargetId(opts);
|
|
10
|
+
ensurePageState(page);
|
|
11
|
+
const cookie = opts.cookie;
|
|
12
|
+
if (!cookie.name || cookie.value === undefined) {
|
|
13
|
+
throw new Error("cookie name and value are required");
|
|
14
|
+
}
|
|
15
|
+
const hasUrl = typeof cookie.url === "string" && cookie.url.trim();
|
|
16
|
+
const hasDomainPath = typeof cookie.domain === "string" &&
|
|
17
|
+
cookie.domain.trim() &&
|
|
18
|
+
typeof cookie.path === "string" &&
|
|
19
|
+
cookie.path.trim();
|
|
20
|
+
if (!hasUrl && !hasDomainPath) {
|
|
21
|
+
throw new Error("cookie requires url, or domain+path");
|
|
22
|
+
}
|
|
23
|
+
await page.context().addCookies([cookie]);
|
|
24
|
+
}
|
|
25
|
+
export async function cookiesClearViaPlaywright(opts) {
|
|
26
|
+
const page = await getPageForTargetId(opts);
|
|
27
|
+
ensurePageState(page);
|
|
28
|
+
await page.context().clearCookies();
|
|
29
|
+
}
|
|
30
|
+
export async function storageGetViaPlaywright(opts) {
|
|
31
|
+
const page = await getPageForTargetId(opts);
|
|
32
|
+
ensurePageState(page);
|
|
33
|
+
const kind = opts.kind;
|
|
34
|
+
const key = typeof opts.key === "string" ? opts.key : undefined;
|
|
35
|
+
const values = await page.evaluate(({ kind: kind2, key: key2 }) => {
|
|
36
|
+
const store = kind2 === "session" ? globalThis.sessionStorage : globalThis.localStorage;
|
|
37
|
+
if (key2) {
|
|
38
|
+
const value = store.getItem(key2);
|
|
39
|
+
return value === null ? {} : { [key2]: value };
|
|
40
|
+
}
|
|
41
|
+
const out = {};
|
|
42
|
+
for (let i = 0; i < store.length; i += 1) {
|
|
43
|
+
const k = store.key(i);
|
|
44
|
+
if (!k) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const v = store.getItem(k);
|
|
48
|
+
if (v !== null) {
|
|
49
|
+
out[k] = v;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}, { kind, key });
|
|
54
|
+
return { values: values ?? {} };
|
|
55
|
+
}
|
|
56
|
+
export async function storageSetViaPlaywright(opts) {
|
|
57
|
+
const page = await getPageForTargetId(opts);
|
|
58
|
+
ensurePageState(page);
|
|
59
|
+
const key = String(opts.key ?? "");
|
|
60
|
+
if (!key) {
|
|
61
|
+
throw new Error("key is required");
|
|
62
|
+
}
|
|
63
|
+
await page.evaluate(({ kind, key: k, value }) => {
|
|
64
|
+
const store = kind === "session" ? globalThis.sessionStorage : globalThis.localStorage;
|
|
65
|
+
store.setItem(k, value);
|
|
66
|
+
}, { kind: opts.kind, key, value: String(opts.value ?? "") });
|
|
67
|
+
}
|
|
68
|
+
export async function storageClearViaPlaywright(opts) {
|
|
69
|
+
const page = await getPageForTargetId(opts);
|
|
70
|
+
ensurePageState(page);
|
|
71
|
+
await page.evaluate(({ kind }) => {
|
|
72
|
+
const store = kind === "session" ? globalThis.sessionStorage : globalThis.localStorage;
|
|
73
|
+
store.clear();
|
|
74
|
+
}, { kind: opts.kind });
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=pw-tools-core.storage.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function traceStartViaPlaywright(opts: {
|
|
2
|
+
cdpUrl: string;
|
|
3
|
+
targetId?: string;
|
|
4
|
+
screenshots?: boolean;
|
|
5
|
+
snapshots?: boolean;
|
|
6
|
+
sources?: boolean;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
export declare function traceStopViaPlaywright(opts: {
|
|
9
|
+
cdpUrl: string;
|
|
10
|
+
targetId?: string;
|
|
11
|
+
path: string;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=pw-tools-core.trace.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ensureContextState, getPageForTargetId } from "./pw-session.js";
|
|
2
|
+
export async function traceStartViaPlaywright(opts) {
|
|
3
|
+
const page = await getPageForTargetId(opts);
|
|
4
|
+
const context = page.context();
|
|
5
|
+
const ctxState = ensureContextState(context);
|
|
6
|
+
if (ctxState.traceActive) {
|
|
7
|
+
throw new Error("Trace already running. Stop the current trace before starting a new one.");
|
|
8
|
+
}
|
|
9
|
+
await context.tracing.start({
|
|
10
|
+
screenshots: opts.screenshots ?? true,
|
|
11
|
+
snapshots: opts.snapshots ?? true,
|
|
12
|
+
sources: opts.sources ?? false,
|
|
13
|
+
});
|
|
14
|
+
ctxState.traceActive = true;
|
|
15
|
+
}
|
|
16
|
+
export async function traceStopViaPlaywright(opts) {
|
|
17
|
+
const page = await getPageForTargetId(opts);
|
|
18
|
+
const context = page.context();
|
|
19
|
+
const ctxState = ensureContextState(context);
|
|
20
|
+
if (!ctxState.traceActive) {
|
|
21
|
+
throw new Error("No active trace. Start a trace before stopping it.");
|
|
22
|
+
}
|
|
23
|
+
await context.tracing.stop({ path: opts.path });
|
|
24
|
+
ctxState.traceActive = false;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=pw-tools-core.trace.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ResolvedBrowserConfig, ResolvedBrowserProfile } from "../config.js";
|
|
2
|
+
export declare class BrowserContext {
|
|
3
|
+
private profiles;
|
|
4
|
+
private config;
|
|
5
|
+
constructor(config: ResolvedBrowserConfig);
|
|
6
|
+
updateConfig(config: ResolvedBrowserConfig): void;
|
|
7
|
+
getProfile(name?: string): ResolvedBrowserProfile;
|
|
8
|
+
private getRuntime;
|
|
9
|
+
ensureBrowserAvailable(profileName?: string): Promise<ResolvedBrowserProfile>;
|
|
10
|
+
stopBrowser(profileName?: string): Promise<void>;
|
|
11
|
+
isReachable(profileName?: string): Promise<boolean>;
|
|
12
|
+
listTabs(profileName?: string): Promise<Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
title: string;
|
|
15
|
+
url: string;
|
|
16
|
+
type: string;
|
|
17
|
+
}>>;
|
|
18
|
+
openTab(url: string, profileName?: string): Promise<{
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
url: string;
|
|
22
|
+
}>;
|
|
23
|
+
closeTab(tabId: string, profileName?: string): Promise<void>;
|
|
24
|
+
focusTab(tabId: string, profileName?: string): Promise<void>;
|
|
25
|
+
setLastTargetId(profileName: string, targetId: string): void;
|
|
26
|
+
getLastTargetId(profileName?: string): string | undefined;
|
|
27
|
+
stopAll(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=browser-context.d.ts.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { resolveProfile } from "../config.js";
|
|
2
|
+
import { isChromeReachable, launchChrome, stopChrome } from "../core/chrome.js";
|
|
3
|
+
import { fetchJson } from "../core/cdp.helpers.js";
|
|
4
|
+
import { appendCdpPath } from "../core/cdp.js";
|
|
5
|
+
import { createTargetViaCdp } from "../core/cdp.js";
|
|
6
|
+
import { createLogger } from "../logger.js";
|
|
7
|
+
import { closePlaywrightBrowserConnection, listPagesViaPlaywright, createPageViaPlaywright, closePageByTargetIdViaPlaywright, focusPageByTargetIdViaPlaywright, } from "../playwright/pw-session.js";
|
|
8
|
+
const log = createLogger("Browser:Context");
|
|
9
|
+
export class BrowserContext {
|
|
10
|
+
profiles = new Map();
|
|
11
|
+
config;
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
updateConfig(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
}
|
|
18
|
+
getProfile(name) {
|
|
19
|
+
const profileName = name || "default";
|
|
20
|
+
const resolved = resolveProfile(this.config, profileName);
|
|
21
|
+
if (!resolved) {
|
|
22
|
+
throw new Error(`Profile "${profileName}" not found`);
|
|
23
|
+
}
|
|
24
|
+
return resolved;
|
|
25
|
+
}
|
|
26
|
+
getRuntime(profile) {
|
|
27
|
+
let runtime = this.profiles.get(profile.name);
|
|
28
|
+
if (!runtime) {
|
|
29
|
+
runtime = { profile };
|
|
30
|
+
this.profiles.set(profile.name, runtime);
|
|
31
|
+
}
|
|
32
|
+
return runtime;
|
|
33
|
+
}
|
|
34
|
+
async ensureBrowserAvailable(profileName) {
|
|
35
|
+
const profile = this.getProfile(profileName);
|
|
36
|
+
const runtime = this.getRuntime(profile);
|
|
37
|
+
if (await isChromeReachable(profile.cdpUrl, 500)) {
|
|
38
|
+
return profile;
|
|
39
|
+
}
|
|
40
|
+
if (this.config.attachOnly) {
|
|
41
|
+
throw new Error(`Browser not reachable at ${profile.cdpUrl} (attachOnly mode)`);
|
|
42
|
+
}
|
|
43
|
+
if (!profile.cdpIsLoopback) {
|
|
44
|
+
throw new Error(`Remote browser at ${profile.cdpUrl} is not reachable`);
|
|
45
|
+
}
|
|
46
|
+
log.info(`Launching Chrome for profile "${profile.name}"...`);
|
|
47
|
+
runtime.chrome = await launchChrome(this.config, profile);
|
|
48
|
+
return profile;
|
|
49
|
+
}
|
|
50
|
+
async stopBrowser(profileName) {
|
|
51
|
+
const profile = this.getProfile(profileName);
|
|
52
|
+
const runtime = this.profiles.get(profile.name);
|
|
53
|
+
if (runtime?.chrome) {
|
|
54
|
+
await stopChrome(runtime.chrome);
|
|
55
|
+
runtime.chrome = undefined;
|
|
56
|
+
}
|
|
57
|
+
await closePlaywrightBrowserConnection();
|
|
58
|
+
}
|
|
59
|
+
async isReachable(profileName) {
|
|
60
|
+
const profile = this.getProfile(profileName);
|
|
61
|
+
return await isChromeReachable(profile.cdpUrl, 500);
|
|
62
|
+
}
|
|
63
|
+
async listTabs(profileName) {
|
|
64
|
+
const profile = this.getProfile(profileName);
|
|
65
|
+
try {
|
|
66
|
+
const listUrl = appendCdpPath(profile.cdpUrl, "/json/list");
|
|
67
|
+
const targets = await fetchJson(listUrl, 2000);
|
|
68
|
+
return targets.map(t => ({
|
|
69
|
+
id: t.id,
|
|
70
|
+
title: t.title || "",
|
|
71
|
+
url: t.url || "",
|
|
72
|
+
type: t.type || "page",
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fallback to Playwright
|
|
77
|
+
const pages = await listPagesViaPlaywright({ cdpUrl: profile.cdpUrl });
|
|
78
|
+
return pages.map(p => ({
|
|
79
|
+
id: p.targetId,
|
|
80
|
+
title: p.title,
|
|
81
|
+
url: p.url,
|
|
82
|
+
type: p.type,
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async openTab(url, profileName) {
|
|
87
|
+
const profile = this.getProfile(profileName);
|
|
88
|
+
try {
|
|
89
|
+
const result = await createTargetViaCdp({ cdpUrl: profile.cdpUrl, url });
|
|
90
|
+
return { id: result.targetId, title: "", url };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
const result = await createPageViaPlaywright({ cdpUrl: profile.cdpUrl, url });
|
|
94
|
+
return { id: result.targetId, title: result.title, url: result.url };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async closeTab(tabId, profileName) {
|
|
98
|
+
const profile = this.getProfile(profileName);
|
|
99
|
+
try {
|
|
100
|
+
const closeUrl = appendCdpPath(profile.cdpUrl, `/json/close/${tabId}`);
|
|
101
|
+
await fetch(closeUrl, { method: "GET" });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
await closePageByTargetIdViaPlaywright({ cdpUrl: profile.cdpUrl, targetId: tabId });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async focusTab(tabId, profileName) {
|
|
108
|
+
const profile = this.getProfile(profileName);
|
|
109
|
+
try {
|
|
110
|
+
const activateUrl = appendCdpPath(profile.cdpUrl, `/json/activate/${tabId}`);
|
|
111
|
+
await fetch(activateUrl, { method: "GET" });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
await focusPageByTargetIdViaPlaywright({ cdpUrl: profile.cdpUrl, targetId: tabId });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
setLastTargetId(profileName, targetId) {
|
|
118
|
+
const profile = this.getProfile(profileName);
|
|
119
|
+
const runtime = this.getRuntime(profile);
|
|
120
|
+
runtime.lastTargetId = targetId;
|
|
121
|
+
}
|
|
122
|
+
getLastTargetId(profileName) {
|
|
123
|
+
const name = profileName || "default";
|
|
124
|
+
return this.profiles.get(name)?.lastTargetId;
|
|
125
|
+
}
|
|
126
|
+
async stopAll() {
|
|
127
|
+
for (const [, runtime] of this.profiles) {
|
|
128
|
+
if (runtime.chrome) {
|
|
129
|
+
await stopChrome(runtime.chrome).catch(() => { });
|
|
130
|
+
runtime.chrome = undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
await closePlaywrightBrowserConnection().catch(() => { });
|
|
134
|
+
this.profiles.clear();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=browser-context.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ResolvedBrowserConfig } from "../config.js";
|
|
2
|
+
import { BrowserContext } from "./browser-context.js";
|
|
3
|
+
export declare function getBrowserContext(): BrowserContext | null;
|
|
4
|
+
export declare function startBrowserServer(config: ResolvedBrowserConfig): Promise<void>;
|
|
5
|
+
export declare function stopBrowserServer(): Promise<void>;
|
|
6
|
+
export declare function reconfigureBrowserServer(config: ResolvedBrowserConfig): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=browser-server.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { serve } from "@hono/node-server";
|
|
2
|
+
import { BrowserContext } from "./browser-context.js";
|
|
3
|
+
import { registerRoutes } from "./routes/index.js";
|
|
4
|
+
import { createLogger } from "../logger.js";
|
|
5
|
+
const log = createLogger("Browser:Server");
|
|
6
|
+
let serverHandle = null;
|
|
7
|
+
let browserContext = null;
|
|
8
|
+
export function getBrowserContext() {
|
|
9
|
+
return browserContext;
|
|
10
|
+
}
|
|
11
|
+
export async function startBrowserServer(config) {
|
|
12
|
+
if (serverHandle) {
|
|
13
|
+
log.warn("Browser server already running, stopping first");
|
|
14
|
+
await stopBrowserServer();
|
|
15
|
+
}
|
|
16
|
+
browserContext = new BrowserContext(config);
|
|
17
|
+
const app = registerRoutes(browserContext);
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
try {
|
|
20
|
+
serverHandle = serve({
|
|
21
|
+
fetch: app.fetch,
|
|
22
|
+
hostname: "127.0.0.1",
|
|
23
|
+
port: config.controlPort,
|
|
24
|
+
}, (info) => {
|
|
25
|
+
log.info(`Browser HTTP server listening on 127.0.0.1:${info.port}`);
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
reject(err);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function stopBrowserServer() {
|
|
35
|
+
if (browserContext) {
|
|
36
|
+
await browserContext.stopAll().catch(() => { });
|
|
37
|
+
browserContext = null;
|
|
38
|
+
}
|
|
39
|
+
if (serverHandle) {
|
|
40
|
+
serverHandle.close();
|
|
41
|
+
serverHandle = null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function reconfigureBrowserServer(config) {
|
|
45
|
+
if (browserContext) {
|
|
46
|
+
browserContext.updateConfig(config);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=browser-server.js.map
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import { clickViaPlaywright, typeViaPlaywright, pressKeyViaPlaywright, hoverViaPlaywright, evaluateViaPlaywright, waitForViaPlaywright, takeScreenshotViaPlaywright, scrollIntoViewViaPlaywright, dragViaPlaywright, selectOptionViaPlaywright, fillFormViaPlaywright, } from "../../playwright/pw-tools-core.interactions.js";
|
|
7
|
+
import { navigateViaPlaywright } from "../../playwright/pw-tools-core.snapshot.js";
|
|
8
|
+
export function actRoutes(ctx) {
|
|
9
|
+
const app = new Hono();
|
|
10
|
+
app.post("/act", async (c) => {
|
|
11
|
+
const body = await c.req.json();
|
|
12
|
+
const { kind, profile: profileName, targetId, ...params } = body;
|
|
13
|
+
if (!kind) {
|
|
14
|
+
return c.json({ ok: false, error: "kind is required" }, 400);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const profile = ctx.getProfile(profileName);
|
|
18
|
+
const cdpUrl = profile.cdpUrl;
|
|
19
|
+
const tid = targetId || ctx.getLastTargetId(profile.name);
|
|
20
|
+
let result;
|
|
21
|
+
switch (kind) {
|
|
22
|
+
case "navigate": {
|
|
23
|
+
const nav = await navigateViaPlaywright({
|
|
24
|
+
cdpUrl,
|
|
25
|
+
targetId: tid,
|
|
26
|
+
url: params.url,
|
|
27
|
+
timeoutMs: params.timeoutMs,
|
|
28
|
+
});
|
|
29
|
+
result = nav;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case "click": {
|
|
33
|
+
await clickViaPlaywright({
|
|
34
|
+
cdpUrl,
|
|
35
|
+
targetId: tid,
|
|
36
|
+
ref: params.ref,
|
|
37
|
+
doubleClick: params.doubleClick,
|
|
38
|
+
button: params.button,
|
|
39
|
+
modifiers: params.modifiers,
|
|
40
|
+
timeoutMs: params.timeoutMs,
|
|
41
|
+
});
|
|
42
|
+
result = { clicked: true };
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "type": {
|
|
46
|
+
await typeViaPlaywright({
|
|
47
|
+
cdpUrl,
|
|
48
|
+
targetId: tid,
|
|
49
|
+
ref: params.ref,
|
|
50
|
+
text: params.text,
|
|
51
|
+
submit: params.submit,
|
|
52
|
+
slowly: params.slowly,
|
|
53
|
+
timeoutMs: params.timeoutMs,
|
|
54
|
+
});
|
|
55
|
+
result = { typed: true };
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case "press": {
|
|
59
|
+
await pressKeyViaPlaywright({
|
|
60
|
+
cdpUrl,
|
|
61
|
+
targetId: tid,
|
|
62
|
+
key: params.key,
|
|
63
|
+
delayMs: params.delayMs,
|
|
64
|
+
});
|
|
65
|
+
result = { pressed: true };
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "hover": {
|
|
69
|
+
await hoverViaPlaywright({
|
|
70
|
+
cdpUrl,
|
|
71
|
+
targetId: tid,
|
|
72
|
+
ref: params.ref,
|
|
73
|
+
timeoutMs: params.timeoutMs,
|
|
74
|
+
});
|
|
75
|
+
result = { hovered: true };
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "scroll": {
|
|
79
|
+
await scrollIntoViewViaPlaywright({
|
|
80
|
+
cdpUrl,
|
|
81
|
+
targetId: tid,
|
|
82
|
+
ref: params.ref,
|
|
83
|
+
timeoutMs: params.timeoutMs,
|
|
84
|
+
});
|
|
85
|
+
result = { scrolled: true };
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "drag": {
|
|
89
|
+
await dragViaPlaywright({
|
|
90
|
+
cdpUrl,
|
|
91
|
+
targetId: tid,
|
|
92
|
+
startRef: params.startRef,
|
|
93
|
+
endRef: params.endRef,
|
|
94
|
+
timeoutMs: params.timeoutMs,
|
|
95
|
+
});
|
|
96
|
+
result = { dragged: true };
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
case "select": {
|
|
100
|
+
await selectOptionViaPlaywright({
|
|
101
|
+
cdpUrl,
|
|
102
|
+
targetId: tid,
|
|
103
|
+
ref: params.ref,
|
|
104
|
+
values: params.values,
|
|
105
|
+
timeoutMs: params.timeoutMs,
|
|
106
|
+
});
|
|
107
|
+
result = { selected: true };
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "fill_form": {
|
|
111
|
+
await fillFormViaPlaywright({
|
|
112
|
+
cdpUrl,
|
|
113
|
+
targetId: tid,
|
|
114
|
+
fields: params.fields,
|
|
115
|
+
timeoutMs: params.timeoutMs,
|
|
116
|
+
});
|
|
117
|
+
result = { filled: true };
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
case "screenshot": {
|
|
121
|
+
const shot = await takeScreenshotViaPlaywright({
|
|
122
|
+
cdpUrl,
|
|
123
|
+
targetId: tid,
|
|
124
|
+
ref: params.ref,
|
|
125
|
+
element: params.element,
|
|
126
|
+
fullPage: params.fullPage,
|
|
127
|
+
type: params.type,
|
|
128
|
+
});
|
|
129
|
+
const ext = params.type === "jpeg" ? "jpg" : "png";
|
|
130
|
+
const fileName = `screenshot-${crypto.randomUUID().slice(0, 8)}.${ext}`;
|
|
131
|
+
const dir = path.join(os.tmpdir(), "grabmeabeer", "screenshots");
|
|
132
|
+
await fs.mkdir(dir, { recursive: true });
|
|
133
|
+
const filePath = path.join(dir, fileName);
|
|
134
|
+
await fs.writeFile(filePath, shot.buffer);
|
|
135
|
+
result = { path: filePath, size: shot.buffer.length };
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case "evaluate": {
|
|
139
|
+
const evalResult = await evaluateViaPlaywright({
|
|
140
|
+
cdpUrl,
|
|
141
|
+
targetId: tid,
|
|
142
|
+
fn: params.fn,
|
|
143
|
+
ref: params.ref,
|
|
144
|
+
timeoutMs: params.timeoutMs,
|
|
145
|
+
});
|
|
146
|
+
result = { value: evalResult };
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
case "wait": {
|
|
150
|
+
await waitForViaPlaywright({
|
|
151
|
+
cdpUrl,
|
|
152
|
+
targetId: tid,
|
|
153
|
+
timeMs: params.timeMs,
|
|
154
|
+
text: params.text,
|
|
155
|
+
textGone: params.textGone,
|
|
156
|
+
selector: params.selector,
|
|
157
|
+
url: params.url,
|
|
158
|
+
loadState: params.loadState,
|
|
159
|
+
fn: params.fn,
|
|
160
|
+
timeoutMs: params.timeoutMs,
|
|
161
|
+
});
|
|
162
|
+
result = { waited: true };
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
return c.json({ ok: false, error: `Unknown action kind: ${kind}` }, 400);
|
|
167
|
+
}
|
|
168
|
+
return c.json({ ok: true, result });
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return app;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=act.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
export function basicRoutes(ctx) {
|
|
3
|
+
const app = new Hono();
|
|
4
|
+
app.get("/", async (c) => {
|
|
5
|
+
const profile = c.req.query("profile");
|
|
6
|
+
try {
|
|
7
|
+
const reachable = await ctx.isReachable(profile);
|
|
8
|
+
return c.json({ ok: true, status: reachable ? "running" : "stopped" });
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
return c.json({ ok: true, status: "stopped", error: String(err) });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
app.post("/start", async (c) => {
|
|
15
|
+
const body = await c.req.json().catch(() => ({}));
|
|
16
|
+
try {
|
|
17
|
+
const profile = await ctx.ensureBrowserAvailable(body.profile);
|
|
18
|
+
return c.json({ ok: true, profile: profile.name, cdpUrl: profile.cdpUrl });
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
app.post("/stop", async (c) => {
|
|
25
|
+
const body = await c.req.json().catch(() => ({}));
|
|
26
|
+
try {
|
|
27
|
+
await ctx.stopBrowser(body.profile);
|
|
28
|
+
return c.json({ ok: true });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return app;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=basic.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { basicRoutes } from "./basic.js";
|
|
3
|
+
import { tabsRoutes } from "./tabs.js";
|
|
4
|
+
import { actRoutes } from "./act.js";
|
|
5
|
+
import { snapshotRoutes } from "./snapshot.js";
|
|
6
|
+
import { storageRoutes } from "./storage.js";
|
|
7
|
+
export function registerRoutes(ctx) {
|
|
8
|
+
const app = new Hono();
|
|
9
|
+
app.route("/", basicRoutes(ctx));
|
|
10
|
+
app.route("/", tabsRoutes(ctx));
|
|
11
|
+
app.route("/", actRoutes(ctx));
|
|
12
|
+
app.route("/", snapshotRoutes(ctx));
|
|
13
|
+
app.route("/", storageRoutes(ctx));
|
|
14
|
+
return app;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=index.js.map
|