nothing-browser 0.0.21 → 0.1.1

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 (68) hide show
  1. package/LICENSE +1 -1
  2. package/dist/human/index.js +25 -1
  3. package/dist/piggy/captcha/index.d.ts +39 -0
  4. package/dist/piggy/captcha/index.d.ts.map +1 -0
  5. package/dist/piggy/capture/index.d.ts +48 -0
  6. package/dist/piggy/capture/index.d.ts.map +1 -0
  7. package/dist/piggy/dialog/index.d.ts +28 -0
  8. package/dist/piggy/dialog/index.d.ts.map +1 -0
  9. package/dist/piggy/export/index.d.ts +62 -0
  10. package/dist/piggy/export/index.d.ts.map +1 -0
  11. package/dist/piggy/find/index.d.ts +52 -0
  12. package/dist/piggy/find/index.d.ts.map +1 -0
  13. package/dist/piggy/http/index.d.ts +14 -0
  14. package/dist/piggy/http/index.d.ts.map +1 -0
  15. package/dist/piggy/human/index.d.ts +36 -4
  16. package/dist/piggy/human/index.d.ts.map +1 -1
  17. package/dist/piggy/iframe/index.d.ts +53 -0
  18. package/dist/piggy/iframe/index.d.ts.map +1 -0
  19. package/dist/piggy/interactions/index.d.ts +24 -0
  20. package/dist/piggy/interactions/index.d.ts.map +1 -0
  21. package/dist/piggy/media/index.d.ts +11 -0
  22. package/dist/piggy/media/index.d.ts.map +1 -0
  23. package/dist/piggy/navigation/index.d.ts +16 -0
  24. package/dist/piggy/navigation/index.d.ts.map +1 -0
  25. package/dist/piggy/provide/index.d.ts +98 -0
  26. package/dist/piggy/provide/index.d.ts.map +1 -0
  27. package/dist/piggy/proxy/index.d.ts +94 -0
  28. package/dist/piggy/proxy/index.d.ts.map +1 -0
  29. package/dist/piggy/register/index.d.ts.map +1 -1
  30. package/dist/piggy/router/index.d.ts +38 -0
  31. package/dist/piggy/router/index.d.ts.map +1 -0
  32. package/dist/piggy/session/index.d.ts +27 -0
  33. package/dist/piggy/session/index.d.ts.map +1 -0
  34. package/dist/piggy/tabs/index.d.ts +10 -0
  35. package/dist/piggy/tabs/index.d.ts.map +1 -0
  36. package/dist/piggy/wait/index.d.ts +28 -0
  37. package/dist/piggy/wait/index.d.ts.map +1 -0
  38. package/dist/piggy.d.ts.map +1 -1
  39. package/dist/piggy.js +898 -181
  40. package/dist/register/index.js +39 -86
  41. package/package.json +1 -1
  42. package/piggy/captcha/index.d.ts +35 -0
  43. package/piggy/captcha/index.ts +93 -0
  44. package/piggy/capture/index.ts +76 -0
  45. package/piggy/dialog/index.d.ts +29 -0
  46. package/piggy/dialog/index.ts +85 -0
  47. package/piggy/export/index.d.ts +117 -0
  48. package/piggy/export/index.ts +147 -0
  49. package/piggy/find/index.ts +118 -0
  50. package/piggy/http/index.ts +65 -0
  51. package/piggy/human/index.ts +115 -53
  52. package/piggy/iframe/index.ts +79 -0
  53. package/piggy/interactions/index.ts +79 -0
  54. package/piggy/media/index.ts +46 -0
  55. package/piggy/navigation/index.ts +52 -0
  56. package/piggy/provide/index.ts +170 -0
  57. package/piggy/proxy/index.ts +154 -0
  58. package/piggy/register/index.ts +41 -59
  59. package/piggy/router/index.ts +69 -0
  60. package/piggy/session/index.ts +76 -0
  61. package/piggy/tabs/index.ts +22 -0
  62. package/piggy/wait/index.ts +90 -0
  63. package/piggy.ts +94 -39
  64. package/dist/piggy/open/index.d.ts +0 -4
  65. package/dist/piggy/open/index.d.ts.map +0 -1
  66. package/piggy/open/index.d.ts +0 -4
  67. package/piggy/open/index.d.ts.map +0 -1
  68. package/piggy/open/index.ts +0 -5
