@phi-code-admin/browser 1.0.0 → 1.0.2
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/index.d.ts +21 -2
- package/dist/index.js +98 -23
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -30,11 +30,21 @@ export declare function closeAll(): Promise<void>;
|
|
|
30
30
|
export interface CreateTabResult {
|
|
31
31
|
tabId: string;
|
|
32
32
|
userId: string;
|
|
33
|
+
sessionKey: string;
|
|
33
34
|
url?: string;
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Open a new browser tab. Returns the tab id used by the other tools.
|
|
38
|
+
*
|
|
39
|
+
* The camofox-browser REST contract requires every tab to be associated
|
|
40
|
+
* with a `userId` (logical user) AND a `sessionKey` (logical session
|
|
41
|
+
* inside that user — used to group tabs that should share cookies /
|
|
42
|
+
* fingerprints / proxies). Phi-code's chat agents only need one of each,
|
|
43
|
+
* so both default to a constant sentinel when omitted.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createTab(options?: {
|
|
37
46
|
userId?: string;
|
|
47
|
+
sessionKey?: string;
|
|
38
48
|
url?: string;
|
|
39
49
|
viewport?: {
|
|
40
50
|
width: number;
|
|
@@ -56,6 +66,7 @@ export declare function navigate(options: {
|
|
|
56
66
|
url: string;
|
|
57
67
|
tabId?: string;
|
|
58
68
|
userId?: string;
|
|
69
|
+
sessionKey?: string;
|
|
59
70
|
waitUntil?: "load" | "domcontentloaded" | "networkidle";
|
|
60
71
|
timeoutMs?: number;
|
|
61
72
|
}): Promise<NavigateResult>;
|
|
@@ -65,6 +76,7 @@ export declare function navigate(options: {
|
|
|
65
76
|
*/
|
|
66
77
|
export declare function snapshot(options: {
|
|
67
78
|
tabId: string;
|
|
79
|
+
userId?: string;
|
|
68
80
|
}): Promise<unknown>;
|
|
69
81
|
export interface ExtractResult {
|
|
70
82
|
url?: string;
|
|
@@ -82,6 +94,7 @@ export interface ExtractResult {
|
|
|
82
94
|
export declare function extract(options: {
|
|
83
95
|
tabId?: string;
|
|
84
96
|
userId?: string;
|
|
97
|
+
sessionKey?: string;
|
|
85
98
|
url?: string;
|
|
86
99
|
mode?: "readability" | "html" | "text";
|
|
87
100
|
}): Promise<ExtractResult>;
|
|
@@ -93,6 +106,7 @@ export interface ScreenshotResult {
|
|
|
93
106
|
/** Capture a screenshot of the given tab as a base64-encoded PNG. */
|
|
94
107
|
export declare function screenshot(options: {
|
|
95
108
|
tabId: string;
|
|
109
|
+
userId?: string;
|
|
96
110
|
fullPage?: boolean;
|
|
97
111
|
clip?: {
|
|
98
112
|
x: number;
|
|
@@ -110,10 +124,12 @@ export declare function search(options: {
|
|
|
110
124
|
query: string;
|
|
111
125
|
engine?: "google" | "duckduckgo" | "bing";
|
|
112
126
|
userId?: string;
|
|
127
|
+
sessionKey?: string;
|
|
113
128
|
}): Promise<ExtractResult>;
|
|
114
129
|
/** Click an element by ref (from `snapshot`) or CSS selector. */
|
|
115
130
|
export declare function click(options: {
|
|
116
131
|
tabId: string;
|
|
132
|
+
userId?: string;
|
|
117
133
|
ref?: string;
|
|
118
134
|
selector?: string;
|
|
119
135
|
button?: "left" | "right" | "middle";
|
|
@@ -123,6 +139,7 @@ export declare function click(options: {
|
|
|
123
139
|
/** Type text into a focused element (or one targeted via ref/selector). */
|
|
124
140
|
export declare function type(options: {
|
|
125
141
|
tabId: string;
|
|
142
|
+
userId?: string;
|
|
126
143
|
text: string;
|
|
127
144
|
ref?: string;
|
|
128
145
|
selector?: string;
|
|
@@ -134,6 +151,7 @@ export declare function type(options: {
|
|
|
134
151
|
/** Scroll the page or a specific element by ref. */
|
|
135
152
|
export declare function scroll(options: {
|
|
136
153
|
tabId: string;
|
|
154
|
+
userId?: string;
|
|
137
155
|
direction: "up" | "down" | "left" | "right";
|
|
138
156
|
ref?: string;
|
|
139
157
|
pixels?: number;
|
|
@@ -143,6 +161,7 @@ export declare function scroll(options: {
|
|
|
143
161
|
/** Close a single tab. The underlying browser context is kept warm. */
|
|
144
162
|
export declare function closeTab(options: {
|
|
145
163
|
tabId: string;
|
|
164
|
+
userId?: string;
|
|
146
165
|
}): Promise<{
|
|
147
166
|
tabId: string;
|
|
148
167
|
}>;
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ let serverProcess = null;
|
|
|
26
26
|
let serverPort = null;
|
|
27
27
|
let bootPromise = null;
|
|
28
28
|
const DEFAULT_USER_ID = "phi-default";
|
|
29
|
+
const DEFAULT_SESSION_KEY = "phi-default-session";
|
|
29
30
|
const HEALTH_TIMEOUT_MS = 30_000;
|
|
30
31
|
const HEALTH_POLL_INTERVAL_MS = 250;
|
|
31
32
|
async function findAvailablePort() {
|
|
@@ -92,24 +93,58 @@ export async function ensureServer() {
|
|
|
92
93
|
stdio: ["ignore", "pipe", "pipe"],
|
|
93
94
|
detached: false,
|
|
94
95
|
});
|
|
95
|
-
// Surface
|
|
96
|
+
// Surface child stderr so the user can see crash reasons. Once the
|
|
97
|
+
// server has become healthy we go quiet again unless
|
|
98
|
+
// PHI_BROWSER_VERBOSE=1 is set. Boot-time crashes ALWAYS print —
|
|
99
|
+
// otherwise a silent E22-style "failed to become healthy" exception
|
|
100
|
+
// is unsurmountable from the consumer side.
|
|
101
|
+
const stderrTail = [];
|
|
102
|
+
let healthy = false;
|
|
96
103
|
child.stderr?.on("data", (chunk) => {
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
const text = chunk.toString();
|
|
105
|
+
if (!healthy || process.env.PHI_BROWSER_VERBOSE) {
|
|
106
|
+
process.stderr.write(`[camofox] ${text}`);
|
|
99
107
|
}
|
|
108
|
+
stderrTail.push(text);
|
|
109
|
+
while (stderrTail.length > 200)
|
|
110
|
+
stderrTail.shift();
|
|
100
111
|
});
|
|
101
112
|
child.on("exit", (code) => {
|
|
102
113
|
serverProcess = null;
|
|
103
114
|
serverPort = null;
|
|
104
115
|
bootPromise = null;
|
|
105
|
-
if (process.env.PHI_BROWSER_VERBOSE) {
|
|
116
|
+
if (!healthy || process.env.PHI_BROWSER_VERBOSE) {
|
|
106
117
|
process.stderr.write(`[camofox] server exited with code ${code}\n`);
|
|
107
118
|
}
|
|
108
119
|
});
|
|
120
|
+
// Expose stderr tail through a wrapper that promotes the listener
|
|
121
|
+
// flip — needed below when waitForHealth resolves.
|
|
122
|
+
child.__markHealthy = () => {
|
|
123
|
+
healthy = true;
|
|
124
|
+
};
|
|
125
|
+
child.__stderrTail = stderrTail;
|
|
109
126
|
serverProcess = child;
|
|
110
127
|
serverPort = port;
|
|
111
128
|
const baseUrl = `http://127.0.0.1:${port}`;
|
|
112
|
-
|
|
129
|
+
try {
|
|
130
|
+
await waitForHealth(baseUrl);
|
|
131
|
+
child.__markHealthy?.();
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
// Augment the health-check error with whatever the child wrote to
|
|
135
|
+
// stderr so the consumer has at least one breadcrumb to follow.
|
|
136
|
+
const tail = (child.__stderrTail ?? [])
|
|
137
|
+
.join("")
|
|
138
|
+
.split(/\r?\n/)
|
|
139
|
+
.filter(Boolean)
|
|
140
|
+
.slice(-20)
|
|
141
|
+
.join("\n");
|
|
142
|
+
const original = err instanceof Error ? err.message : String(err);
|
|
143
|
+
const augmented = new Error(tail
|
|
144
|
+
? `${original}\nLast stderr lines from camofox-browser child:\n${tail}`
|
|
145
|
+
: `${original}\n(no stderr captured — set PHI_BROWSER_VERBOSE=1 for more)`);
|
|
146
|
+
throw augmented;
|
|
147
|
+
}
|
|
113
148
|
return { baseUrl };
|
|
114
149
|
})();
|
|
115
150
|
try {
|
|
@@ -209,16 +244,25 @@ async function request(pathname, options = {}) {
|
|
|
209
244
|
clearTimeout(timeout);
|
|
210
245
|
}
|
|
211
246
|
}
|
|
212
|
-
/**
|
|
213
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Open a new browser tab. Returns the tab id used by the other tools.
|
|
249
|
+
*
|
|
250
|
+
* The camofox-browser REST contract requires every tab to be associated
|
|
251
|
+
* with a `userId` (logical user) AND a `sessionKey` (logical session
|
|
252
|
+
* inside that user — used to group tabs that should share cookies /
|
|
253
|
+
* fingerprints / proxies). Phi-code's chat agents only need one of each,
|
|
254
|
+
* so both default to a constant sentinel when omitted.
|
|
255
|
+
*/
|
|
256
|
+
export async function createTab(options = {}) {
|
|
214
257
|
const userId = options.userId ?? DEFAULT_USER_ID;
|
|
215
|
-
const
|
|
258
|
+
const sessionKey = options.sessionKey ?? DEFAULT_SESSION_KEY;
|
|
259
|
+
const body = { userId, sessionKey };
|
|
216
260
|
if (options.url)
|
|
217
261
|
body.url = options.url;
|
|
218
262
|
if (options.viewport)
|
|
219
263
|
body.viewport = options.viewport;
|
|
220
264
|
const res = await request("/tabs", { method: "POST", body });
|
|
221
|
-
return { tabId: res.tabId, userId, url: options.url };
|
|
265
|
+
return { tabId: res.tabId, userId, sessionKey, url: options.url };
|
|
222
266
|
}
|
|
223
267
|
/**
|
|
224
268
|
* Navigate the given tab (or a freshly opened one) to a URL.
|
|
@@ -228,11 +272,19 @@ export async function createTab(options) {
|
|
|
228
272
|
export async function navigate(options) {
|
|
229
273
|
let tabId = options.tabId;
|
|
230
274
|
if (!tabId) {
|
|
231
|
-
const tab = await createTab({
|
|
275
|
+
const tab = await createTab({
|
|
276
|
+
userId: options.userId,
|
|
277
|
+
sessionKey: options.sessionKey,
|
|
278
|
+
url: options.url,
|
|
279
|
+
});
|
|
232
280
|
tabId = tab.tabId;
|
|
233
281
|
return { tabId, url: options.url };
|
|
234
282
|
}
|
|
235
|
-
const body = {
|
|
283
|
+
const body = {
|
|
284
|
+
userId: options.userId ?? DEFAULT_USER_ID,
|
|
285
|
+
sessionKey: options.sessionKey ?? DEFAULT_SESSION_KEY,
|
|
286
|
+
url: options.url,
|
|
287
|
+
};
|
|
236
288
|
if (options.waitUntil)
|
|
237
289
|
body.waitUntil = options.waitUntil;
|
|
238
290
|
if (options.timeoutMs)
|
|
@@ -245,7 +297,9 @@ export async function navigate(options) {
|
|
|
245
297
|
* Refs returned here can be used with `click`/`type`/`scroll`.
|
|
246
298
|
*/
|
|
247
299
|
export async function snapshot(options) {
|
|
248
|
-
|
|
300
|
+
const userId = options.userId ?? DEFAULT_USER_ID;
|
|
301
|
+
const qs = `?userId=${encodeURIComponent(userId)}`;
|
|
302
|
+
return await request(`/tabs/${encodeURIComponent(options.tabId)}/snapshot${qs}`);
|
|
249
303
|
}
|
|
250
304
|
/**
|
|
251
305
|
* Extract the readable content of the current page (Readability-style).
|
|
@@ -258,32 +312,45 @@ export async function extract(options) {
|
|
|
258
312
|
if (!options.url) {
|
|
259
313
|
throw new Error("extract() requires either tabId or url");
|
|
260
314
|
}
|
|
261
|
-
const tab = await createTab({
|
|
315
|
+
const tab = await createTab({
|
|
316
|
+
userId: options.userId,
|
|
317
|
+
sessionKey: options.sessionKey,
|
|
318
|
+
url: options.url,
|
|
319
|
+
});
|
|
262
320
|
tabId = tab.tabId;
|
|
263
321
|
// Wait for the navigation to settle before extracting.
|
|
264
322
|
await request(`/tabs/${encodeURIComponent(tabId)}/wait`, {
|
|
265
323
|
method: "POST",
|
|
266
|
-
body: { event: "load" },
|
|
324
|
+
body: { userId: options.userId ?? DEFAULT_USER_ID, event: "load" },
|
|
267
325
|
}).catch(() => { });
|
|
268
326
|
}
|
|
269
327
|
else if (options.url) {
|
|
270
|
-
await navigate({
|
|
328
|
+
await navigate({
|
|
329
|
+
tabId,
|
|
330
|
+
url: options.url,
|
|
331
|
+
userId: options.userId,
|
|
332
|
+
sessionKey: options.sessionKey,
|
|
333
|
+
});
|
|
271
334
|
}
|
|
272
335
|
const res = await request(`/tabs/${encodeURIComponent(tabId)}/extract`, {
|
|
273
336
|
method: "POST",
|
|
274
|
-
body: {
|
|
337
|
+
body: {
|
|
338
|
+
userId: options.userId ?? DEFAULT_USER_ID,
|
|
339
|
+
sessionKey: options.sessionKey ?? DEFAULT_SESSION_KEY,
|
|
340
|
+
mode: options.mode ?? "readability",
|
|
341
|
+
},
|
|
275
342
|
});
|
|
276
343
|
return res;
|
|
277
344
|
}
|
|
278
345
|
/** Capture a screenshot of the given tab as a base64-encoded PNG. */
|
|
279
346
|
export async function screenshot(options) {
|
|
280
347
|
const query = new URLSearchParams();
|
|
348
|
+
query.set("userId", options.userId ?? DEFAULT_USER_ID);
|
|
281
349
|
if (options.fullPage)
|
|
282
350
|
query.set("fullPage", "1");
|
|
283
351
|
if (options.clip)
|
|
284
352
|
query.set("clip", JSON.stringify(options.clip));
|
|
285
|
-
const
|
|
286
|
-
const res = await request(`/tabs/${encodeURIComponent(options.tabId)}/screenshot${qs ? `?${qs}` : ""}`);
|
|
353
|
+
const res = await request(`/tabs/${encodeURIComponent(options.tabId)}/screenshot?${query.toString()}`);
|
|
287
354
|
return {
|
|
288
355
|
tabId: options.tabId,
|
|
289
356
|
mimeType: res.mimeType ?? "image/png",
|
|
@@ -302,14 +369,14 @@ export async function search(options) {
|
|
|
302
369
|
: engine === "bing"
|
|
303
370
|
? `https://www.bing.com/search?q=${encodeURIComponent(options.query)}`
|
|
304
371
|
: `https://duckduckgo.com/?q=${encodeURIComponent(options.query)}`;
|
|
305
|
-
return await extract({ url, userId: options.userId });
|
|
372
|
+
return await extract({ url, userId: options.userId, sessionKey: options.sessionKey });
|
|
306
373
|
}
|
|
307
374
|
/** Click an element by ref (from `snapshot`) or CSS selector. */
|
|
308
375
|
export async function click(options) {
|
|
309
376
|
if (!options.ref && !options.selector) {
|
|
310
377
|
throw new Error("click() requires `ref` or `selector`");
|
|
311
378
|
}
|
|
312
|
-
const body = {};
|
|
379
|
+
const body = { userId: options.userId ?? DEFAULT_USER_ID };
|
|
313
380
|
if (options.ref)
|
|
314
381
|
body.ref = options.ref;
|
|
315
382
|
if (options.selector)
|
|
@@ -324,7 +391,10 @@ export async function click(options) {
|
|
|
324
391
|
}
|
|
325
392
|
/** Type text into a focused element (or one targeted via ref/selector). */
|
|
326
393
|
export async function type(options) {
|
|
327
|
-
const body = {
|
|
394
|
+
const body = {
|
|
395
|
+
userId: options.userId ?? DEFAULT_USER_ID,
|
|
396
|
+
text: options.text,
|
|
397
|
+
};
|
|
328
398
|
if (options.ref)
|
|
329
399
|
body.ref = options.ref;
|
|
330
400
|
if (options.selector)
|
|
@@ -341,7 +411,10 @@ export async function type(options) {
|
|
|
341
411
|
}
|
|
342
412
|
/** Scroll the page or a specific element by ref. */
|
|
343
413
|
export async function scroll(options) {
|
|
344
|
-
const body = {
|
|
414
|
+
const body = {
|
|
415
|
+
userId: options.userId ?? DEFAULT_USER_ID,
|
|
416
|
+
direction: options.direction,
|
|
417
|
+
};
|
|
345
418
|
if (options.ref)
|
|
346
419
|
body.ref = options.ref;
|
|
347
420
|
if (options.pixels)
|
|
@@ -354,7 +427,9 @@ export async function scroll(options) {
|
|
|
354
427
|
}
|
|
355
428
|
/** Close a single tab. The underlying browser context is kept warm. */
|
|
356
429
|
export async function closeTab(options) {
|
|
357
|
-
|
|
430
|
+
const userId = options.userId ?? DEFAULT_USER_ID;
|
|
431
|
+
const qs = `?userId=${encodeURIComponent(userId)}`;
|
|
432
|
+
await request(`/tabs/${encodeURIComponent(options.tabId)}${qs}`, { method: "DELETE" });
|
|
358
433
|
return { tabId: options.tabId };
|
|
359
434
|
}
|
|
360
435
|
/** List all open tabs for a user. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phi-code-admin/browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Phi-code browser automation API: lazy-start the bundled Camoufox + camofox-browser server and expose 10 high-level tools (navigate, extract, screenshot, click, type, scroll, snapshot, search, close_tab, list_tabs) as plain ES module functions. Zero external dependencies at runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"clean": "rimraf dist"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@phi-code-admin/camofox-browser": "1.0.
|
|
41
|
+
"@phi-code-admin/camofox-browser": "1.0.1",
|
|
42
42
|
"@phi-code-admin/camoufox-js": "1.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|