nothing-browser 0.1.3 → 0.1.4

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 (120) hide show
  1. package/package.json +14 -26
  2. package/piggy/cache/{memory.ts → memory.js} +5 -10
  3. package/piggy/captcha/{index.ts → index.js} +19 -28
  4. package/piggy/capture/index.js +40 -0
  5. package/piggy/client/{index.ts → index.js} +133 -154
  6. package/piggy/dialog/{index.ts → index.js} +15 -31
  7. package/piggy/export/index.js +105 -0
  8. package/piggy/expose/index.js +24 -0
  9. package/piggy/find/{index.ts → index.js} +24 -39
  10. package/piggy/http/{index.ts → index.js} +7 -16
  11. package/piggy/human/{index.ts → index.js} +18 -49
  12. package/piggy/iframe/index.js +56 -0
  13. package/piggy/interactions/{index.ts → index.js} +20 -23
  14. package/piggy/intercept/{scripts.ts → scripts.js} +4 -13
  15. package/piggy/launch/{detect.ts → detect.js} +4 -6
  16. package/piggy/launch/{spawn.ts → spawn.js} +17 -22
  17. package/piggy/media/{index.ts → index.js} +13 -11
  18. package/piggy/navigation/{index.ts → index.js} +16 -14
  19. package/piggy/pool/index.js +76 -0
  20. package/piggy/provide/index.js +97 -0
  21. package/piggy/proxy/index.js +95 -0
  22. package/piggy/register/index.js +543 -0
  23. package/piggy/router/index.js +44 -0
  24. package/piggy/server/{index.ts → index.js} +16 -64
  25. package/piggy/session/index.js +69 -0
  26. package/piggy/store/{index.ts → index.js} +22 -62
  27. package/piggy/tabs/index.js +24 -0
  28. package/piggy/wait/index.js +77 -0
  29. package/{piggy.ts → piggy.js} +122 -146
  30. package/dist/cache/memory.js +0 -35
  31. package/dist/client/index.js +0 -1284
  32. package/dist/human/index.js +0 -82
  33. package/dist/launch/detect.js +0 -782
  34. package/dist/launch/spawn.js +0 -915
  35. package/dist/logger/index.js +0 -733
  36. package/dist/piggy/cache/memory.d.ts +0 -7
  37. package/dist/piggy/cache/memory.d.ts.map +0 -1
  38. package/dist/piggy/captcha/index.d.ts +0 -39
  39. package/dist/piggy/captcha/index.d.ts.map +0 -1
  40. package/dist/piggy/capture/index.d.ts +0 -48
  41. package/dist/piggy/capture/index.d.ts.map +0 -1
  42. package/dist/piggy/client/index.d.ts +0 -146
  43. package/dist/piggy/client/index.d.ts.map +0 -1
  44. package/dist/piggy/dialog/index.d.ts +0 -28
  45. package/dist/piggy/dialog/index.d.ts.map +0 -1
  46. package/dist/piggy/export/index.d.ts +0 -62
  47. package/dist/piggy/export/index.d.ts.map +0 -1
  48. package/dist/piggy/expose/index.d.ts +0 -6
  49. package/dist/piggy/expose/index.d.ts.map +0 -1
  50. package/dist/piggy/find/index.d.ts +0 -52
  51. package/dist/piggy/find/index.d.ts.map +0 -1
  52. package/dist/piggy/http/index.d.ts +0 -14
  53. package/dist/piggy/http/index.d.ts.map +0 -1
  54. package/dist/piggy/human/index.d.ts +0 -39
  55. package/dist/piggy/human/index.d.ts.map +0 -1
  56. package/dist/piggy/iframe/index.d.ts +0 -53
  57. package/dist/piggy/iframe/index.d.ts.map +0 -1
  58. package/dist/piggy/interactions/index.d.ts +0 -24
  59. package/dist/piggy/interactions/index.d.ts.map +0 -1
  60. package/dist/piggy/intercept/scripts.d.ts +0 -13
  61. package/dist/piggy/intercept/scripts.d.ts.map +0 -1
  62. package/dist/piggy/launch/detect.d.ts +0 -3
  63. package/dist/piggy/launch/detect.d.ts.map +0 -1
  64. package/dist/piggy/launch/spawn.d.ts +0 -6
  65. package/dist/piggy/launch/spawn.d.ts.map +0 -1
  66. package/dist/piggy/logger/index.d.ts +0 -3
  67. package/dist/piggy/logger/index.d.ts.map +0 -1
  68. package/dist/piggy/media/index.d.ts +0 -11
  69. package/dist/piggy/media/index.d.ts.map +0 -1
  70. package/dist/piggy/navigation/index.d.ts +0 -16
  71. package/dist/piggy/navigation/index.d.ts.map +0 -1
  72. package/dist/piggy/pool/index.d.ts +0 -23
  73. package/dist/piggy/pool/index.d.ts.map +0 -1
  74. package/dist/piggy/provide/index.d.ts +0 -98
  75. package/dist/piggy/provide/index.d.ts.map +0 -1
  76. package/dist/piggy/proxy/index.d.ts +0 -94
  77. package/dist/piggy/proxy/index.d.ts.map +0 -1
  78. package/dist/piggy/register/index.d.ts +0 -9
  79. package/dist/piggy/register/index.d.ts.map +0 -1
  80. package/dist/piggy/router/index.d.ts +0 -38
  81. package/dist/piggy/router/index.d.ts.map +0 -1
  82. package/dist/piggy/server/index.d.ts +0 -42
  83. package/dist/piggy/server/index.d.ts.map +0 -1
  84. package/dist/piggy/session/index.d.ts +0 -27
  85. package/dist/piggy/session/index.d.ts.map +0 -1
  86. package/dist/piggy/store/index.d.ts +0 -22
  87. package/dist/piggy/store/index.d.ts.map +0 -1
  88. package/dist/piggy/tabs/index.d.ts +0 -12
  89. package/dist/piggy/tabs/index.d.ts.map +0 -1
  90. package/dist/piggy/wait/index.d.ts +0 -28
  91. package/dist/piggy/wait/index.d.ts.map +0 -1
  92. package/dist/piggy.d.ts +0 -10
  93. package/dist/piggy.d.ts.map +0 -1
  94. package/dist/piggy.js +0 -30223
  95. package/dist/register/index.js +0 -23710
  96. package/dist/server/index.js +0 -26831
  97. package/piggy/capture/index.ts +0 -76
  98. package/piggy/export/index.d.ts +0 -117
  99. package/piggy/export/index.ts +0 -147
  100. package/piggy/expose/index.ts +0 -42
  101. package/piggy/iframe/index.ts +0 -79
  102. package/piggy/intercept/scripts.d.ts +0 -13
  103. package/piggy/intercept/scripts.d.ts.map +0 -1
  104. package/piggy/launch/detect.d.ts +0 -3
  105. package/piggy/launch/detect.d.ts.map +0 -1
  106. package/piggy/launch/spawn.d.ts +0 -6
  107. package/piggy/launch/spawn.d.ts.map +0 -1
  108. package/piggy/logger/index.d.ts +0 -3
  109. package/piggy/logger/index.d.ts.map +0 -1
  110. package/piggy/pool/index.d.ts +0 -12
  111. package/piggy/pool/index.ts +0 -75
  112. package/piggy/provide/index.ts +0 -170
  113. package/piggy/proxy/index.ts +0 -154
  114. package/piggy/register/index.ts +0 -575
  115. package/piggy/router/index.ts +0 -69
  116. package/piggy/session/index.ts +0 -76
  117. package/piggy/store/index.d.ts +0 -26
  118. package/piggy/tabs/index.ts +0 -23
  119. package/piggy/wait/index.ts +0 -90
  120. /package/piggy/logger/{index.ts → index.js} +0 -0