@@ -0,0 +1,147 @@
1
+ // piggy/export/index.ts
2
+ import { PiggyClient } from "../client";
3
+ import type { CapturedRequest, WebSocketFrame, CapturedCookie } from "../capture";
4
+ import type { SessionPaths } from "../session";
5
+
6
+ // ─── Intercept ────────────────────────────────────────────────────────────────
7
+ export interface InterceptRule {
8
+ pattern: string;
9
+ block?: boolean;
10
+ redirect?: string;
11
+ setHeaders?: Record<string, string>;
12
+ removeHeaders?: string[];
13
+ }
14
+
15
+ // ─── Cookie types (defined here, imported by session) ────────────────────────
16
+ export interface CookieSetOptions {
17
+ name: string;
18
+ value: string;
19
+ domain: string;
20
+ path?: string;
21
+ httpOnly?: boolean;
22
+ secure?: boolean;
23
+ expiry?: number;
24
+ }
25
+
26
+ export interface CookieDeleteOptions {
27
+ name: string;
28
+ domain: string;
29
+ }
30
+
31
+ // ─── Session export types (re-exported from capture) ────────────────────────
32
+ export type { CapturedRequest, WebSocketFrame, CapturedCookie, SessionPaths };
33
+
34
+ export interface SessionExport {
35
+ url: string;
36
+ requests: CapturedRequest[];
37
+ ws: WebSocketFrame[];
38
+ cookies: CapturedCookie[];
39
+ }
40
+
41
+ export interface ExposedFunctionCall {
42
+ name: string;
43
+ callId: string;
44
+ data: string;
45
+ tabId: string;
46
+ }
47
+
48
+ // ─── ExportClient ─────────────────────────────────────────────────────────────
49
+ export class ExportClient {
50
+ constructor(private client: PiggyClient) {}
51
+
52
+ // DOM fetch
53
+ searchCss(query: string, tabId = "default"): Promise<any> {
54
+ return this.client.send("search.css", { query, tabId });
55
+ }
56
+
57
+ searchId(query: string, tabId = "default"): Promise<any> {
58
+ return this.client.send("search.id", { query, tabId });
59
+ }
60
+
61
+ // Cookies
62
+ setCookie(opts: CookieSetOptions, tabId = "default"): Promise<void> {
63
+ return this.client.send("cookie.set", { ...opts, tabId });
64
+ }
65
+
66
+ deleteCookie(opts: CookieDeleteOptions, tabId = "default"): Promise<void> {
67
+ return this.client.send("cookie.delete", { ...opts, tabId });
68
+ }
69
+
70
+ // Session
71
+ sessionReload(tabId = "default"): Promise<void> {
72
+ return this.client.send("session.reload", { tabId });
73
+ }
74
+
75
+ cookiesPath(): Promise<string> {
76
+ return this.client.send("session.cookies.path", {});
77
+ }
78
+
79
+ profilePath(): Promise<string> {
80
+ return this.client.send("session.profile.path", {});
81
+ }
82
+
83
+ wsPath(): Promise<string> {
84
+ return this.client.send("session.ws.path", {});
85
+ }
86
+
87
+ pingsPath(): Promise<string> {
88
+ return this.client.send("session.pings.path", {});
89
+ }
90
+
91
+ sessionPaths(): Promise<SessionPaths> {
92
+ return this.client.send("session.paths", {});
93
+ }
94
+
95
+ setWsSave(enabled: boolean): Promise<void> {
96
+ return this.client.send("session.ws.save", { enabled });
97
+ }
98
+
99
+ setPingsSave(enabled: boolean): Promise<void> {
100
+ return this.client.send("session.pings.save", { enabled });
101
+ }
102
+
103
+ // Intercept rules
104
+ addInterceptRule(rule: InterceptRule, tabId = "default"): Promise<void> {
105
+ return this.client.send("intercept.rule.add", { ...rule, tabId });
106
+ }
107
+
108
+ clearInterceptRules(tabId = "default"): Promise<void> {
109
+ return this.client.send("intercept.rule.clear", { tabId });
110
+ }
111
+
112
+ // Session export / import
113
+ async exportSession(tabId = "default"): Promise<SessionExport> {
114
+ const raw = await this.client.send<string>("session.export", { tabId });
115
+ return typeof raw === "string" ? JSON.parse(raw) : raw;
116
+ }
117
+
118
+ importSession(data: SessionExport, tabId = "default"): Promise<void> {
119
+ return this.client.send("session.import", {
120
+ data: JSON.stringify(data),
121
+ tabId,
122
+ });
123
+ }
124
+
125
+ // Exposed functions
126
+ exposeFunction(name: string, tabId = "default"): Promise<void> {
127
+ return this.client.send("expose.function", { name, tabId });
128
+ }
129
+
130
+ resolveExposed(callId: string, result: string, isError = false, tabId = "default"): Promise<void> {
131
+ return this.client.send("exposed.result", { callId, result, isError, tabId });
132
+ }
133
+
134
+ // Init scripts
135
+ addInitScript(js: string, tabId = "default"): Promise<void> {
136
+ return this.client.send("addInitScript", { js, tabId });
137
+ }
138
+
139
+ // Events
140
+ onExposedFunctionCalled(tabId: string, handler: (call: ExposedFunctionCall) => void): () => void {
141
+ return this.client.onEvent("exposed_call", tabId, handler);
142
+ }
143
+ }
144
+
145
+ export function createExportAPI(client: PiggyClient): ExportClient {
146
+ return new ExportClient(client);
147
+ }
@@ -0,0 +1,118 @@
1
+ // piggy/find/index.ts
2
+ import { PiggyClient } from "../client";
3
+
4
+ // ─── Element descriptor ───────────────────────────────────────────────────────
5
+ // Mirrors __nb_serialize() in PiggyFind.cpp
6
+
7
+ export interface ElementDescriptor {
8
+ tag: string;
9
+ id: string;
10
+ cls: string;
11
+ /** First 400 chars of innerText */
12
+ text: string;
13
+ /** First 800 chars of innerHTML */
14
+ html: string;
15
+ href: string;
16
+ src: string;
17
+ value: string;
18
+ attrs: Record<string, string>;
19
+ }
20
+
21
+ // ─── FindClient ───────────────────────────────────────────────────────────────
22
+ // find answers ONE question: "is this thing here, and where?"
23
+ // All methods take plain selector strings — no option objects, no parent scoping.
24
+ // If you need a value out of an element, use provide instead.
25
+
26
+ export class FindClient {
27
+ constructor(private client: PiggyClient) {}
28
+
29
+ // ── Multi-result ─────────────────────────────────────────────────────────────
30
+
31
+ /** querySelectorAll — returns all matching elements. */
32
+ css(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
33
+ return this.client.send("find.css", { selector, tabId });
34
+ }
35
+
36
+ /** Alias for css(). */
37
+ all(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
38
+ return this.client.send("find.all", { selector, tabId });
39
+ }
40
+
41
+ /** querySelector — returns a single-element array or []. */
42
+ first(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
43
+ return this.client.send("find.first", { selector, tabId });
44
+ }
45
+
46
+ /** Find elements whose innerText contains the given text. */
47
+ byText(text: string, tabId = "default"): Promise<ElementDescriptor[]> {
48
+ return this.client.send("find.byText", { text, tabId });
49
+ }
50
+
51
+ /** Find elements by attribute name (and optional value). */
52
+ byAttr(attr: string, value?: string, tabId = "default"): Promise<ElementDescriptor[]> {
53
+ return this.client.send("find.byAttr", { attr, value, tabId });
54
+ }
55
+
56
+ /** getElementsByTagName. */
57
+ byTag(tag: string, tabId = "default"): Promise<ElementDescriptor[]> {
58
+ return this.client.send("find.byTag", { tag, tabId });
59
+ }
60
+
61
+ /** Find inputs/textareas whose placeholder contains the given text. */
62
+ byPlaceholder(text: string, tabId = "default"): Promise<ElementDescriptor[]> {
63
+ return this.client.send("find.byPlaceholder", { text, tabId });
64
+ }
65
+
66
+ /** Find elements by ARIA role, optionally filtered by accessible name. */
67
+ byRole(role: string, name?: string, tabId = "default"): Promise<ElementDescriptor[]> {
68
+ return this.client.send("find.byRole", { role, name, tabId });
69
+ }
70
+
71
+ /** Direct children of the matched element. */
72
+ children(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
73
+ return this.client.send("find.children", { selector, tabId });
74
+ }
75
+
76
+ /** parentElement of the matched element. */
77
+ parent(selector: string, tabId = "default"): Promise<ElementDescriptor[]> {
78
+ return this.client.send("find.parent", { selector, tabId });
79
+ }
80
+
81
+ /** Walk up the DOM from selector until ancestor matches. */
82
+ closest(selector: string, ancestor: string, tabId = "default"): Promise<ElementDescriptor[]> {
83
+ return this.client.send("find.closest", { selector, ancestor, tabId });
84
+ }
85
+
86
+ // ── Boolean / numeric ────────────────────────────────────────────────────────
87
+
88
+ /** Number of elements matching the selector. */
89
+ count(selector: string, tabId = "default"): Promise<number> {
90
+ return this.client.send("find.count", { selector, tabId });
91
+ }
92
+
93
+ /** True if at least one element matches. */
94
+ exists(selector: string, tabId = "default"): Promise<boolean> {
95
+ return this.client.send("find.exists", { selector, tabId });
96
+ }
97
+
98
+ /** True if the first matched element is visible. */
99
+ visible(selector: string, tabId = "default"): Promise<boolean> {
100
+ return this.client.send("find.visible", { selector, tabId });
101
+ }
102
+
103
+ /** True if the first matched element is not disabled. */
104
+ enabled(selector: string, tabId = "default"): Promise<boolean> {
105
+ return this.client.send("find.enabled", { selector, tabId });
106
+ }
107
+
108
+ /** True if the first matched checkbox/radio is checked. */
109
+ checked(selector: string, tabId = "default"): Promise<boolean> {
110
+ return this.client.send("find.checked", { selector, tabId });
111
+ }
112
+ }
113
+
114
+ // ─── Factory helper ───────────────────────────────────────────────────────────
115
+
116
+ export function createFindAPI(client: PiggyClient): FindClient {
117
+ return new FindClient(client);
118
+ }
@@ -0,0 +1,65 @@
1
+ // piggy/http/index.ts
2
+ // Mirrors PiggyHttp.cpp — connects to the HTTP server on port 2005.
3
+ // Use this when you want to talk to the browser over HTTP instead of the unix socket.
4
+
5
+ export interface HttpClientOptions {
6
+ host?: string;
7
+ port?: number;
8
+ key: string;
9
+ }
10
+
11
+ export class PiggyHttpClient {
12
+ private baseUrl: string;
13
+ private key: string;
14
+
15
+ constructor(opts: HttpClientOptions) {
16
+ const host = opts.host ?? "localhost";
17
+ const port = opts.port ?? 2005;
18
+ this.baseUrl = `http://${host}:${port}`;
19
+ this.key = opts.key;
20
+ }
21
+
22
+ // ── Health check ──────────────────────────────────────────────────────────
23
+
24
+ async ping(): Promise<boolean> {
25
+ try {
26
+ const res = await fetch(this.baseUrl, {
27
+ method: "POST",
28
+ headers: {
29
+ "Content-Type": "application/json",
30
+ "X-Piggy-Key": this.key,
31
+ },
32
+ body: "hello",
33
+ });
34
+ const text = await res.text();
35
+ return text.includes("active");
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ // ── Send command ──────────────────────────────────────────────────────────
42
+
43
+ async send<T = any>(cmd: string, payload: Record<string, any> = {}): Promise<T> {
44
+ const res = await fetch(this.baseUrl, {
45
+ method: "POST",
46
+ headers: {
47
+ "Content-Type": "application/json",
48
+ "X-Piggy-Key": this.key,
49
+ },
50
+ body: JSON.stringify({ id: String(Date.now()), cmd, payload }),
51
+ });
52
+
53
+ if (res.status === 401) throw new Error("Unauthorized — invalid X-Piggy-Key");
54
+ if (res.status === 400) throw new Error("Bad request — invalid JSON");
55
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
56
+
57
+ const data = await res.json() as { ok: boolean; data: T };
58
+ if (!data.ok) throw new Error(String(data.data) ?? "command failed");
59
+ return data.data;
60
+ }
61
+ }
62
+
63
+ export function createHttpClient(opts: HttpClientOptions): PiggyHttpClient {
64
+ return new PiggyHttpClient(opts);
65
+ }
@@ -1,53 +1,115 @@
1
- // piggy/human/index.ts
2
-
3
- export function randomDelay(min: number, max: number): Promise<void> {
4
- return new Promise(r => setTimeout(r, Math.floor(Math.random() * (max - min + 1)) + min));
5
- }
6
-
7
- /**
8
- * Simulates human typing by introducing ~2 random typos and correcting them.
9
- * Returns an array of { char, isBackspace } actions to replay.
10
- */
11
- export function humanTypeSequence(text: string): string[] {
12
- const adjacent: Record<string, string[]> = {
13
- a: ["q","w","s","z"], b: ["v","g","h","n"], c: ["x","d","f","v"],
14
- d: ["s","e","r","f","c","x"], e: ["w","r","d","s"],
15
- f: ["d","r","t","g","v","c"], g: ["f","t","y","h","b","v"],
16
- h: ["g","y","u","j","n","b"], i: ["u","o","k","j"],
17
- j: ["h","u","i","k","m","n"], k: ["j","i","o","l","m"],
18
- l: ["k","o","p"], m: ["n","j","k"], n: ["b","h","j","m"],
19
- o: ["i","p","l","k"], p: ["o","l"], q: ["w","a"],
20
- r: ["e","t","f","d"], s: ["a","w","e","d","x","z"],
21
- t: ["r","y","g","f"], u: ["y","i","h","j"],
22
- v: ["c","f","g","b"], w: ["q","e","a","s"],
23
- x: ["z","s","d","c"], y: ["t","u","g","h"],
24
- z: ["a","s","x"],
25
- };
26
-
27
- const actions: string[] = [];
28
- const typoIndices = new Set<number>();
29
-
30
- // Pick ~2 random positions to typo (skip short strings)
31
- if (text.length > 4) {
32
- let tries = 0;
33
- while (typoIndices.size < 2 && tries < 20) {
34
- typoIndices.add(Math.floor(Math.random() * text.length));
35
- tries++;
36
- }
37
- }
38
-
39
- for (let i = 0; i < text.length; i++) {
40
- if (typoIndices.has(i)) {
41
- const ch = text[i]!.toLowerCase();
42
- const neighbors = adjacent[ch];
43
- const typo = neighbors
44
- ? neighbors[Math.floor(Math.random() * neighbors.length)] ?? ch
45
- : ch;
46
- actions.push(typo); // wrong char
47
- actions.push("BACKSPACE"); // correct it
48
- }
49
- actions.push(text[i]!);
50
- }
51
-
52
- return actions;
53
- }
1
+ // piggy/human/index.ts
2
+ import { PiggyClient } from "../client";
3
+
4
+ // ─── Local utilities ──────────────────────────────────────────────────────────
5
+
6
+ export function randomDelay(min: number, max: number): Promise<void> {
7
+ return new Promise(r => setTimeout(r, Math.floor(Math.random() * (max - min + 1)) + min));
8
+ }
9
+
10
+ export function humanTypeSequence(text: string): string[] {
11
+ const adjacent: Record<string, string[]> = {
12
+ a: ["q","w","s","z"], b: ["v","g","h","n"], c: ["x","d","f","v"],
13
+ d: ["s","e","r","f","c","x"], e: ["w","r","d","s"],
14
+ f: ["d","r","t","g","v","c"], g: ["f","t","y","h","b","v"],
15
+ h: ["g","y","u","j","n","b"], i: ["u","o","k","j"],
16
+ j: ["h","u","i","k","m","n"], k: ["j","i","o","l","m"],
17
+ l: ["k","o","p"], m: ["n","j","k"], n: ["b","h","j","m"],
18
+ o: ["i","p","l","k"], p: ["o","l"], q: ["w","a"],
19
+ r: ["e","t","f","d"], s: ["a","w","e","d","x","z"],
20
+ t: ["r","y","g","f"], u: ["y","i","h","j"],
21
+ v: ["c","f","g","b"], w: ["q","e","a","s"],
22
+ x: ["z","s","d","c"], y: ["t","u","g","h"],
23
+ z: ["a","s","x"],
24
+ };
25
+
26
+ const actions: string[] = [];
27
+ const typoIndices = new Set<number>();
28
+
29
+ if (text.length > 4) {
30
+ let tries = 0;
31
+ while (typoIndices.size < 2 && tries < 20) {
32
+ typoIndices.add(Math.floor(Math.random() * text.length));
33
+ tries++;
34
+ }
35
+ }
36
+
37
+ for (let i = 0; i < text.length; i++) {
38
+ if (typoIndices.has(i)) {
39
+ const ch = text[i]!.toLowerCase();
40
+ const neighbors = adjacent[ch];
41
+ const typo = neighbors
42
+ ? neighbors[Math.floor(Math.random() * neighbors.length)] ?? ch
43
+ : ch;
44
+ actions.push(typo);
45
+ actions.push("BACKSPACE");
46
+ }
47
+ actions.push(text[i]!);
48
+ }
49
+
50
+ return actions;
51
+ }
52
+
53
+ // ─── Profile types ────────────────────────────────────────────────────────────
54
+
55
+ export type TypingSpeed = "slow" | "normal" | "fast";
56
+ export type ClickDelay = "cautious" | "normal" | "fast";
57
+ export type ScrollSpeed = "slow" | "normal" | "fast";
58
+
59
+ export interface HumanProfile {
60
+ typingSpeed: TypingSpeed;
61
+ clickDelay: ClickDelay;
62
+ scrollSpeed: ScrollSpeed;
63
+ mouseWiggle: boolean;
64
+ }
65
+
66
+ export interface HumanSetOptions {
67
+ typingSpeed?: TypingSpeed;
68
+ clickDelay?: ClickDelay;
69
+ scrollSpeed?: ScrollSpeed;
70
+ mouseWiggle?: boolean;
71
+ }
72
+
73
+ export interface HumanTypeOptions {
74
+ selector: string;
75
+ text: string;
76
+ clear?: boolean;
77
+ speed?: number;
78
+ }
79
+
80
+ export interface HumanClickOptions {
81
+ selector: string;
82
+ force?: boolean;
83
+ delay?: number;
84
+ }
85
+
86
+ // ─── HumanClient ─────────────────────────────────────────────────────────────
87
+ // Maps 1:1 to PiggyHuman.cpp commands
88
+
89
+ export class HumanClient {
90
+ constructor(private client: PiggyClient) {}
91
+
92
+ // human.set — update global profile
93
+ set(opts: HumanSetOptions, tabId = "default"): Promise<HumanProfile> {
94
+ return this.client.send("human.set", { ...opts, tabId });
95
+ }
96
+
97
+ // human.get — read current profile
98
+ get(tabId = "default"): Promise<HumanProfile> {
99
+ return this.client.send("human.get", { tabId });
100
+ }
101
+
102
+ // human.type — char-by-char typing with realistic delays
103
+ type(opts: HumanTypeOptions, tabId = "default"): Promise<void> {
104
+ return this.client.send("human.type", { ...opts, tabId });
105
+ }
106
+
107
+ // human.click — delayed click with optional force dispatch
108
+ click(opts: HumanClickOptions, tabId = "default"): Promise<void> {
109
+ return this.client.send("human.click", { ...opts, tabId });
110
+ }
111
+ }
112
+
113
+ export function createHumanAPI(client: PiggyClient): HumanClient {
114
+ return new HumanClient(client);
115
+ }
@@ -0,0 +1,79 @@
1
+ // piggy/iframe/index.ts
2
+ import { PiggyClient } from "../client";
3
+
4
+ // ─── Descriptor types ─────────────────────────────────────────────────────────
5
+
6
+ export interface IframeDescriptor {
7
+ index: number;
8
+ src: string;
9
+ id: string;
10
+ name: string;
11
+ }
12
+
13
+ // ─── Option types ─────────────────────────────────────────────────────────────
14
+
15
+ /** Target an iframe by its index or src. One must be provided. */
16
+ export type IframeTarget =
17
+ | { index: number; src?: never }
18
+ | { src: string; index?: never };
19
+
20
+ export type IframeEvaluateOptions = IframeTarget & { js: string };
21
+ export type IframeClickOptions = IframeTarget & { sel: string };
22
+ export type IframeTypeOptions = IframeTarget & { sel: string; text: string };
23
+ export type IframeTextOptions = IframeTarget & { sel: string };
24
+ export type IframeHtmlOptions = IframeTarget;
25
+ export type IframeWaitSelOptions = IframeTarget & { sel: string; timeout?: number };
26
+
27
+ // ─── IframeClient ─────────────────────────────────────────────────────────────
28
+
29
+ export class IframeClient {
30
+ constructor(private client: PiggyClient) {}
31
+
32
+ /** List all iframes on the page: index, src, id, name. */
33
+ list(tabId = "default"): Promise<IframeDescriptor[]> {
34
+ return this.client.send("iframe.list", { tabId });
35
+ }
36
+
37
+ /** Run arbitrary JS inside the targeted iframe. Returns whatever the script returns. */
38
+ evaluate(opts: IframeEvaluateOptions, tabId = "default"): Promise<unknown> {
39
+ return this.client.send("iframe.evaluate", { ...opts, tabId });
40
+ }
41
+
42
+ /** Click a selector inside the targeted iframe. */
43
+ click(opts: IframeClickOptions, tabId = "default"): Promise<boolean> {
44
+ return this.client.send("iframe.click", { ...opts, tabId });
45
+ }
46
+
47
+ /** Type text into a selector inside the targeted iframe. */
48
+ type(opts: IframeTypeOptions, tabId = "default"): Promise<boolean> {
49
+ return this.client.send("iframe.type", { ...opts, tabId });
50
+ }
51
+
52
+ /** Get innerText of a selector inside the targeted iframe. */
53
+ text(opts: IframeTextOptions, tabId = "default"): Promise<string> {
54
+ return this.client.send("iframe.text", { ...opts, tabId });
55
+ }
56
+
57
+ /** Get the full HTML of the targeted iframe. */
58
+ html(opts: IframeHtmlOptions, tabId = "default"): Promise<string> {
59
+ return this.client.send("iframe.html", { ...opts, tabId });
60
+ }
61
+
62
+ /** Wait until a selector appears inside the targeted iframe. */
63
+ waitSel(opts: IframeWaitSelOptions, tabId = "default"): Promise<boolean> {
64
+ return this.client.send("iframe.waitSel", { ...opts, tabId });
65
+ }
66
+ }
67
+
68
+ // ─── Factory helper ───────────────────────────────────────────────────────────
69
+
70
+ export function createIframeAPI(client: PiggyClient): IframeClient {
71
+ return new IframeClient(client);
72
+ }
73
+ // Done. One thing worth noting — `IframeTarget` is a discriminated union so TypeScript enforces you pass either `index` or `src`, never both. Usage looks like:
74
+
75
+ // ```js
76
+ // piggy.google.iframe.list()
77
+ // piggy.google.iframe.click({ index: 0, sel: "#btn" })
78
+ // piggy.google.iframe.evaluate({ src: "https://...", js: "document.title" })
79
+ // ```
@@ -0,0 +1,79 @@
1
+ // piggy/interactions/index.ts
2
+ import { PiggyClient } from "../client";
3
+
4
+ export interface MousePosition {
5
+ x: number;
6
+ y: number;
7
+ }
8
+
9
+ export class InteractionsClient {
10
+ constructor(private client: PiggyClient) {}
11
+
12
+ // ── Click ─────────────────────────────────────────────────────────────────
13
+
14
+ click(selector: string, tabId = "default"): Promise<boolean> {
15
+ return this.client.send("click", { selector, tabId });
16
+ }
17
+
18
+ dblclick(selector: string, tabId = "default"): Promise<boolean> {
19
+ return this.client.send("dblclick", { selector, tabId });
20
+ }
21
+
22
+ hover(selector: string, tabId = "default"): Promise<boolean> {
23
+ return this.client.send("hover", { selector, tabId });
24
+ }
25
+
26
+ // ── Input ─────────────────────────────────────────────────────────────────
27
+
28
+ type(selector: string, text: string, tabId = "default"): Promise<boolean> {
29
+ return this.client.send("type", { selector, text, tabId });
30
+ }
31
+
32
+ typeClear(selector: string, text: string, tabId = "default"): Promise<boolean> {
33
+ return this.client.send("type", { selector, text, clear: true, tabId });
34
+ }
35
+
36
+ select(selector: string, value: string, tabId = "default"): Promise<boolean> {
37
+ return this.client.send("select", { selector, value, tabId });
38
+ }
39
+
40
+ // ── Scroll ────────────────────────────────────────────────────────────────
41
+
42
+ scrollTo(selector: string, tabId = "default"): Promise<boolean> {
43
+ return this.client.send("scroll.to", { selector, tabId });
44
+ }
45
+
46
+ scrollBy(px: number, tabId = "default"): Promise<boolean> {
47
+ return this.client.send("scroll.by", { px, tabId });
48
+ }
49
+
50
+ // ── Keyboard ──────────────────────────────────────────────────────────────
51
+
52
+ keyPress(key: string, tabId = "default"): Promise<boolean> {
53
+ return this.client.send("keyboard.press", { key, tabId });
54
+ }
55
+
56
+ keyCombo(combo: string, tabId = "default"): Promise<boolean> {
57
+ return this.client.send("keyboard.combo", { combo, tabId });
58
+ }
59
+
60
+ // ── Mouse ─────────────────────────────────────────────────────────────────
61
+
62
+ mouseMove(x: number, y: number, tabId = "default"): Promise<boolean> {
63
+ return this.client.send("mouse.move", { x, y, tabId });
64
+ }
65
+
66
+ mouseDrag(from: MousePosition, to: MousePosition, tabId = "default"): Promise<boolean> {
67
+ return this.client.send("mouse.drag", { from, to, tabId });
68
+ }
69
+
70
+ // ── Evaluate ──────────────────────────────────────────────────────────────
71
+
72
+ evaluate(js: string, tabId = "default"): Promise<unknown> {
73
+ return this.client.send("evaluate", { js, tabId });
74
+ }
75
+ }
76
+
77
+ export function createInteractionsAPI(client: PiggyClient): InteractionsClient {
78
+ return new InteractionsClient(client);
79
+ }