@webhands/core 0.4.0 → 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.
Files changed (59) hide show
  1. package/README.md +69 -6
  2. package/dist/errors.d.ts +112 -1
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js +121 -0
  5. package/dist/errors.js.map +1 -1
  6. package/dist/hand-host.d.ts +198 -5
  7. package/dist/hand-host.d.ts.map +1 -1
  8. package/dist/hand-host.js +664 -21
  9. package/dist/hand-host.js.map +1 -1
  10. package/dist/index.d.ts +5 -4
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -3
  13. package/dist/index.js.map +1 -1
  14. package/dist/playwright-attach-transport.d.ts +8 -1
  15. package/dist/playwright-attach-transport.d.ts.map +1 -1
  16. package/dist/playwright-attach-transport.js +19 -4
  17. package/dist/playwright-attach-transport.js.map +1 -1
  18. package/dist/playwright-launch-transport.d.ts +23 -0
  19. package/dist/playwright-launch-transport.d.ts.map +1 -1
  20. package/dist/playwright-launch-transport.js +40 -6
  21. package/dist/playwright-launch-transport.js.map +1 -1
  22. package/dist/profile-location.d.ts +19 -0
  23. package/dist/profile-location.d.ts.map +1 -1
  24. package/dist/profile-location.js +21 -0
  25. package/dist/profile-location.js.map +1 -1
  26. package/dist/seam.d.ts +501 -7
  27. package/dist/seam.d.ts.map +1 -1
  28. package/dist/seam.js +31 -0
  29. package/dist/seam.js.map +1 -1
  30. package/dist/session-rpc.d.ts +63 -1
  31. package/dist/session-rpc.d.ts.map +1 -1
  32. package/dist/session-rpc.js +174 -11
  33. package/dist/session-rpc.js.map +1 -1
  34. package/dist/socks-proxy.d.ts +61 -0
  35. package/dist/socks-proxy.d.ts.map +1 -0
  36. package/dist/socks-proxy.js +84 -0
  37. package/dist/socks-proxy.js.map +1 -0
  38. package/dist/stub-transport.d.ts.map +1 -1
  39. package/dist/stub-transport.js +74 -6
  40. package/dist/stub-transport.js.map +1 -1
  41. package/dist/test-fixtures/fixture-pages.d.ts.map +1 -1
  42. package/dist/test-fixtures/fixture-pages.js +994 -0
  43. package/dist/test-fixtures/fixture-pages.js.map +1 -1
  44. package/dist/test-fixtures/fixture-server.d.ts.map +1 -1
  45. package/dist/test-fixtures/fixture-server.js +33 -3
  46. package/dist/test-fixtures/fixture-server.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/errors.ts +164 -1
  49. package/src/hand-host.ts +797 -21
  50. package/src/index.ts +27 -1
  51. package/src/playwright-attach-transport.ts +25 -3
  52. package/src/playwright-launch-transport.ts +63 -4
  53. package/src/profile-location.ts +25 -0
  54. package/src/seam.ts +535 -7
  55. package/src/session-rpc.ts +276 -14
  56. package/src/socks-proxy.ts +127 -0
  57. package/src/stub-transport.ts +83 -6
  58. package/src/test-fixtures/fixture-pages.ts +1010 -0
  59. package/src/test-fixtures/fixture-server.ts +32 -3