@@ -0,0 +1,543 @@
1
+ // piggy/register/index.ts → stripped to JS
2
+ import { PiggyClient } from "../client";
3
+ import { HumanClient } from "../human";
4
+ import logger from "../logger";
5
+ import { routeRegistry, keepAliveSites } from "../server";
6
+ import { buildRespondScript, buildModifyResponseScript } from "../intercept/scripts";
7
+ import { storeRecord } from "../store";
8
+ import { TabPool } from "../pool";
9
+ import { createFindAPI } from "../find";
10
+ import { createProvideAPI } from "../provide";
11
+ import { createHumanAPI } from "../human";
12
+ import { createSessionAPI } from "../session";
13
+ import { DialogClient } from "../dialog";
14
+ import { createIframeAPI } from "../iframe";
15
+ import { exposeFunction, unexposeFunction, clearExposedFunctions, exposeAndInject } from "../expose";
16
+ import { createCaptchaAPI } from "../captcha";
17
+
18
+ let globalClient = null;
19
+ export let humanMode = false;
20
+
21
+ export function setClient(c) { globalClient = c; }
22
+ export function setHumanMode(v) { humanMode = v; }
23
+
24
+ async function retry(label, fn, retries = 2, backoff = 150) {
25
+ let last;
26
+ for (let i = 0; i <= retries; i++) {
27
+ try { return await fn(); } catch (e) {
28
+ last = e;
29
+ if (i < retries) {
30
+ logger.warn(`[${label}] retry ${i + 1}/${retries}: ${e.message}`);
31
+ await new Promise(r => setTimeout(r, backoff * (i + 1)));
32
+ }
33
+ }
34
+ }
35
+ throw last;
36
+ }
37
+
38
+ export function createSiteObject(name, registeredUrl, client, tabId, pool) {
39
+ let _currentUrl = registeredUrl;
40
+ let _modifyRuleCounter = 0;
41
+
42
+ function withTab(fn) {
43
+ return pool ? pool.withTab(fn) : fn(tabId);
44
+ }
45
+
46
+ const _eventListeners = new Map();
47
+
48
+ const _unsubNavigate = client.onEvent("navigate", tabId, (url) => {
49
+ _currentUrl = url;
50
+ const handlers = _eventListeners.get("navigate");
51
+ if (handlers) {
52
+ for (const h of handlers) {
53
+ try { h(url); } catch (e) { logger.error(`[${name}] navigate handler error: ${e}`); }
54
+ }
55
+ }
56
+ });
57
+
58
+ const withErrScreen = async (fn, label) => {
59
+ try { return await fn(); } catch (err) {
60
+ const p = `./error-${name}-${Date.now()}.png`;
61
+ try { await client.screenshot(p, tabId); logger.error(`[${name}] ${label} failed → ${p}`); }
62
+ catch { logger.error(`[${name}] ${label} failed (no screenshot)`); }
63
+ throw err;
64
+ }
65
+ };
66
+
67
+ const site = {
68
+ _name: name,
69
+ _tabId: tabId,
70
+ _pool: pool ?? null,
71
+
72
+ poolStats: () => pool?.stats ?? null,
73
+
74
+ navigate: (url, opts) => {
75
+ const target = url ?? registeredUrl;
76
+ return withTab(t =>
77
+ retry(name, async () => {
78
+ logger.network(`[${name}] navigating → ${target}`);
79
+ await client.navigate(target, t);
80
+ _currentUrl = target;
81
+ }, opts?.retries ?? 2)
82
+ );
83
+ },
84
+
85
+ reload: () => withTab(t => client.reload(t)),
86
+ goBack: () => withTab(t => client.goBack(t)),
87
+ goForward: () => withTab(t => client.goForward(t)),
88
+ waitForNavigation: () => withTab(t => client.waitForNavigation(t)),
89
+
90
+ title: () => withTab(async t => {
91
+ const title = await client.getTitle(t);
92
+ logger.info(`[${name}] title: ${title}`);
93
+ return title;
94
+ }),
95
+
96
+ url: () => _currentUrl,
97
+ content: () => withTab(t => client.content(t)),
98
+
99
+ wait: (ms) => {
100
+ const actual = humanMode ? ms + Math.floor(Math.random() * 600) - 300 : ms;
101
+ return new Promise(r => setTimeout(r, Math.max(0, actual)));
102
+ },
103
+
104
+ waitForSelector: (selector, timeout = 30000) =>
105
+ withTab(t => {
106
+ logger.debug(`[${name}] waitForSelector: ${selector}`);
107
+ return client.waitForSelector(selector, timeout, t);
108
+ }),
109
+
110
+ exposeFunction: (fnName, handler) =>
111
+ exposeFunction(client, fnName, handler, tabId).then(() => site),
112
+
113
+ unexposeFunction: (fnName) =>
114
+ unexposeFunction(client, fnName, tabId).then(() => site),
115
+
116
+ clearExposedFunctions: () =>
117
+ clearExposedFunctions(client, tabId).then(() => site),
118
+
119
+ exposeAndInject: (fnName, handler, injectionJs) =>
120
+ exposeAndInject(client, fnName, handler, injectionJs, tabId).then(() => site),
121
+
122
+ waitForVisible: (selector, timeout = 30000) =>
123
+ withTab(t => client.waitForSelector(selector, timeout, t)),
124
+
125
+ waitForResponse: (pattern, timeout = 30000) =>
126
+ withTab(t => client.waitForResponse(pattern, timeout, t)),
127
+
128
+ addInitScript: async (js) => {
129
+ const code = typeof js === "function" ? `(${js.toString()})();` : js;
130
+ await withTab(t => client.addInitScript(code, t));
131
+ logger.success(`[${name}] init script added`);
132
+ return site;
133
+ },
134
+
135
+ on: (event, handler) => {
136
+ if (!_eventListeners.has(event)) _eventListeners.set(event, new Set());
137
+ _eventListeners.get(event).add(handler);
138
+ logger.debug(`[${name}] on('${event}') registered`);
139
+ return () => {
140
+ _eventListeners.get(event)?.delete(handler);
141
+ logger.debug(`[${name}] on('${event}') unsubscribed`);
142
+ };
143
+ },
144
+
145
+ off: (event, handler) => {
146
+ _eventListeners.get(event)?.delete(handler);
147
+ },
148
+
149
+ click: (selector, opts) =>
150
+ withErrScreen(() =>
151
+ withTab(t =>
152
+ retry(name, async () => {
153
+ if (humanMode) await new Promise(r => setTimeout(r, 80 + Math.random() * 140));
154
+ await client.waitForSelector(selector, opts?.timeout ?? 15000, t);
155
+ const ok = await client.click(selector, t);
156
+ if (!ok) throw new Error(`click failed: ${selector}`);
157
+ logger.success(`[${name}] clicked: ${selector}`);
158
+ return ok;
159
+ }, opts?.retries ?? 2)
160
+ ),
161
+ `click(${selector})`
162
+ ),
163
+
164
+ doubleClick: (selector) =>
165
+ withErrScreen(() =>
166
+ withTab(async t => {
167
+ if (humanMode) await new Promise(r => setTimeout(r, 80 + Math.random() * 120));
168
+ return client.doubleClick(selector, t);
169
+ }),
170
+ `dblclick(${selector})`
171
+ ),
172
+
173
+ hover: (selector) =>
174
+ withErrScreen(() =>
175
+ withTab(async t => {
176
+ if (humanMode) await new Promise(r => setTimeout(r, 50 + Math.random() * 100));
177
+ return client.hover(selector, t);
178
+ }),
179
+ `hover(${selector})`
180
+ ),
181
+
182
+ type: (selector, text, opts) =>
183
+ withErrScreen(() =>
184
+ withTab(async t => {
185
+ await client.waitForSelector(selector, 30000, t);
186
+
187
+ if (humanMode) {
188
+ const human = new HumanClient(client);
189
+ await human.type({
190
+ selector,
191
+ text,
192
+ clear: opts?.clear ?? false,
193
+ speed: opts?.speed
194
+ }, t);
195
+ } else {
196
+ if (opts?.clear) {
197
+ await client.evaluate(`
198
+ const el = document.querySelector('${selector.replace(/'/g, "\\'")}');
199
+ if (el) { el.value = ''; el.dispatchEvent(new Event('input', { bubbles: true })); }
200
+ `, t);
201
+ }
202
+ await client.type(selector, text, t);
203
+ }
204
+
205
+ await client.evaluate(`
206
+ document.querySelector('${selector.replace(/'/g, "\\'")}')?.dispatchEvent(new Event('blur', { bubbles: true }));
207
+ `, t);
208
+
209
+ logger.success(`[${name}] typed into: ${selector}`);
210
+ return true;
211
+ }),
212
+ `type(${selector})`
213
+ ),
214
+
215
+ select: (selector, value) => withTab(t => client.select(selector, value, t)),
216
+
217
+ evaluate: (js, ...args) => {
218
+ const code = typeof js === "function"
219
+ ? `(${js.toString()})(${args.map(a => JSON.stringify(a)).join(",")})`
220
+ : js;
221
+ return withTab(t => client.evaluate(code, t));
222
+ },
223
+
224
+ keyboard: {
225
+ press: (key) => withTab(t => client.keyPress(key, t)),
226
+ combo: (combo) => withTab(t => client.keyCombo(combo, t)),
227
+ },
228
+
229
+ mouse: {
230
+ move: (x, y) => withTab(t => client.mouseMove(x, y, t)),
231
+ drag: (from, to) => withTab(t => client.mouseDrag(from, to, t)),
232
+ },
233
+
234
+ scroll: {
235
+ to: (selector) => withTab(t => client.scrollTo(selector, t)),
236
+ by: (px) => withTab(async t => {
237
+ if (humanMode) {
238
+ const steps = Math.ceil(Math.abs(px) / 120);
239
+ const chunk = px / steps;
240
+ for (let i = 0; i < steps; i++) {
241
+ await client.scrollBy(chunk, t);
242
+ await new Promise(r => setTimeout(r, 30 + Math.random() * 50));
243
+ }
244
+ } else {
245
+ await client.scrollBy(px, t);
246
+ }
247
+ }),
248
+ },
249
+
250
+ fetchText: (selector) => withTab(t => client.fetchText(selector, t)),
251
+
252
+ fetchLinks: async (selector) => {
253
+ const links = await withTab(t => client.fetchLinks(selector, t));
254
+ logger.info(`[${name}] fetchLinks(${selector}): ${links.length}`);
255
+ return links;
256
+ },
257
+
258
+ fetchImages: async (selector) => {
259
+ const imgs = await withTab(t => client.fetchImages(selector, t));
260
+ logger.info(`[${name}] fetchImages(${selector}): ${imgs.length}`);
261
+ return imgs;
262
+ },
263
+
264
+ search: {
265
+ css: (query) => withTab(t => client.searchCss(query, t)),
266
+ id: (query) => withTab(t => client.searchId(query, t)),
267
+ },
268
+
269
+ captcha: {
270
+ status: () => withTab(t => createCaptchaAPI(client).status(t)),
271
+ resolve: () => withTab(t => createCaptchaAPI(client).resolve(t)),
272
+ pause: () => withTab(t => createCaptchaAPI(client).pause(t)),
273
+ check: () => withTab(t => createCaptchaAPI(client).check(t)),
274
+ autoRetry: (opts) => withTab(t => createCaptchaAPI(client).setAutoRetry(opts.enabled)),
275
+ onCaptcha: (handler) => createCaptchaAPI(client).onCaptcha(tabId, handler),
276
+ onResolved: (handler) => createCaptchaAPI(client).onCaptchaResolved(tabId, handler),
277
+ },
278
+
279
+ block: {
280
+ status: () => withTab(t => createCaptchaAPI(client).blockStatus(t)),
281
+ retry: () => withTab(t => createCaptchaAPI(client).blockRetry(t)),
282
+ onBlocked: (handler) => createCaptchaAPI(client).onBlocked(tabId, handler),
283
+ onRetry: (handler) => createCaptchaAPI(client).onBlockRetry(tabId, handler),
284
+ },
285
+
286
+ find: {
287
+ css: (selector) => withTab(t => {
288
+ console.log("[DEBUG] find.css tabId:", t);
289
+ return createFindAPI(client).css(selector, t);
290
+ }),
291
+ all: (selector) => withTab(t => createFindAPI(client).all(selector, t)),
292
+ first: (selector) => withTab(t => createFindAPI(client).first(selector, t)),
293
+ byText: (text) => withTab(t => createFindAPI(client).byText(text, t)),
294
+ byAttr: (attr, value) => withTab(t => createFindAPI(client).byAttr(attr, value, t)),
295
+ byTag: (tag) => withTab(t => createFindAPI(client).byTag(tag, t)),
296
+ byPlaceholder: (text) => withTab(t => createFindAPI(client).byPlaceholder(text, t)),
297
+ byRole: (role, name) => withTab(t => createFindAPI(client).byRole(role, name, t)),
298
+ children: (selector) => withTab(t => createFindAPI(client).children(selector, t)),
299
+ parent: (selector) => withTab(t => createFindAPI(client).parent(selector, t)),
300
+ closest: (selector, ancestor) => withTab(t => createFindAPI(client).closest(selector, ancestor, t)),
301
+ count: (selector) => withTab(t => createFindAPI(client).count(selector, t)),
302
+ exists: (selector) => withTab(t => createFindAPI(client).exists(selector, t)),
303
+ visible: (selector) => withTab(t => createFindAPI(client).visible(selector, t)),
304
+ enabled: (selector) => withTab(t => createFindAPI(client).enabled(selector, t)),
305
+ checked: (selector) => withTab(t => createFindAPI(client).checked(selector, t)),
306
+ },
307
+
308
+ provide: {
309
+ text: (opts) => withTab(t => createProvideAPI(client).text(opts, t)),
310
+ textAll: (opts) => withTab(t => createProvideAPI(client).textAll(opts, t)),
311
+ attr: (opts) => withTab(t => createProvideAPI(client).attr(opts, t)),
312
+ attrAll: (opts) => withTab(t => createProvideAPI(client).attrAll(opts, t)),
313
+ html: (opts) => withTab(t => createProvideAPI(client).html(opts, t)),
314
+ table: (opts) => withTab(t => createProvideAPI(client).table(opts, t)),
315
+ list: (opts) => withTab(t => createProvideAPI(client).list(opts, t)),
316
+ links: (opts) => withTab(t => createProvideAPI(client).links(opts, t)),
317
+ images: (opts) => withTab(t => createProvideAPI(client).images(opts, t)),
318
+ form: (opts) => withTab(t => createProvideAPI(client).form(opts, t)),
319
+ page: () => withTab(t => createProvideAPI(client).page(t)),
320
+ div: (opts) => withTab(t => createProvideAPI(client).div(opts, t)),
321
+ meta: () => withTab(t => createProvideAPI(client).meta(t)),
322
+ select: (opts) => withTab(t => createProvideAPI(client).select(opts, t)),
323
+ json: (opts) => withTab(t => createProvideAPI(client).json(opts, t)),
324
+ },
325
+
326
+ iframe: {
327
+ list: () => withTab(t => createIframeAPI(client).list(t)),
328
+ evaluate: (opts) => withTab(t => createIframeAPI(client).evaluate(opts, t)),
329
+ click: (opts) => withTab(t => createIframeAPI(client).click(opts, t)),
330
+ type: (opts) => withTab(t => createIframeAPI(client).type(opts, t)),
331
+ text: (opts) => withTab(t => createIframeAPI(client).text(opts, t)),
332
+ html: (opts) => withTab(t => createIframeAPI(client).html(opts, t)),
333
+ waitSel: (opts) => withTab(t => createIframeAPI(client).waitSel(opts, t)),
334
+ },
335
+
336
+ screenshot: async (filePath) => {
337
+ const r = await withTab(t => client.screenshot(filePath, t));
338
+ logger.success(`[${name}] screenshot → ${filePath ?? "base64"}`);
339
+ return r;
340
+ },
341
+
342
+ pdf: async (filePath) => {
343
+ const r = await withTab(t => client.pdf(filePath, t));
344
+ logger.success(`[${name}] pdf → ${filePath ?? "base64"}`);
345
+ return r;
346
+ },
347
+
348
+ human: {
349
+ set: (opts) => withTab(t => createHumanAPI(client).set(opts, t)),
350
+ get: () => withTab(t => createHumanAPI(client).get(t)),
351
+ type: (opts) => withTab(t => createHumanAPI(client).type(opts, t)),
352
+ click: (opts) => withTab(t => createHumanAPI(client).click(opts, t)),
353
+ },
354
+
355
+ blockImages: () => withTab(async t => { await client.blockImages(t); logger.info(`[${name}] images blocked`); }),
356
+ unblockImages: () => withTab(async t => { await client.unblockImages(t); logger.info(`[${name}] images unblocked`); }),
357
+
358
+ cookies: {
359
+ set: async (cookieName, value, domain, path = "/") => {
360
+ await withTab(t => client.setCookie(cookieName, value, domain, path, t));
361
+ logger.info(`[${name}] cookie set: ${cookieName} @ ${domain}`);
362
+ },
363
+ get: (cookieName, domain = "") => withTab(t => client.getCookie(cookieName, domain, t)),
364
+ delete: async (cookieName, domain) => {
365
+ const d = domain ?? new URL(registeredUrl).hostname;
366
+ await withTab(t => client.deleteCookie(cookieName, d, t));
367
+ logger.info(`[${name}] cookie deleted: ${cookieName}`);
368
+ },
369
+ list: (domain = "") => withTab(t => client.listCookies(domain, t)),
370
+ },
371
+
372
+ intercept: {
373
+ block: async (pattern) => {
374
+ await withTab(t => client.addInterceptRule("block", pattern, {}, t));
375
+ logger.info(`[${name}] intercept block: ${pattern}`);
376
+ },
377
+
378
+ redirect: async (pattern, redirectUrl) => {
379
+ await withTab(t => client.addInterceptRule("redirect", pattern, { redirectUrl }, t));
380
+ logger.info(`[${name}] intercept redirect: ${pattern} → ${redirectUrl}`);
381
+ },
382
+
383
+ headers: async (pattern, headers) => {
384
+ await withTab(t => client.addInterceptRule("modifyHeaders", pattern, { headers }, t));
385
+ logger.info(`[${name}] intercept modifyHeaders: ${pattern}`);
386
+ },
387
+
388
+ respond: async (pattern, handlerOrResponse) => {
389
+ const isStatic = typeof handlerOrResponse === "object";
390
+
391
+ if (!isStatic) {
392
+ const fnName = `__piggy_respond_${name}_${++_modifyRuleCounter}__`;
393
+ await client.exposeFunction(fnName, async (req) => {
394
+ try {
395
+ const result = handlerOrResponse(req);
396
+ return { success: true, result: { status: result.status ?? 200, contentType: result.contentType ?? "application/json", body: result.body ?? "" } };
397
+ } catch (e) {
398
+ return { success: false, error: e.message };
399
+ }
400
+ }, tabId);
401
+
402
+ const dynamicScript = `
403
+ (function() {
404
+ 'use strict';
405
+ if (!window.__PIGGY_DYNAMIC_RESPOND__) window.__PIGGY_DYNAMIC_RESPOND__ = [];
406
+ window.__PIGGY_DYNAMIC_RESPOND__.push({ pattern: ${JSON.stringify(pattern)}, fn: ${JSON.stringify(fnName)} });
407
+ function matchUrl(url, pattern) { try { return url.includes(pattern) || new RegExp(pattern).test(url); } catch { return url.includes(pattern); } }
408
+ if (window.__PIGGY_DYN_INSTALLED__) return;
409
+ window.__PIGGY_DYN_INSTALLED__ = true;
410
+ const _origFetch = window.fetch;
411
+ window.fetch = async function(input, init) {
412
+ const url = typeof input === 'string' ? input : (input?.url ?? String(input));
413
+ const method = (init?.method ?? 'GET').toUpperCase();
414
+ const rules = window.__PIGGY_DYNAMIC_RESPOND__ || [];
415
+ for (const rule of rules) {
416
+ if (matchUrl(url, rule.pattern) && typeof window[rule.fn] === 'function') {
417
+ try { const r = await window[rule.fn]({ url, method }); return new Response(r.body ?? '', { status: r.status ?? 200, headers: { 'Content-Type': r.contentType ?? 'application/json' } }); } catch { break; }
418
+ }
419
+ }
420
+ return _origFetch.apply(this, arguments);
421
+ };
422
+ })();`;
423
+ await withTab(async t => {
424
+ await client.addInitScript(dynamicScript, t);
425
+ await client.evaluate(dynamicScript, t);
426
+ });
427
+ logger.success(`[${name}] intercept.respond (dynamic): ${pattern}`);
428
+ return site;
429
+ }
430
+
431
+ const response = handlerOrResponse;
432
+ const script = buildRespondScript(pattern, response.status ?? 200, response.contentType ?? "application/json", response.body);
433
+ await withTab(async t => {
434
+ await client.addInitScript(script, t);
435
+ await client.evaluate(script, t);
436
+ });
437
+ logger.success(`[${name}] intercept.respond (static): ${pattern} → ${response.status ?? 200}`);
438
+ return site;
439
+ },
440
+
441
+ modifyResponse: async (pattern, handler) => {
442
+ const fnName = `__piggy_modres_${name}_${++_modifyRuleCounter}__`;
443
+ await client.exposeFunction(fnName, async (response) => {
444
+ try { const mod = await handler(response); return { success: true, result: mod ?? {} }; }
445
+ catch (e) { return { success: false, error: e.message }; }
446
+ }, tabId);
447
+
448
+ const script = buildModifyResponseScript(pattern, fnName);
449
+ await withTab(async t => {
450
+ await client.addInitScript(script, t);
451
+ await client.evaluate(script, t);
452
+ });
453
+ logger.success(`[${name}] intercept.modifyResponse: ${pattern}`);
454
+ return site;
455
+ },
456
+
457
+ clear: async () => {
458
+ await withTab(t => client.clearInterceptRules(t));
459
+ logger.info(`[${name}] intercept rules cleared`);
460
+ },
461
+ },
462
+
463
+ dialog: {
464
+ accept: (tabId = "default", text) => new DialogClient(client).accept(tabId, text),
465
+ dismiss: (tabId = "default") => new DialogClient(client).dismiss(tabId),
466
+ status: (tabId = "default") => new DialogClient(client).status(tabId),
467
+ setAutoAction: (tabId = "default", action) => new DialogClient(client).setAutoAction(tabId, action),
468
+ upload: (selector, filePath, tabId = "default") => new DialogClient(client).upload(selector, filePath, tabId),
469
+ onDialog: (tabId, handler) => new DialogClient(client).onDialog(tabId, handler),
470
+ waitAndAccept: (tabId = "default", text, timeoutMs = 30000) => new DialogClient(client).waitAndAccept(tabId, text, timeoutMs),
471
+ waitAndDismiss: (tabId = "default", timeoutMs = 30000) => new DialogClient(client).waitAndDismiss(tabId, timeoutMs),
472
+ },
473
+
474
+ capture: {
475
+ start: () => withTab(async t => { await client.captureStart(t); logger.info(`[${name}] capture started`); }),
476
+ stop: () => withTab(async t => { await client.captureStop(t); logger.info(`[${name}] capture stopped`); }),
477
+ requests: () => withTab(t => client.captureRequests(t)),
478
+ ws: () => withTab(t => client.captureWs(t)),
479
+ cookies: () => withTab(t => client.captureCookies(t)),
480
+ storage: () => withTab(t => client.captureStorage(t)),
481
+ clear: () => withTab(async t => { await client.captureClear(t); logger.info(`[${name}] capture cleared`); }),
482
+ },
483
+
484
+ session: {
485
+ export: () => withTab(t => createSessionAPI(client).export(t)),
486
+ import: (data) => withTab(t => createSessionAPI(client).import(data, t)),
487
+ reload: () => withTab(t => createSessionAPI(client).reload(t)),
488
+ paths: () => createSessionAPI(client).paths(),
489
+ cookies: { path: () => createSessionAPI(client).cookiesPath() },
490
+ profile: { path: () => createSessionAPI(client).profilePath() },
491
+ ws: { save: (opts) => createSessionAPI(client).setWsSave(opts.enabled) },
492
+ pings: { save: (opts) => createSessionAPI(client).setPingsSave(opts.enabled) },
493
+ },
494
+
495
+ store: async (data, schemaName) => {
496
+ const target = schemaName ?? name;
497
+ const result = await storeRecord(target, data);
498
+ logger.info(`[${name}] store → stored: ${result.stored}, skipped: ${result.skipped}`);
499
+ return result;
500
+ },
501
+
502
+ api: (path, handler, opts) => {
503
+ const key = `${name}:${path}`;
504
+ if (routeRegistry.has(key)) { logger.warn(`[${name}] route ${path} already registered`); return site; }
505
+ routeRegistry.set(key, {
506
+ path,
507
+ method: opts?.method ?? "GET",
508
+ handler,
509
+ ttl: opts?.ttl ?? 360_000,
510
+ before: opts?.before ?? [],
511
+ detail: opts?.detail,
512
+ });
513
+ logger.info(`[${name}] api route: ${opts?.method ?? "GET"} /${name}${path}`);
514
+ return site;
515
+ },
516
+
517
+ noclose: () => { keepAliveSites.add(name); logger.info(`[${name}] keep-alive`); return site; },
518
+
519
+ close: async () => {
520
+ _unsubNavigate();
521
+ keepAliveSites.delete(name);
522
+ if (pool) {
523
+ await pool.close();
524
+ } else if (tabId !== "default") {
525
+ await client.closeTab(tabId);
526
+ logger.info(`[${name}] tab closed`);
527
+ }
528
+ },
529
+ };
530
+
531
+ return site;
532
+ }
533
+
534
+ export function createExposedAPI(site, apiName, handlers) {
535
+ const wrappedHandler = async (call) => {
536
+ const { method, args } = call;
537
+ const handler = handlers[method];
538
+ if (!handler) throw new Error(`Unknown method: ${method}`);
539
+ try { return await handler(args); }
540
+ catch (err) { logger.error(`[${site._name}] API error in ${method}: ${err}`); throw err; }
541
+ };
542
+ return site.exposeFunction(apiName, wrappedHandler);
543
+ }
@@ -0,0 +1,44 @@
1
+ // piggy/router/index.js
2
+ // Mirrors PiggyCommandRouter.cpp.
3
+ // This is the single entry point that composes all sub-clients
4
+ // into one object — the same way the C++ router dispatches to sub-handlers.
5
+
6
+ import { PiggyClient } from "../client.js";
7
+ import { createCaptureAPI } from "../capture/index.js";
8
+ import { createCaptchaAPI } from "../captcha/index.js";
9
+ import { createDialogAPI } from "../dialog/index.js";
10
+ import { createExportAPI } from "../export/index.js";
11
+ import { createFindAPI } from "../find/index.js";
12
+ import { createHumanAPI } from "../human/index.js";
13
+ import { createIframeAPI } from "../iframe/index.js";
14
+ import { createInteractionsAPI } from "../interactions/index.js";
15
+ import { createMediaAPI } from "../media/index.js";
16
+ import { createNavigationAPI } from "../navigation/index.js";
17
+ import { createProvideAPI } from "../provide/index.js";
18
+ import { createProxyAPI } from "../proxy/index.js";
19
+ import { createSessionAPI } from "../session/index.js";
20
+ import { createTabsAPI } from "../tabs/index.js";
21
+ import { createWaitAPI, createEvaluateAPI, createFetchAPI } from "../wait/index.js";
22
+
23
+ export function createRouter(client) {
24
+ return {
25
+ client,
26
+ tabs: createTabsAPI(client),
27
+ navigation: createNavigationAPI(client),
28
+ interactions: createInteractionsAPI(client),
29
+ media: createMediaAPI(client),
30
+ capture: createCaptureAPI(client),
31
+ find: createFindAPI(client),
32
+ provide: createProvideAPI(client),
33
+ wait: createWaitAPI(client),
34
+ evaluate: createEvaluateAPI(client),
35
+ fetch: createFetchAPI(client),
36
+ proxy: createProxyAPI(client),
37
+ captcha: createCaptchaAPI(client),
38
+ dialog: createDialogAPI(client),
39
+ human: createHumanAPI(client),
40
+ iframe: createIframeAPI(client),
41
+ session: createSessionAPI(client),
42
+ export: createExportAPI(client),
43
+ };
44
+ }