@flrande/browserctl 0.5.0-dev.22.1 → 0.6.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/dist/client.d.ts +34 -0
- package/dist/client.js +138 -0
- package/dist/commandRegistry.d.ts +16 -0
- package/dist/commandRegistry.js +21 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +24 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/runCli.d.ts +5 -0
- package/dist/runCli.js +170 -0
- package/package.json +32 -59
- package/INSTALL-CN.md +0 -92
- package/INSTALL.md +0 -92
- package/LICENSE +0 -21
- package/README-CN.md +0 -69
- package/README.md +0 -69
- package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
- package/apps/browserctl/src/commands/act.test.ts +0 -71
- package/apps/browserctl/src/commands/act.ts +0 -64
- package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
- package/apps/browserctl/src/commands/common.test.ts +0 -87
- package/apps/browserctl/src/commands/common.ts +0 -191
- package/apps/browserctl/src/commands/console-list.test.ts +0 -102
- package/apps/browserctl/src/commands/console-list.ts +0 -108
- package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
- package/apps/browserctl/src/commands/cookie-get.ts +0 -18
- package/apps/browserctl/src/commands/cookie-set.ts +0 -22
- package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
- package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
- package/apps/browserctl/src/commands/dom-query.ts +0 -18
- package/apps/browserctl/src/commands/download-trigger.ts +0 -22
- package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
- package/apps/browserctl/src/commands/download-wait.ts +0 -27
- package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
- package/apps/browserctl/src/commands/frame-list.ts +0 -16
- package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
- package/apps/browserctl/src/commands/har-export.test.ts +0 -112
- package/apps/browserctl/src/commands/har-export.ts +0 -120
- package/apps/browserctl/src/commands/memory-delete.ts +0 -20
- package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
- package/apps/browserctl/src/commands/memory-list.ts +0 -90
- package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
- package/apps/browserctl/src/commands/memory-purge.ts +0 -16
- package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
- package/apps/browserctl/src/commands/memory-status.ts +0 -16
- package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
- package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
- package/apps/browserctl/src/commands/network-list.test.ts +0 -110
- package/apps/browserctl/src/commands/network-list.ts +0 -112
- package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
- package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
- package/apps/browserctl/src/commands/profile-list.ts +0 -16
- package/apps/browserctl/src/commands/profile-use.ts +0 -18
- package/apps/browserctl/src/commands/response-body.ts +0 -24
- package/apps/browserctl/src/commands/screenshot.ts +0 -16
- package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
- package/apps/browserctl/src/commands/session-drop.ts +0 -16
- package/apps/browserctl/src/commands/session-list.test.ts +0 -81
- package/apps/browserctl/src/commands/session-list.ts +0 -70
- package/apps/browserctl/src/commands/snapshot.ts +0 -16
- package/apps/browserctl/src/commands/status.ts +0 -10
- package/apps/browserctl/src/commands/storage-get.ts +0 -20
- package/apps/browserctl/src/commands/storage-set.ts +0 -22
- package/apps/browserctl/src/commands/tab-close.ts +0 -20
- package/apps/browserctl/src/commands/tab-focus.ts +0 -20
- package/apps/browserctl/src/commands/tab-open.ts +0 -19
- package/apps/browserctl/src/commands/tabs.ts +0 -13
- package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
- package/apps/browserctl/src/commands/trace-get.ts +0 -62
- package/apps/browserctl/src/commands/upload-arm.ts +0 -26
- package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-element.ts +0 -76
- package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
- package/apps/browserctl/src/commands/wait-text.ts +0 -93
- package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
- package/apps/browserctl/src/commands/wait-url.ts +0 -76
- package/apps/browserctl/src/daemon-client.test.ts +0 -512
- package/apps/browserctl/src/daemon-client.ts +0 -632
- package/apps/browserctl/src/e2e.test.ts +0 -103
- package/apps/browserctl/src/main.dispatch.test.ts +0 -461
- package/apps/browserctl/src/main.test.ts +0 -334
- package/apps/browserctl/src/main.ts +0 -957
- package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
- package/apps/browserctl/src/test-port.ts +0 -26
- package/apps/browserd/src/bootstrap.ts +0 -432
- package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
- package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
- package/apps/browserd/src/container.ts +0 -3088
- package/apps/browserd/src/main.test.ts +0 -1522
- package/apps/browserd/src/main.ts +0 -7
- package/apps/browserd/src/test-port.ts +0 -26
- package/apps/browserd/src/tool-matrix.test.ts +0 -887
- package/bin/browserctl.cjs +0 -21
- package/bin/browserd.cjs +0 -21
- package/extensions/chrome-relay/README-CN.md +0 -39
- package/extensions/chrome-relay/README.md +0 -39
- package/extensions/chrome-relay/background.js +0 -1687
- package/extensions/chrome-relay/manifest.json +0 -15
- package/extensions/chrome-relay/popup.html +0 -369
- package/extensions/chrome-relay/popup.js +0 -972
- package/packages/core/src/bootstrap.test.ts +0 -10
- package/packages/core/src/driver-registry.test.ts +0 -45
- package/packages/core/src/driver-registry.ts +0 -22
- package/packages/core/src/driver.ts +0 -47
- package/packages/core/src/index.ts +0 -6
- package/packages/core/src/navigation-memory.test.ts +0 -259
- package/packages/core/src/navigation-memory.ts +0 -360
- package/packages/core/src/ref-cache.test.ts +0 -61
- package/packages/core/src/ref-cache.ts +0 -28
- package/packages/core/src/session-store.test.ts +0 -82
- package/packages/core/src/session-store.ts +0 -138
- package/packages/core/src/types.ts +0 -9
- package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
- package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
- package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
- package/packages/driver-chrome-relay/src/index.ts +0 -26
- package/packages/driver-managed/src/index.ts +0 -22
- package/packages/driver-managed/src/managed-driver.test.ts +0 -183
- package/packages/driver-managed/src/managed-driver.ts +0 -341
- package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
- package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
- package/packages/driver-remote-cdp/src/index.ts +0 -19
- package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
- package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
- package/packages/protocol/src/envelope.test.ts +0 -25
- package/packages/protocol/src/envelope.ts +0 -31
- package/packages/protocol/src/errors.test.ts +0 -17
- package/packages/protocol/src/errors.ts +0 -11
- package/packages/protocol/src/index.ts +0 -3
- package/packages/protocol/src/tools.ts +0 -3
- package/packages/transport-mcp-stdio/src/index.ts +0 -3
- package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
- package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
- package/packages/transport-mcp-stdio/src/server.ts +0 -183
- package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
import { once } from "node:events";
|
|
2
|
-
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
|
-
import type { AddressInfo } from "node:net";
|
|
4
|
-
|
|
5
|
-
import { describe, expect, it } from "vitest";
|
|
6
|
-
|
|
7
|
-
import { createManagedDriver } from "./index";
|
|
8
|
-
|
|
9
|
-
describe("createManagedDriver", () => {
|
|
10
|
-
it("returns status with managed kind", async () => {
|
|
11
|
-
const driver = createManagedDriver();
|
|
12
|
-
const status = await driver.status();
|
|
13
|
-
|
|
14
|
-
expect(status.kind).toBe("managed");
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("openTab creates a target and listTabs reflects it", async () => {
|
|
18
|
-
const driver = createManagedDriver();
|
|
19
|
-
|
|
20
|
-
expect(await driver.listTabs()).toEqual([]);
|
|
21
|
-
|
|
22
|
-
const firstTarget = await driver.openTab("https://example.com/a");
|
|
23
|
-
const secondTarget = await driver.openTab("https://example.com/b");
|
|
24
|
-
|
|
25
|
-
expect(firstTarget).not.toBe(secondTarget);
|
|
26
|
-
expect(await driver.listTabs()).toEqual([firstTarget, secondTarget]);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("closeTab removes the target from listTabs", async () => {
|
|
30
|
-
const driver = createManagedDriver();
|
|
31
|
-
const firstTarget = await driver.openTab("https://example.com/a");
|
|
32
|
-
const secondTarget = await driver.openTab("https://example.com/b");
|
|
33
|
-
|
|
34
|
-
await driver.closeTab(firstTarget);
|
|
35
|
-
|
|
36
|
-
expect(await driver.listTabs()).toEqual([secondTarget]);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("isolates tabs by profile", async () => {
|
|
40
|
-
const driver = createManagedDriver();
|
|
41
|
-
const alphaProfile = "profile:alpha";
|
|
42
|
-
const betaProfile = "profile:beta";
|
|
43
|
-
|
|
44
|
-
const alphaTarget = await driver.openTab("https://example.com/alpha", alphaProfile);
|
|
45
|
-
const betaTarget = await driver.openTab("https://example.com/beta", betaProfile);
|
|
46
|
-
|
|
47
|
-
expect(await driver.listTabs(alphaProfile)).toEqual([alphaTarget]);
|
|
48
|
-
expect(await driver.listTabs(betaProfile)).toEqual([betaTarget]);
|
|
49
|
-
expect(await driver.listTabs(alphaProfile)).not.toContain(betaTarget);
|
|
50
|
-
expect(await driver.listTabs(betaProfile)).not.toContain(alphaTarget);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it("focusTab uses targetId within the selected profile", async () => {
|
|
54
|
-
const driver = createManagedDriver();
|
|
55
|
-
const profile = "profile:alpha";
|
|
56
|
-
const target = await driver.openTab("https://example.com/alpha", profile);
|
|
57
|
-
|
|
58
|
-
await expect(driver.focusTab(target, profile)).resolves.toBeUndefined();
|
|
59
|
-
await expect(driver.focusTab(target, "profile:beta")).rejects.toThrowError(
|
|
60
|
-
`Unknown targetId: ${target} (profile: profile:beta)`
|
|
61
|
-
);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("tracks local URL/html state for navigate/login/download flows", async () => {
|
|
65
|
-
function sendHtml(response: ServerResponse, route: string): void {
|
|
66
|
-
response.statusCode = 200;
|
|
67
|
-
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
68
|
-
response.end(`<!doctype html><h1>Mock</h1><p>Route: ${route}</p>`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const server = createServer((request: IncomingMessage, response: ServerResponse) => {
|
|
72
|
-
if (request.url === "/app" && request.method === "GET") {
|
|
73
|
-
sendHtml(response, "/app");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (request.url === "/app/cart" && request.method === "GET") {
|
|
78
|
-
sendHtml(response, "/app/cart");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (request.url === "/api/login" && request.method === "POST") {
|
|
83
|
-
response.statusCode = 200;
|
|
84
|
-
response.setHeader("set-cookie", "mock_session=test; Path=/; HttpOnly");
|
|
85
|
-
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
86
|
-
response.end(JSON.stringify({ ok: true }));
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (request.url === "/download/orders.csv" && request.method === "GET") {
|
|
91
|
-
response.statusCode = 200;
|
|
92
|
-
response.setHeader("content-type", "text/csv; charset=utf-8");
|
|
93
|
-
response.end("order_id,total\n1,42\n");
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
response.statusCode = 404;
|
|
98
|
-
response.end("not-found");
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
server.listen(0, "127.0.0.1");
|
|
102
|
-
await once(server, "listening");
|
|
103
|
-
|
|
104
|
-
const address = server.address();
|
|
105
|
-
if (address === null || typeof address === "string") {
|
|
106
|
-
throw new Error("Expected server address info to be available.");
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const baseUrl = `http://127.0.0.1:${(address as AddressInfo).port}`;
|
|
110
|
-
const driver = createManagedDriver();
|
|
111
|
-
|
|
112
|
-
try {
|
|
113
|
-
const targetId = await driver.openTab(`${baseUrl}/app`);
|
|
114
|
-
const initialSnapshot = await driver.snapshot(targetId);
|
|
115
|
-
expect(initialSnapshot).toMatchObject({
|
|
116
|
-
hasTarget: true,
|
|
117
|
-
url: `${baseUrl}/app`
|
|
118
|
-
});
|
|
119
|
-
expect(initialSnapshot).toMatchObject({
|
|
120
|
-
html: expect.stringContaining("Route: /app")
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const loginResult = await driver.act(
|
|
124
|
-
{
|
|
125
|
-
type: "login",
|
|
126
|
-
payload: { path: "/api/login" }
|
|
127
|
-
},
|
|
128
|
-
targetId
|
|
129
|
-
);
|
|
130
|
-
expect(loginResult).toMatchObject({
|
|
131
|
-
actionType: "login",
|
|
132
|
-
targetKnown: true,
|
|
133
|
-
ok: true,
|
|
134
|
-
executed: true
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
const navigateResult = await driver.act(
|
|
138
|
-
{
|
|
139
|
-
type: "navigate",
|
|
140
|
-
payload: { path: "/app/cart" }
|
|
141
|
-
},
|
|
142
|
-
targetId
|
|
143
|
-
);
|
|
144
|
-
expect(navigateResult).toMatchObject({
|
|
145
|
-
actionType: "navigate",
|
|
146
|
-
targetKnown: true,
|
|
147
|
-
ok: true,
|
|
148
|
-
executed: true
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const postNavigateSnapshot = await driver.snapshot(targetId);
|
|
152
|
-
expect(postNavigateSnapshot).toMatchObject({
|
|
153
|
-
hasTarget: true,
|
|
154
|
-
url: `${baseUrl}/app/cart`
|
|
155
|
-
});
|
|
156
|
-
expect(postNavigateSnapshot).toMatchObject({
|
|
157
|
-
html: expect.stringContaining("Route: /app/cart")
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
const prepareDownloadResult = await driver.act(
|
|
161
|
-
{
|
|
162
|
-
type: "prepare-download",
|
|
163
|
-
payload: { path: "/download/orders.csv" }
|
|
164
|
-
},
|
|
165
|
-
targetId
|
|
166
|
-
);
|
|
167
|
-
expect(prepareDownloadResult).toMatchObject({
|
|
168
|
-
actionType: "prepare-download",
|
|
169
|
-
targetKnown: true,
|
|
170
|
-
ok: true,
|
|
171
|
-
executed: true
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
await expect(
|
|
175
|
-
driver.waitDownload(targetId, undefined, "C:\\downloads\\orders.csv")
|
|
176
|
-
).resolves.toEqual({
|
|
177
|
-
path: "C:\\downloads\\orders.csv"
|
|
178
|
-
});
|
|
179
|
-
} finally {
|
|
180
|
-
server.close();
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
});
|
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import type { BrowserDriver } from "../../core/src/driver";
|
|
2
|
-
import type { ProfileId, TargetId } from "../../core/src/types";
|
|
3
|
-
|
|
4
|
-
export type ManagedDriverStatus = {
|
|
5
|
-
kind: "managed";
|
|
6
|
-
connected: true;
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const DEFAULT_PROFILE_ID: ProfileId = "profile:managed:default";
|
|
10
|
-
|
|
11
|
-
type ManagedTab = {
|
|
12
|
-
url: string;
|
|
13
|
-
html: string;
|
|
14
|
-
cookieHeader?: string;
|
|
15
|
-
pendingDownloadUrl?: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
type ManagedProfileState = {
|
|
19
|
-
nextTargetNumber: number;
|
|
20
|
-
tabs: Map<TargetId, ManagedTab>;
|
|
21
|
-
tabOrder: TargetId[];
|
|
22
|
-
focusedTargetId?: TargetId;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
function resolveProfileId(profile?: ProfileId): ProfileId {
|
|
26
|
-
return profile ?? DEFAULT_PROFILE_ID;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function createTargetId(profileId: ProfileId, targetNumber: number): TargetId {
|
|
30
|
-
return `target:managed:${profileId}:${targetNumber}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function createProfileState(): ManagedProfileState {
|
|
34
|
-
return {
|
|
35
|
-
nextTargetNumber: 1,
|
|
36
|
-
tabs: new Map<TargetId, ManagedTab>(),
|
|
37
|
-
tabOrder: []
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function isObjectRecord(value: unknown): value is Record<string, unknown> {
|
|
42
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parseCookieHeader(response: Response): string | undefined {
|
|
46
|
-
const rawSetCookie = response.headers.get("set-cookie");
|
|
47
|
-
if (rawSetCookie === null) {
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const [cookieSegment] = rawSetCookie.split(";");
|
|
52
|
-
const cookieHeader = cookieSegment.trim();
|
|
53
|
-
return cookieHeader.length > 0 ? cookieHeader : undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function readPayloadString(
|
|
57
|
-
payload: Record<string, unknown>,
|
|
58
|
-
fieldName: string
|
|
59
|
-
): string | undefined {
|
|
60
|
-
const value = payload[fieldName];
|
|
61
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function requestTab(
|
|
65
|
-
tab: ManagedTab,
|
|
66
|
-
url: string,
|
|
67
|
-
init: RequestInit
|
|
68
|
-
): Promise<void> {
|
|
69
|
-
const headers = new Headers(init.headers ?? {});
|
|
70
|
-
if (tab.cookieHeader !== undefined && !headers.has("cookie")) {
|
|
71
|
-
headers.set("cookie", tab.cookieHeader);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const response = await fetch(url, {
|
|
75
|
-
...init,
|
|
76
|
-
headers
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const cookieHeader = parseCookieHeader(response);
|
|
80
|
-
if (cookieHeader !== undefined) {
|
|
81
|
-
tab.cookieHeader = cookieHeader;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const body = await response.text();
|
|
85
|
-
if (!response.ok) {
|
|
86
|
-
throw new Error(`${init.method ?? "GET"} ${url} failed with ${response.status}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
tab.url = response.url;
|
|
90
|
-
tab.html = body;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function createManagedDriver(): BrowserDriver<ManagedDriverStatus> {
|
|
94
|
-
const profileStates = new Map<ProfileId, ManagedProfileState>();
|
|
95
|
-
|
|
96
|
-
function getOrCreateProfileState(profileId: ProfileId): ManagedProfileState {
|
|
97
|
-
const existingState = profileStates.get(profileId);
|
|
98
|
-
if (existingState !== undefined) {
|
|
99
|
-
return existingState;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const createdState = createProfileState();
|
|
103
|
-
profileStates.set(profileId, createdState);
|
|
104
|
-
return createdState;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function requireTargetInProfile(profileId: ProfileId, targetId: TargetId): ManagedProfileState {
|
|
108
|
-
const profileState = profileStates.get(profileId);
|
|
109
|
-
if (profileState === undefined || !profileState.tabs.has(targetId)) {
|
|
110
|
-
throw new Error(`Unknown targetId: ${targetId} (profile: ${profileId})`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return profileState;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
status: async () => ({ kind: "managed", connected: true }),
|
|
118
|
-
listProfiles: async () => {
|
|
119
|
-
const knownProfiles = new Set<ProfileId>([DEFAULT_PROFILE_ID]);
|
|
120
|
-
for (const profileId of profileStates.keys()) {
|
|
121
|
-
knownProfiles.add(profileId);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return Array.from(knownProfiles).sort();
|
|
125
|
-
},
|
|
126
|
-
listTabs: async (profile) => {
|
|
127
|
-
const profileId = resolveProfileId(profile);
|
|
128
|
-
const profileState = profileStates.get(profileId);
|
|
129
|
-
return profileState === undefined ? [] : [...profileState.tabOrder];
|
|
130
|
-
},
|
|
131
|
-
openTab: async (url, profile) => {
|
|
132
|
-
const profileId = resolveProfileId(profile);
|
|
133
|
-
const profileState = getOrCreateProfileState(profileId);
|
|
134
|
-
const targetId = createTargetId(profileId, profileState.nextTargetNumber);
|
|
135
|
-
|
|
136
|
-
profileState.nextTargetNumber += 1;
|
|
137
|
-
const tab: ManagedTab = {
|
|
138
|
-
url,
|
|
139
|
-
html: ""
|
|
140
|
-
};
|
|
141
|
-
profileState.tabs.set(targetId, tab);
|
|
142
|
-
profileState.tabOrder.push(targetId);
|
|
143
|
-
if (profileState.focusedTargetId === undefined) {
|
|
144
|
-
profileState.focusedTargetId = targetId;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
await requestTab(tab, url, { method: "GET" });
|
|
149
|
-
} catch {
|
|
150
|
-
// Keep managed driver deterministic even when URL is unreachable.
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return targetId;
|
|
154
|
-
},
|
|
155
|
-
focusTab: async (targetId, profile) => {
|
|
156
|
-
const profileId = resolveProfileId(profile);
|
|
157
|
-
const profileState = requireTargetInProfile(profileId, targetId);
|
|
158
|
-
profileState.focusedTargetId = targetId;
|
|
159
|
-
},
|
|
160
|
-
closeTab: async (targetId, profile) => {
|
|
161
|
-
const profileId = resolveProfileId(profile);
|
|
162
|
-
const profileState = requireTargetInProfile(profileId, targetId);
|
|
163
|
-
|
|
164
|
-
profileState.tabs.delete(targetId);
|
|
165
|
-
profileState.tabOrder = profileState.tabOrder.filter((existingTargetId) => existingTargetId !== targetId);
|
|
166
|
-
if (profileState.focusedTargetId === targetId) {
|
|
167
|
-
profileState.focusedTargetId = profileState.tabOrder[0];
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
snapshot: async (targetId, profile) => {
|
|
171
|
-
const profileId = resolveProfileId(profile);
|
|
172
|
-
const tab = profileStates.get(profileId)?.tabs.get(targetId);
|
|
173
|
-
if (tab === undefined) {
|
|
174
|
-
return {
|
|
175
|
-
kind: "managed",
|
|
176
|
-
profile: profileId,
|
|
177
|
-
targetId,
|
|
178
|
-
hasTarget: false
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return {
|
|
183
|
-
kind: "managed",
|
|
184
|
-
profile: profileId,
|
|
185
|
-
targetId,
|
|
186
|
-
hasTarget: true,
|
|
187
|
-
url: tab.url,
|
|
188
|
-
html: tab.html
|
|
189
|
-
};
|
|
190
|
-
},
|
|
191
|
-
act: async (action, targetId, profile) => {
|
|
192
|
-
const profileId = resolveProfileId(profile);
|
|
193
|
-
const tab = profileStates.get(profileId)?.tabs.get(targetId);
|
|
194
|
-
if (tab === undefined) {
|
|
195
|
-
return {
|
|
196
|
-
actionType: action.type,
|
|
197
|
-
profile: profileId,
|
|
198
|
-
targetId,
|
|
199
|
-
targetKnown: false,
|
|
200
|
-
ok: false,
|
|
201
|
-
executed: false
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const payload = isObjectRecord(action.payload) ? action.payload : {};
|
|
206
|
-
if (action.type === "navigate" || action.type === "goto") {
|
|
207
|
-
const nextUrlValue = readPayloadString(payload, "url") ?? readPayloadString(payload, "path");
|
|
208
|
-
if (nextUrlValue === undefined) {
|
|
209
|
-
return {
|
|
210
|
-
actionType: action.type,
|
|
211
|
-
profile: profileId,
|
|
212
|
-
targetId,
|
|
213
|
-
targetKnown: true,
|
|
214
|
-
ok: false,
|
|
215
|
-
executed: true,
|
|
216
|
-
error: "action.payload.url or action.payload.path is required for navigate action."
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const nextUrl = new URL(nextUrlValue, tab.url).toString();
|
|
221
|
-
try {
|
|
222
|
-
await requestTab(tab, nextUrl, { method: "GET" });
|
|
223
|
-
} catch (error) {
|
|
224
|
-
return {
|
|
225
|
-
actionType: action.type,
|
|
226
|
-
profile: profileId,
|
|
227
|
-
targetId,
|
|
228
|
-
targetKnown: true,
|
|
229
|
-
ok: false,
|
|
230
|
-
executed: true,
|
|
231
|
-
error: error instanceof Error ? error.message : String(error)
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return {
|
|
236
|
-
actionType: action.type,
|
|
237
|
-
profile: profileId,
|
|
238
|
-
targetId,
|
|
239
|
-
targetKnown: true,
|
|
240
|
-
ok: true,
|
|
241
|
-
executed: true,
|
|
242
|
-
data: {
|
|
243
|
-
url: tab.url
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (action.type === "login") {
|
|
249
|
-
const loginPath = readPayloadString(payload, "path") ?? "/api/login";
|
|
250
|
-
const loginUrl = new URL(loginPath, tab.url).toString();
|
|
251
|
-
try {
|
|
252
|
-
await requestTab(tab, loginUrl, { method: "POST" });
|
|
253
|
-
} catch (error) {
|
|
254
|
-
return {
|
|
255
|
-
actionType: action.type,
|
|
256
|
-
profile: profileId,
|
|
257
|
-
targetId,
|
|
258
|
-
targetKnown: true,
|
|
259
|
-
ok: false,
|
|
260
|
-
executed: true,
|
|
261
|
-
error: error instanceof Error ? error.message : String(error)
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return {
|
|
266
|
-
actionType: action.type,
|
|
267
|
-
profile: profileId,
|
|
268
|
-
targetId,
|
|
269
|
-
targetKnown: true,
|
|
270
|
-
ok: true,
|
|
271
|
-
executed: true
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (action.type === "prepare-download") {
|
|
276
|
-
const downloadPath = readPayloadString(payload, "path");
|
|
277
|
-
if (downloadPath === undefined) {
|
|
278
|
-
return {
|
|
279
|
-
actionType: action.type,
|
|
280
|
-
profile: profileId,
|
|
281
|
-
targetId,
|
|
282
|
-
targetKnown: true,
|
|
283
|
-
ok: false,
|
|
284
|
-
executed: true,
|
|
285
|
-
error: "action.payload.path is required for prepare-download action."
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
tab.pendingDownloadUrl = new URL(downloadPath, tab.url).toString();
|
|
290
|
-
return {
|
|
291
|
-
actionType: action.type,
|
|
292
|
-
profile: profileId,
|
|
293
|
-
targetId,
|
|
294
|
-
targetKnown: true,
|
|
295
|
-
ok: true,
|
|
296
|
-
executed: true,
|
|
297
|
-
data: {
|
|
298
|
-
url: tab.pendingDownloadUrl
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
actionType: action.type,
|
|
305
|
-
profile: profileId,
|
|
306
|
-
targetId,
|
|
307
|
-
targetKnown: true,
|
|
308
|
-
ok: true,
|
|
309
|
-
executed: false
|
|
310
|
-
};
|
|
311
|
-
},
|
|
312
|
-
armUpload: async () => {},
|
|
313
|
-
armDialog: async () => {},
|
|
314
|
-
waitDownload: async (targetId, profile, path) => {
|
|
315
|
-
const profileId = resolveProfileId(profile);
|
|
316
|
-
const tab = profileStates.get(profileId)?.tabs.get(targetId);
|
|
317
|
-
if (tab === undefined) {
|
|
318
|
-
throw new Error(`Unknown targetId: ${targetId} (profile: ${profileId})`);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (tab.pendingDownloadUrl !== undefined) {
|
|
322
|
-
const headers = new Headers();
|
|
323
|
-
if (tab.cookieHeader !== undefined) {
|
|
324
|
-
headers.set("cookie", tab.cookieHeader);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
try {
|
|
328
|
-
await fetch(tab.pendingDownloadUrl, {
|
|
329
|
-
method: "GET",
|
|
330
|
-
headers
|
|
331
|
-
});
|
|
332
|
-
} finally {
|
|
333
|
-
tab.pendingDownloadUrl = undefined;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return { path: path ?? "managed-download.bin" };
|
|
338
|
-
},
|
|
339
|
-
triggerDownload: async () => {}
|
|
340
|
-
};
|
|
341
|
-
}
|