@@ -1 +1 @@
1
- {"version":3,"file":"fixture-pages.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-pages.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,KAAK,GAAG;;;;;;;;;;;;;CAab,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBvB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BlB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;CAenB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BZ,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,OAAO,GAAG;;;;;;;;;;;;;;;;CAgBf,CAAC;AAEF,+EAA+E;AAC/E,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC9D,YAAY,EAAE,KAAK;IACnB,iBAAiB,EAAE,UAAU;IAC7B,cAAc,EAAE,eAAe;IAC/B,kBAAkB,EAAE,WAAW;IAC/B,WAAW,EAAE,IAAI;IACjB,cAAc,EAAE,OAAO;CACvB,CAAC"}
1
+ {"version":3,"file":"fixture-pages.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-pages.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fixture-server.d.ts","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"AAGA,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,SAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CAgCzE"}
1
+ {"version":3,"file":"fixture-server.d.ts","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"AAiBA,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC7B,mDAAmD;IACnD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,SAAI,GAAG,OAAO,CAAC,aAAa,CAAC,CA+CzE"}
@@ -1,5 +1,19 @@
1
1
  import { createServer } from 'node:http';
2
2
  import { FIXTURE_PAGES } from './fixture-pages.js';
3
+ /** Largest artificial response delay a fixture request may ask for (a guard so
4
+ * a stray `?delayMs=` can never hang the suite). */
5
+ const MAX_DELAY_MS = 5_000;
6
+ /** Parse `?delayMs=N` from a request URL, clamped to `[0, MAX_DELAY_MS]`. */
7
+ function parseDelayMs(reqUrl) {
8
+ const q = reqUrl.indexOf('?');
9
+ if (q === -1)
10
+ return 0;
11
+ const raw = new URLSearchParams(reqUrl.slice(q + 1)).get('delayMs');
12
+ const n = raw === null ? 0 : Number.parseInt(raw, 10);
13
+ if (!Number.isFinite(n) || n <= 0)
14
+ return 0;
15
+ return Math.min(n, MAX_DELAY_MS);
16
+ }
3
17
  /**
4
18
  * Start a local HTTP server that serves the controlled static fixture pages
5
19
  * from {@link FIXTURE_PAGES}. This is the DETERMINISTIC target for later
@@ -12,7 +26,8 @@ import { FIXTURE_PAGES } from './fixture-pages.js';
12
26
  */
13
27
  export async function startFixtureServer(port = 0) {
14
28
  const server = createServer((req, res) => {
15
- const rawPath = (req.url ?? '/').split('?')[0];
29
+ const reqUrl = req.url ?? '/';
30
+ const rawPath = reqUrl.split('?')[0];
16
31
  const key = rawPath === '/' ? 'index.html' : rawPath.replace(/^\/+/, '');
17
32
  const body = FIXTURE_PAGES[key];
18
33
  if (body === undefined) {
@@ -20,8 +35,23 @@ export async function startFixtureServer(port = 0) {
20
35
  res.end('not found');
21
36
  return;
22
37
  }
23
- res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
24
- res.end(body);
38
+ // A `?delayMs=N` query holds the RESPONSE back by N ms before sending the
39
+ // (otherwise normal) page. This makes a slow NAVIGATION deterministic: a
40
+ // click that submits to `index.html?delayMs=1500` performs instantly but
41
+ // the navigation only commits ~1.5s later, the case the `click` verb must
42
+ // not mistake for a non-actionable element (it auto-waits for navigation
43
+ // only when not told otherwise). Capped so a bad value cannot hang a test.
44
+ const delayMs = parseDelayMs(reqUrl);
45
+ const send = () => {
46
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
47
+ res.end(body);
48
+ };
49
+ if (delayMs > 0) {
50
+ setTimeout(send, delayMs);
51
+ }
52
+ else {
53
+ send();
54
+ }
25
55
  });
26
56
  await new Promise((resolve) => server.listen(port, '127.0.0.1', resolve));
27
57
  const address = server.address();
@@ -1 +1 @@
1
- {"version":3,"file":"fixture-server.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAc,MAAM,WAAW,CAAC;AACpD,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAUjD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAI,GAAG,CAAC;IAChD,MAAM,MAAM,GAAW,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAChD,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAC,cAAc,EAAE,2BAA2B,EAAC,CAAC,CAAC;YAClE,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YACrB,OAAO;QACR,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAC,cAAc,EAAE,0BAA0B,EAAC,CAAC,CAAC;QACjE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CACnC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CACzC,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IACjC,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QACrD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACpE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IAChE,CAAC;IAED,OAAO;QACN,GAAG,EAAE,oBAAoB,OAAO,CAAC,IAAI,EAAE;QACvC,KAAK;YACJ,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC5C,MAAM,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACxD,CAAC,CAAC,CAAC;QACJ,CAAC;KACD,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"fixture-server.js","sourceRoot":"","sources":["../../src/test-fixtures/fixture-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webhands/core",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Core library for webhands: drives a real, persistent browser via Playwright.",
5
5
  "keywords": [
6
6
  "browser",
package/src/errors.ts CHANGED
@@ -19,11 +19,15 @@
19
19
  export type ControllerErrorCode =
20
20
  | 'missing-browser-binary'
21
21
  | 'missing-stealth-dependency'
22
+ | 'invalid-proxy'
22
23
  | 'missing-profile'
23
24
  | 'attach-not-chromium'
24
25
  | 'attach-no-context'
25
26
  | 'no-live-server'
26
- | 'session-already-active';
27
+ | 'session-already-active'
28
+ | 'cross-origin-frame'
29
+ | 'screenshot-path-outside-managed-dir'
30
+ | 'stale-ref';
27
31
 
28
32
  /**
29
33
  * Base class for every identifiable `core` error. Branch on {@link code}.
@@ -96,6 +100,35 @@ export class MissingStealthDependencyError extends ControllerError {
96
100
  }
97
101
  }
98
102
 
103
+ /**
104
+ * The `--proxy` value (a SOCKS URL) could not be parsed into a usable proxy
105
+ * config. webhands routes ALL traffic and DNS through one SOCKS proxy, so the
106
+ * value must be a `socks5://` or `socks5h://` URL with a host and port (an
107
+ * optional `user:pass@` is allowed). We refuse a malformed value LOUDLY with
108
+ * this typed condition rather than silently launching with no proxy (which
109
+ * would leak the very traffic the user asked to tunnel). The CLI maps the
110
+ * {@link code} to a fix message showing the expected URL shape.
111
+ *
112
+ * Mirrors {@link MissingStealthDependencyError}: a stable typed error whose
113
+ * brittle detection is confined to one spot (the proxy parser).
114
+ */
115
+ export class InvalidProxyError extends ControllerError {
116
+ readonly code = 'invalid-proxy';
117
+ /** The offending raw `--proxy` value, echoed back so the user can see it. */
118
+ readonly value: string;
119
+
120
+ constructor(
121
+ value: string,
122
+ message: string = `Invalid --proxy value ${JSON.stringify(
123
+ value,
124
+ )}. Expected a SOCKS URL like socks5h://host:1080 or socks5://user:pass@host:1080 (socks5h tunnels DNS too; both route all traffic through the proxy).`,
125
+ options?: {cause?: unknown},
126
+ ) {
127
+ super(message, options);
128
+ this.value = value;
129
+ }
130
+ }
131
+
99
132
  /**
100
133
  * The named profile has not been set up yet: its dedicated profile directory
101
134
  * does not exist on disk. A profile is created by the headed `setup-profile`
@@ -200,6 +233,136 @@ export class SessionAlreadyActiveError extends ControllerError {
200
233
  }
201
234
  }
202
235
 
236
+ /**
237
+ * A frame-scoped `eval` (or any same-origin-frame op) addressed a CROSS-ORIGIN
238
+ * child frame. Page-world JS cannot cross a browser security boundary, so the
239
+ * frame-scoped `eval` reaches the top document and SAME-ORIGIN descendant frames
240
+ * ONLY (the idea's honest-scope note); a cross-origin frame is unreachable BY
241
+ * DESIGN, not a missing feature. We refuse LOUDLY with this typed condition
242
+ * rather than return a silent empty result, because a silent empty would let an
243
+ * agent believe its callback fired / its read succeeded when the security
244
+ * boundary actually blocked it.
245
+ *
246
+ * Cross-origin frame reach is the SEPARATE Tier-4 `frameLocator`/coordinate
247
+ * surface, not this verb; the message points there so the agent is not left
248
+ * guessing. Mirrors the other typed conditions: the CLI maps {@link code} to a
249
+ * message, and {@link isControllerError} narrows it across a bundle boundary.
250
+ */
251
+ export class CrossOriginFrameError extends ControllerError {
252
+ readonly code = 'cross-origin-frame';
253
+ /** The frame selector the caller passed (echoed back so it is visible). */
254
+ readonly frame: string;
255
+ /** The cross-origin frame's origin, when known (e.g. `https://hcaptcha.com`). */
256
+ readonly frameOrigin?: string;
257
+ /** The page's own (main-frame) origin the frame had to match. */
258
+ readonly pageOrigin?: string;
259
+
260
+ constructor(
261
+ frame: string,
262
+ details?: {frameOrigin?: string; pageOrigin?: string},
263
+ message: string = `The frame ${JSON.stringify(frame)} is CROSS-ORIGIN${
264
+ details?.frameOrigin !== undefined && details?.pageOrigin !== undefined
265
+ ? ` (frame origin ${details.frameOrigin}, page origin ${details.pageOrigin})`
266
+ : ''
267
+ } and is unreachable from page-world JS. eval --frame reaches the top document and SAME-ORIGIN child frames only; a cross-origin frame is a browser security boundary. Reach cross-origin frames with the Tier-4 frameLocator/coordinate ops instead.`,
268
+ options?: {cause?: unknown},
269
+ ) {
270
+ super(message, options);
271
+ this.frame = frame;
272
+ this.frameOrigin = details?.frameOrigin;
273
+ this.pageOrigin = details?.pageOrigin;
274
+ }
275
+ }
276
+
277
+ /**
278
+ * A `screenshot --out <path>` override pointed OUTSIDE the managed screenshots
279
+ * dir. webhands MINTS screenshots under one managed directory it owns (under the
280
+ * controller home root, beside `profiles/`); a caller MAY override the output
281
+ * path, but only WITHIN that managed dir (prd `broaden-agent-verb-surface`, R3:
282
+ * "validate it stays under a sane managed dir"). An override that escapes it
283
+ * (an absolute path elsewhere, or a `..` traversal out) is refused LOUDLY with
284
+ * this typed condition rather than silently writing a PNG to an arbitrary
285
+ * filesystem location — the seam returns a path, so an unbounded path would let
286
+ * the verb clobber any file the process can write.
287
+ *
288
+ * Mirrors the other typed conditions: the CLI maps {@link code} to a message,
289
+ * and {@link isControllerError} narrows it across a bundle boundary.
290
+ */
291
+ export class ScreenshotPathError extends ControllerError {
292
+ readonly code = 'screenshot-path-outside-managed-dir';
293
+ /** The offending caller-supplied `out` path, echoed back so it is visible. */
294
+ readonly path: string;
295
+ /** The managed screenshots dir the path had to stay under. */
296
+ readonly managedDir: string;
297
+
298
+ constructor(
299
+ path: string,
300
+ managedDir: string,
301
+ message: string = `The screenshot output path ${JSON.stringify(
302
+ path,
303
+ )} is OUTSIDE the managed screenshots dir (${managedDir}). A caller may override --out only within the managed dir; webhands never writes a screenshot to an arbitrary location.`,
304
+ options?: {cause?: unknown},
305
+ ) {
306
+ super(message, options);
307
+ this.path = path;
308
+ this.managedDir = managedDir;
309
+ }
310
+ }
311
+
312
+ /**
313
+ * A durable `query` `ref` (prd `broaden-agent-verb-surface`, R4; finding
314
+ * `query-ref-mint-mechanism-attribute-beats-weakmap`) failed to resolve to
315
+ * EXACTLY ONE element when an action verb (`click`/`type`) tried to act on it.
316
+ * A `ref` is a SHORT-LIVED handle, not a stable identity: between the `query`
317
+ * that minted it and the act, the page may have re-rendered (React keyed-list /
318
+ * Svelte `{#each}` NODE REPLACEMENT), navigated, or cloned a subtree carrying
319
+ * our minted attribute. Two stale shapes, BOTH this one error, never a silent
320
+ * wrong-element action:
321
+ *
322
+ * - resolve-to-ZERO ({@link matched} `=== 0`) — the element was removed or
323
+ * replaced (its minted node is gone, or a reused stable attribute it carried
324
+ * no longer exists). The agent must re-`query` against the fresh DOM.
325
+ * - resolve-to-MANY ({@link matched} `> 1`) — the ref matches more than one
326
+ * element (a framework cloned a subtree carrying our minted attribute, or a
327
+ * reused attribute turned out non-unique after a mutation). We refuse to
328
+ * "pick the first", because that is exactly the silent wrong-element click the
329
+ * ref exists to PREVENT.
330
+ *
331
+ * This is STRICTLY SAFER than re-addressing by a positional `.nth(i)`, which
332
+ * SILENTLY clicks whatever now sits at that index. The agent is told "re-query,
333
+ * the page changed" — its natural loop anyway. Mirrors the other typed
334
+ * conditions: the CLI maps {@link code} to a message, and
335
+ * {@link isControllerError} narrows it across a bundle boundary.
336
+ */
337
+ export class StaleRefError extends ControllerError {
338
+ readonly code = 'stale-ref';
339
+ /** The ref locator string that went stale (echoed back so it is visible). */
340
+ readonly ref: string;
341
+ /** How many elements the ref resolved to (0 = removed/replaced, >1 = ambiguous). */
342
+ readonly matched: number;
343
+ /** Which verb tried to act on the stale ref (e.g. `click`, `type`). */
344
+ readonly verb: string;
345
+
346
+ constructor(
347
+ ref: string,
348
+ matched: number,
349
+ verb: string,
350
+ message: string = matched === 0
351
+ ? `${verb}: the ref ${JSON.stringify(
352
+ ref,
353
+ )} is STALE — it now matches NO element (the page changed: the element was removed, replaced by a re-render, or the page navigated). Re-run query to get a fresh ref.`
354
+ : `${verb}: the ref ${JSON.stringify(
355
+ ref,
356
+ )} is AMBIGUOUS — it now matches ${matched} elements (the page changed: a subtree was cloned, or the attribute is no longer unique). Refusing to act on a guessed element; re-run query to get a fresh ref.`,
357
+ options?: {cause?: unknown},
358
+ ) {
359
+ super(message, options);
360
+ this.ref = ref;
361
+ this.matched = matched;
362
+ this.verb = verb;
363
+ }
364
+ }
365
+
203
366
  /**
204
367
  * Narrow an unknown caught value to a {@link ControllerError}. Prefer this over
205
368
  * `instanceof` at package boundaries: it checks the {@link ControllerError.isControllerError}