nothing-browser 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.js +24 -6
- package/dist/piggy/client/index.d.ts +3 -11
- package/dist/piggy/client/index.d.ts.map +1 -1
- package/dist/piggy/expose/index.d.ts +6 -0
- package/dist/piggy/expose/index.d.ts.map +1 -0
- package/dist/piggy/register/index.d.ts.map +1 -1
- package/dist/piggy/tabs/index.d.ts +3 -1
- package/dist/piggy/tabs/index.d.ts.map +1 -1
- package/dist/piggy.d.ts.map +1 -1
- package/dist/piggy.js +640 -501
- package/dist/register/index.js +429 -36
- package/dist/server/index.js +1 -1
- package/package.json +1 -1
- package/piggy/client/index.ts +48 -126
- package/piggy/expose/index.ts +42 -0
- package/piggy/register/index.ts +115 -40
- package/piggy/tabs/index.ts +2 -1
- package/piggy.ts +79 -11
- package/piggy/cache/memory.d.ts +0 -7
- package/piggy/cache/memory.d.ts.map +0 -1
- package/piggy/captcha/index.d.ts +0 -35
- package/piggy/client/index.d.ts +0 -79
- package/piggy/client/index.d.ts.map +0 -1
- package/piggy/dialog/index.d.ts +0 -29
- package/piggy/human/index.d.ts +0 -7
- package/piggy/human/index.d.ts.map +0 -1
- package/piggy/register/index.d.ts +0 -7
- package/piggy/register/index.d.ts.map +0 -1
- package/piggy/server/index.d.ts +0 -58
- package/piggy/server/index.d.ts.map +0 -1
package/piggy/client/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
//piggy/client/index.ts
|
|
2
2
|
import { connect, type Socket } from "net";
|
|
3
3
|
import { writeFileSync, mkdirSync } from "fs";
|
|
4
4
|
import { dirname } from "path";
|
|
@@ -280,6 +280,23 @@ export class PiggyClient {
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
+
|
|
284
|
+
if (event.event === "dialog") {
|
|
285
|
+
const key = `dialog:${event.tabId ?? "default"}`;
|
|
286
|
+
const handlers = this.globalEventHandlers.get(key);
|
|
287
|
+
if (handlers) {
|
|
288
|
+
for (const h of handlers) {
|
|
289
|
+
try {
|
|
290
|
+
h({
|
|
291
|
+
dialogType: event.dialogType,
|
|
292
|
+
message: event.message,
|
|
293
|
+
defaultValue: event.defaultValue,
|
|
294
|
+
tabId: event.tabId,
|
|
295
|
+
});
|
|
296
|
+
} catch (e) { logger.error(`dialog handler error: ${e}`); }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
283
300
|
}
|
|
284
301
|
|
|
285
302
|
onEvent(eventName: string, tabId: string, handler: (data: any) => void): () => void {
|
|
@@ -384,9 +401,9 @@ export class PiggyClient {
|
|
|
384
401
|
|
|
385
402
|
// ── Cookies ───────────────────────────────────────────────────────────────
|
|
386
403
|
async setCookie(name: string, value: string, domain: string, path = "/", tabId = "default"): Promise<void> { await this.send("cookie.set", { name, value, domain, path, tabId }); }
|
|
387
|
-
async getCookie(name: string, tabId = "default"): Promise<any> { return this.send("cookie.get", { name, tabId }); }
|
|
388
|
-
async deleteCookie(name: string, tabId = "default"): Promise<void> { await this.send("cookie.delete", { name, tabId }); }
|
|
389
|
-
async listCookies(tabId = "default"): Promise<any[]> { return this.send<any[]>("cookie.list", { tabId }); }
|
|
404
|
+
async getCookie(name: string, domain = "", tabId = "default"): Promise<any> { return this.send("cookie.get", { name, domain, tabId }); }
|
|
405
|
+
async deleteCookie(name: string, domain: string, tabId = "default"): Promise<void> { await this.send("cookie.delete", { name, domain, tabId }); }
|
|
406
|
+
async listCookies(domain = "", tabId = "default"): Promise<any[]> { return this.send<any[]>("cookie.list", { domain, tabId }); }
|
|
390
407
|
|
|
391
408
|
// ── Interception ──────────────────────────────────────────────────────────
|
|
392
409
|
async addInterceptRule(action: "block" | "redirect" | "modifyHeaders", pattern: string, options: { redirectUrl?: string; headers?: Record<string, string> } = {}, tabId = "default"): Promise<void> {
|
|
@@ -407,55 +424,14 @@ export class PiggyClient {
|
|
|
407
424
|
async sessionExport(tabId = "default"): Promise<any> { return this.send("session.export", { tabId }); }
|
|
408
425
|
async sessionImport(data: any, tabId = "default"): Promise<void> { await this.send("session.import", { data, tabId }); }
|
|
409
426
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
async
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/** Enable or disable saving ping log to pings.json in cwd */
|
|
420
|
-
async sessionPingsSave(enabled = true): Promise<void> {
|
|
421
|
-
await this.send("session.pings.save", { enabled });
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
/** Get all data file paths for the current session */
|
|
425
|
-
async sessionPaths(): Promise<{
|
|
426
|
-
workDir: string;
|
|
427
|
-
cookies: string;
|
|
428
|
-
profile: string;
|
|
429
|
-
ws: string;
|
|
430
|
-
pings: string;
|
|
431
|
-
}> {
|
|
432
|
-
return this.send("session.paths", {});
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/** Get path to cookies.json */
|
|
436
|
-
async sessionCookiesPath(): Promise<string> {
|
|
437
|
-
return this.send("session.cookies.path", {});
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/** Get path to profile.json */
|
|
441
|
-
async sessionProfilePath(): Promise<string> {
|
|
442
|
-
return this.send("session.profile.path", {});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
/** Get path to ws.json */
|
|
446
|
-
async sessionWsPath(): Promise<string> {
|
|
447
|
-
return this.send("session.ws.path", {});
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/** Get path to pings.json */
|
|
451
|
-
async sessionPingsPath(): Promise<string> {
|
|
452
|
-
return this.send("session.pings.path", {});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/** Reload cookies.json and profile.json from disk without restarting */
|
|
456
|
-
async sessionReload(): Promise<void> {
|
|
457
|
-
await this.send("session.reload", {});
|
|
458
|
-
}
|
|
427
|
+
async sessionWsSave(enabled = true): Promise<void> { await this.send("session.ws.save", { enabled }); }
|
|
428
|
+
async sessionPingsSave(enabled = true): Promise<void> { await this.send("session.pings.save", { enabled }); }
|
|
429
|
+
async sessionPaths(): Promise<{ workDir: string; cookies: string; profile: string; ws: string; pings: string; }> { return this.send("session.paths", {}); }
|
|
430
|
+
async sessionCookiesPath(): Promise<string> { return this.send("session.cookies.path", {}); }
|
|
431
|
+
async sessionProfilePath(): Promise<string> { return this.send("session.profile.path", {}); }
|
|
432
|
+
async sessionWsPath(): Promise<string> { return this.send("session.ws.path", {}); }
|
|
433
|
+
async sessionPingsPath(): Promise<string> { return this.send("session.pings.path", {}); }
|
|
434
|
+
async sessionReload(): Promise<void> { await this.send("session.reload", {}); }
|
|
459
435
|
|
|
460
436
|
// ── Expose Function ───────────────────────────────────────────────────────
|
|
461
437
|
async exposeFunction(name: string, handler: (data: any) => Promise<any> | any, tabId = "default"): Promise<void> {
|
|
@@ -485,82 +461,28 @@ export class PiggyClient {
|
|
|
485
461
|
}
|
|
486
462
|
|
|
487
463
|
// ── Proxy ─────────────────────────────────────────────────────────────────
|
|
488
|
-
|
|
489
|
-
async
|
|
490
|
-
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
async proxyFetch(url: string): Promise<void> {
|
|
494
|
-
await this.send("proxy.fetch", { url });
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async proxyOvpn(path: string): Promise<void> {
|
|
498
|
-
await this.send("proxy.ovpn", { path });
|
|
499
|
-
}
|
|
464
|
+
async proxyLoad(path: string): Promise<void> { await this.send("proxy.load", { path }); }
|
|
465
|
+
async proxyFetch(url: string): Promise<void> { await this.send("proxy.fetch", { url }); }
|
|
466
|
+
async proxyOvpn(path: string): Promise<void> { await this.send("proxy.ovpn", { path }); }
|
|
500
467
|
|
|
501
468
|
async proxySet(opts: {
|
|
502
|
-
host?: string;
|
|
503
|
-
port?: number;
|
|
469
|
+
host?: string; port?: number;
|
|
504
470
|
type?: "http" | "https" | "socks5" | "socks4";
|
|
505
|
-
user?: string;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
async
|
|
513
|
-
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
async
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async proxyNext(): Promise<void> {
|
|
521
|
-
await this.send("proxy.next", {});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
async proxyDisable(): Promise<void> {
|
|
525
|
-
await this.send("proxy.disable", {});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
async proxyEnable(): Promise<void> {
|
|
529
|
-
await this.send("proxy.enable", {});
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
async proxyCurrent(): Promise<{
|
|
533
|
-
host: string; port: number; type: string;
|
|
534
|
-
user?: string; alive: boolean; latencyMs?: number;
|
|
535
|
-
}> {
|
|
536
|
-
return this.send("proxy.current", {});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async proxyStats(): Promise<{
|
|
540
|
-
total: number; alive: number; dead: number;
|
|
541
|
-
index: number; checking: boolean;
|
|
542
|
-
}> {
|
|
543
|
-
return this.send("proxy.stats", {});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async proxyList(limit?: number): Promise<{
|
|
547
|
-
host: string; port: number; type: string;
|
|
548
|
-
alive: boolean; latencyMs?: number;
|
|
549
|
-
}[]> {
|
|
550
|
-
return this.send("proxy.list", limit !== undefined ? { limit } : {});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
async proxyRotation(mode: "none" | "timed" | "perrequest", interval?: number): Promise<void> {
|
|
554
|
-
await this.send("proxy.rotation", { mode, ...(interval !== undefined ? { interval } : {}) });
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
async proxyConfig(opts: { skipDead?: boolean; autoCheck?: boolean }): Promise<void> {
|
|
558
|
-
await this.send("proxy.config", opts as Record<string, any>);
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
async proxySave(path: string, filter: "alive" | "dead" | "all" = "all"): Promise<void> {
|
|
562
|
-
await this.send("proxy.save", { path, filter });
|
|
563
|
-
}
|
|
471
|
+
user?: string; pass?: string; proxy?: string;
|
|
472
|
+
}): Promise<void> { await this.send("proxy.set", opts as Record<string, any>); }
|
|
473
|
+
|
|
474
|
+
async proxyTest(): Promise<void> { await this.send("proxy.test", {}); }
|
|
475
|
+
async proxyTestStop(): Promise<void> { await this.send("proxy.test.stop", {}); }
|
|
476
|
+
async proxyNext(): Promise<void> { await this.send("proxy.next", {}); }
|
|
477
|
+
async proxyDisable(): Promise<void> { await this.send("proxy.disable", {}); }
|
|
478
|
+
async proxyEnable(): Promise<void> { await this.send("proxy.enable", {}); }
|
|
479
|
+
|
|
480
|
+
async proxyCurrent(): Promise<{ host: string; port: number; type: string; user?: string; alive: boolean; latencyMs?: number; }> { return this.send("proxy.current", {}); }
|
|
481
|
+
async proxyStats(): Promise<{ total: number; alive: number; dead: number; index: number; checking: boolean; }> { return this.send("proxy.stats", {}); }
|
|
482
|
+
async proxyList(limit?: number): Promise<{ host: string; port: number; type: string; alive: boolean; latencyMs?: number; }[]> { return this.send("proxy.list", limit !== undefined ? { limit } : {}); }
|
|
483
|
+
async proxyRotation(mode: "none" | "timed" | "perrequest", interval?: number): Promise<void> { await this.send("proxy.rotation", { mode, ...(interval !== undefined ? { interval } : {}) }); }
|
|
484
|
+
async proxyConfig(opts: { skipDead?: boolean; autoCheck?: boolean }): Promise<void> { await this.send("proxy.config", opts as Record<string, any>); }
|
|
485
|
+
async proxySave(path: string, filter: "alive" | "dead" | "all" = "all"): Promise<void> { await this.send("proxy.save", { path, filter }); }
|
|
564
486
|
|
|
565
487
|
onProxyEvent(event: string, handler: (data: any) => void): () => void {
|
|
566
488
|
return this.onEvent(event, "*", handler);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PiggyClient } from "../client";
|
|
2
|
+
import logger from "../logger";
|
|
3
|
+
|
|
4
|
+
export async function exposeFunction(
|
|
5
|
+
client: PiggyClient,
|
|
6
|
+
fnName: string,
|
|
7
|
+
handler: (data: any) => Promise<any> | any,
|
|
8
|
+
tabId: string
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
await client.exposeFunction(fnName, handler, tabId);
|
|
11
|
+
logger.success(`[${tabId}] exposed function: ${fnName}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function unexposeFunction(
|
|
15
|
+
client: PiggyClient,
|
|
16
|
+
fnName: string,
|
|
17
|
+
tabId: string
|
|
18
|
+
): Promise<void> {
|
|
19
|
+
await client.unexposeFunction(fnName, tabId);
|
|
20
|
+
logger.info(`[${tabId}] unexposed function: ${fnName}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function clearExposedFunctions(
|
|
24
|
+
client: PiggyClient,
|
|
25
|
+
tabId: string
|
|
26
|
+
): Promise<void> {
|
|
27
|
+
await client.clearExposedFunctions(tabId);
|
|
28
|
+
logger.info(`[${tabId}] cleared all exposed functions`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function exposeAndInject(
|
|
32
|
+
client: PiggyClient,
|
|
33
|
+
fnName: string,
|
|
34
|
+
handler: (data: any) => Promise<any> | any,
|
|
35
|
+
injectionJs: string | ((fnName: string) => string),
|
|
36
|
+
tabId: string
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
await client.exposeFunction(fnName, handler, tabId);
|
|
39
|
+
const js = typeof injectionJs === "function" ? injectionJs(fnName) : injectionJs;
|
|
40
|
+
await client.evaluate(js, tabId);
|
|
41
|
+
logger.success(`[${tabId}] exposed and injected: ${fnName}`);
|
|
42
|
+
}
|
package/piggy/register/index.ts
CHANGED
|
@@ -6,7 +6,14 @@ import { routeRegistry, keepAliveSites, type RouteHandler, type BeforeMiddleware
|
|
|
6
6
|
import { buildRespondScript, buildModifyResponseScript } from "../intercept/scripts";
|
|
7
7
|
import { storeRecord } from "../store";
|
|
8
8
|
import { TabPool } from "../pool";
|
|
9
|
-
|
|
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";
|
|
10
17
|
let globalClient: PiggyClient | null = null;
|
|
11
18
|
export let humanMode = false;
|
|
12
19
|
|
|
@@ -105,6 +112,18 @@ export function createSiteObject(
|
|
|
105
112
|
return client.waitForSelector(selector, timeout, t);
|
|
106
113
|
}),
|
|
107
114
|
|
|
115
|
+
exposeFunction: (fnName: string, handler: (data: any) => Promise<any> | any) =>
|
|
116
|
+
exposeFunction(client, fnName, handler, tabId).then(() => site),
|
|
117
|
+
|
|
118
|
+
unexposeFunction: (fnName: string) =>
|
|
119
|
+
unexposeFunction(client, fnName, tabId).then(() => site),
|
|
120
|
+
|
|
121
|
+
clearExposedFunctions: () =>
|
|
122
|
+
clearExposedFunctions(client, tabId).then(() => site),
|
|
123
|
+
|
|
124
|
+
exposeAndInject: (fnName: string, handler: (data: any) => Promise<any> | any, injectionJs: string | ((fnName: string) => string)) =>
|
|
125
|
+
exposeAndInject(client, fnName, handler, injectionJs, tabId).then(() => site),
|
|
126
|
+
|
|
108
127
|
waitForVisible: (selector: string, timeout = 30000) =>
|
|
109
128
|
withTab(t => client.waitForSelector(selector, timeout, t)),
|
|
110
129
|
|
|
@@ -257,6 +276,69 @@ export function createSiteObject(
|
|
|
257
276
|
css: (query: string) => withTab(t => client.searchCss(query, t)),
|
|
258
277
|
id: (query: string) => withTab(t => client.searchId(query, t)),
|
|
259
278
|
},
|
|
279
|
+
captcha: {
|
|
280
|
+
status: () => withTab(t => createCaptchaAPI(client).status(t)),
|
|
281
|
+
resolve: () => withTab(t => createCaptchaAPI(client).resolve(t)),
|
|
282
|
+
pause: () => withTab(t => createCaptchaAPI(client).pause(t)),
|
|
283
|
+
check: () => withTab(t => createCaptchaAPI(client).check(t)),
|
|
284
|
+
autoRetry: (opts: { enabled: boolean }) => withTab(t => createCaptchaAPI(client).setAutoRetry(opts.enabled)),
|
|
285
|
+
onCaptcha: (handler: any) => createCaptchaAPI(client).onCaptcha(tabId, handler),
|
|
286
|
+
onResolved:(handler: any) => createCaptchaAPI(client).onCaptchaResolved(tabId, handler),
|
|
287
|
+
},
|
|
288
|
+
block: {
|
|
289
|
+
status: () => withTab(t => createCaptchaAPI(client).blockStatus(t)),
|
|
290
|
+
retry: () => withTab(t => createCaptchaAPI(client).blockRetry(t)),
|
|
291
|
+
onBlocked: (handler: any) => createCaptchaAPI(client).onBlocked(tabId, handler),
|
|
292
|
+
onRetry: (handler: any) => createCaptchaAPI(client).onBlockRetry(tabId, handler),
|
|
293
|
+
},
|
|
294
|
+
find: {
|
|
295
|
+
css: (selector: string) => withTab(t => {
|
|
296
|
+
console.log("[DEBUG] find.css tabId:", t);
|
|
297
|
+
return createFindAPI(client).css(selector, t);
|
|
298
|
+
}),
|
|
299
|
+
all: (selector: string) => withTab(t => createFindAPI(client).all(selector, t)),
|
|
300
|
+
first: (selector: string) => withTab(t => createFindAPI(client).first(selector, t)),
|
|
301
|
+
byText: (text: string) => withTab(t => createFindAPI(client).byText(text, t)),
|
|
302
|
+
byAttr: (attr: string, value?: string) => withTab(t => createFindAPI(client).byAttr(attr, value, t)),
|
|
303
|
+
byTag: (tag: string) => withTab(t => createFindAPI(client).byTag(tag, t)),
|
|
304
|
+
byPlaceholder: (text: string) => withTab(t => createFindAPI(client).byPlaceholder(text, t)),
|
|
305
|
+
byRole: (role: string, name?: string) => withTab(t => createFindAPI(client).byRole(role, name, t)),
|
|
306
|
+
children: (selector: string) => withTab(t => createFindAPI(client).children(selector, t)),
|
|
307
|
+
parent: (selector: string) => withTab(t => createFindAPI(client).parent(selector, t)),
|
|
308
|
+
closest: (selector: string, ancestor: string) => withTab(t => createFindAPI(client).closest(selector, ancestor, t)),
|
|
309
|
+
count: (selector: string) => withTab(t => createFindAPI(client).count(selector, t)),
|
|
310
|
+
exists: (selector: string) => withTab(t => createFindAPI(client).exists(selector, t)),
|
|
311
|
+
visible: (selector: string) => withTab(t => createFindAPI(client).visible(selector, t)),
|
|
312
|
+
enabled: (selector: string) => withTab(t => createFindAPI(client).enabled(selector, t)),
|
|
313
|
+
checked: (selector: string) => withTab(t => createFindAPI(client).checked(selector, t)),
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
provide: {
|
|
317
|
+
text: (opts: any) => withTab(t => createProvideAPI(client).text(opts, t)),
|
|
318
|
+
textAll: (opts: any) => withTab(t => createProvideAPI(client).textAll(opts, t)),
|
|
319
|
+
attr: (opts: any) => withTab(t => createProvideAPI(client).attr(opts, t)),
|
|
320
|
+
attrAll: (opts: any) => withTab(t => createProvideAPI(client).attrAll(opts, t)),
|
|
321
|
+
html: (opts: any) => withTab(t => createProvideAPI(client).html(opts, t)),
|
|
322
|
+
table: (opts: any) => withTab(t => createProvideAPI(client).table(opts, t)),
|
|
323
|
+
list: (opts: any) => withTab(t => createProvideAPI(client).list(opts, t)),
|
|
324
|
+
links: (opts: any) => withTab(t => createProvideAPI(client).links(opts, t)),
|
|
325
|
+
images: (opts: any) => withTab(t => createProvideAPI(client).images(opts, t)),
|
|
326
|
+
form: (opts: any) => withTab(t => createProvideAPI(client).form(opts, t)),
|
|
327
|
+
page: () => withTab(t => createProvideAPI(client).page(t)),
|
|
328
|
+
div: (opts: any) => withTab(t => createProvideAPI(client).div(opts, t)),
|
|
329
|
+
meta: () => withTab(t => createProvideAPI(client).meta(t)),
|
|
330
|
+
select: (opts: any) => withTab(t => createProvideAPI(client).select(opts, t)),
|
|
331
|
+
json: (opts?: any) => withTab(t => createProvideAPI(client).json(opts, t)),
|
|
332
|
+
},
|
|
333
|
+
iframe: {
|
|
334
|
+
list: () => withTab(t => createIframeAPI(client).list(t)),
|
|
335
|
+
evaluate:(opts: any) => withTab(t => createIframeAPI(client).evaluate(opts, t)),
|
|
336
|
+
click: (opts: any) => withTab(t => createIframeAPI(client).click(opts, t)),
|
|
337
|
+
type: (opts: any) => withTab(t => createIframeAPI(client).type(opts, t)),
|
|
338
|
+
text: (opts: any) => withTab(t => createIframeAPI(client).text(opts, t)),
|
|
339
|
+
html: (opts: any) => withTab(t => createIframeAPI(client).html(opts, t)),
|
|
340
|
+
waitSel: (opts: any) => withTab(t => createIframeAPI(client).waitSel(opts, t)),
|
|
341
|
+
},
|
|
260
342
|
|
|
261
343
|
screenshot: async (filePath?: string) => {
|
|
262
344
|
const r = await withTab(t => client.screenshot(filePath, t));
|
|
@@ -269,6 +351,13 @@ export function createSiteObject(
|
|
|
269
351
|
logger.success(`[${name}] pdf → ${filePath ?? "base64"}`);
|
|
270
352
|
return r;
|
|
271
353
|
},
|
|
354
|
+
human: {
|
|
355
|
+
set: (opts: any) => withTab(t => createHumanAPI(client).set(opts, t)),
|
|
356
|
+
get: () => withTab(t => createHumanAPI(client).get(t)),
|
|
357
|
+
type: (opts: any) => withTab(t => createHumanAPI(client).type(opts, t)),
|
|
358
|
+
click: (opts: any) => withTab(t => createHumanAPI(client).click(opts, t)),
|
|
359
|
+
},
|
|
360
|
+
|
|
272
361
|
|
|
273
362
|
blockImages: () => withTab(async t => { await client.blockImages(t); logger.info(`[${name}] images blocked`); }),
|
|
274
363
|
unblockImages: () => withTab(async t => { await client.unblockImages(t); logger.info(`[${name}] images unblocked`); }),
|
|
@@ -278,14 +367,14 @@ export function createSiteObject(
|
|
|
278
367
|
await withTab(t => client.setCookie(cookieName, value, domain, path, t));
|
|
279
368
|
logger.info(`[${name}] cookie set: ${cookieName} @ ${domain}`);
|
|
280
369
|
},
|
|
281
|
-
get:
|
|
282
|
-
delete: async (cookieName: string) => {
|
|
283
|
-
|
|
370
|
+
get: (cookieName: string, domain = "") => withTab(t => client.getCookie(cookieName, domain, t)),
|
|
371
|
+
delete: async (cookieName: string, domain?: string) => {
|
|
372
|
+
const d = domain ?? new URL(registeredUrl).hostname;
|
|
373
|
+
await withTab(t => client.deleteCookie(cookieName, d, t));
|
|
284
374
|
logger.info(`[${name}] cookie deleted: ${cookieName}`);
|
|
285
375
|
},
|
|
286
|
-
list: () => withTab(t => client.listCookies(t)),
|
|
376
|
+
list: (domain = "") => withTab(t => client.listCookies(domain, t)),
|
|
287
377
|
},
|
|
288
|
-
|
|
289
378
|
intercept: {
|
|
290
379
|
block: async (pattern: string) => {
|
|
291
380
|
await withTab(t => client.addInterceptRule("block", pattern, {}, t));
|
|
@@ -385,6 +474,16 @@ export function createSiteObject(
|
|
|
385
474
|
logger.info(`[${name}] intercept rules cleared`);
|
|
386
475
|
},
|
|
387
476
|
},
|
|
477
|
+
dialog: {
|
|
478
|
+
accept: (tabId = "default", text?: string) => new DialogClient(client).accept(tabId, text),
|
|
479
|
+
dismiss: (tabId = "default") => new DialogClient(client).dismiss(tabId),
|
|
480
|
+
status: (tabId = "default") => new DialogClient(client).status(tabId),
|
|
481
|
+
setAutoAction: (tabId = "default", action: "accept" | "dismiss" | "") => new DialogClient(client).setAutoAction(tabId, action),
|
|
482
|
+
upload: (selector: string, filePath: string, tabId = "default") => new DialogClient(client).upload(selector, filePath, tabId),
|
|
483
|
+
onDialog: (tabId: string, handler: (data: any) => void) => new DialogClient(client).onDialog(tabId, handler),
|
|
484
|
+
waitAndAccept: (tabId = "default", text?: string, timeoutMs = 30000) => new DialogClient(client).waitAndAccept(tabId, text, timeoutMs),
|
|
485
|
+
waitAndDismiss:(tabId = "default", timeoutMs = 30000) => new DialogClient(client).waitAndDismiss(tabId, timeoutMs),
|
|
486
|
+
},
|
|
388
487
|
|
|
389
488
|
capture: {
|
|
390
489
|
start: () => withTab(async t => { await client.captureStart(t); logger.info(`[${name}] capture started`); }),
|
|
@@ -396,40 +495,16 @@ export function createSiteObject(
|
|
|
396
495
|
clear: () => withTab(async t => { await client.captureClear(t); logger.info(`[${name}] capture cleared`); }),
|
|
397
496
|
},
|
|
398
497
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
},
|
|
410
|
-
|
|
411
|
-
exposeFunction: async (fnName: string, handler: (data: any) => Promise<any> | any) => {
|
|
412
|
-
await client.exposeFunction(fnName, handler, tabId);
|
|
413
|
-
logger.success(`[${name}] exposed function: ${fnName}`);
|
|
414
|
-
return site;
|
|
415
|
-
},
|
|
416
|
-
unexposeFunction: async (fnName: string) => {
|
|
417
|
-
await client.unexposeFunction(fnName, tabId);
|
|
418
|
-
logger.info(`[${name}] unexposed function: ${fnName}`);
|
|
419
|
-
return site;
|
|
420
|
-
},
|
|
421
|
-
clearExposedFunctions: async () => {
|
|
422
|
-
await client.clearExposedFunctions(tabId);
|
|
423
|
-
logger.info(`[${name}] cleared all exposed functions`);
|
|
424
|
-
return site;
|
|
425
|
-
},
|
|
426
|
-
exposeAndInject: async (fnName: string, handler: (data: any) => Promise<any> | any, injectionJs: string | ((fnName: string) => string)) => {
|
|
427
|
-
await client.exposeFunction(fnName, handler, tabId);
|
|
428
|
-
const js = typeof injectionJs === "function" ? injectionJs(fnName) : injectionJs;
|
|
429
|
-
await withTab(t => client.evaluate(js, t));
|
|
430
|
-
logger.success(`[${name}] exposed and injected: ${fnName}`);
|
|
431
|
-
return site;
|
|
432
|
-
},
|
|
498
|
+
session: {
|
|
499
|
+
export: () => withTab(t => createSessionAPI(client).export(t)),
|
|
500
|
+
import: (data: any) => withTab(t => createSessionAPI(client).import(data, t)),
|
|
501
|
+
reload: () => withTab(t => createSessionAPI(client).reload(t)),
|
|
502
|
+
paths: () => createSessionAPI(client).paths(),
|
|
503
|
+
cookies: { path: () => createSessionAPI(client).cookiesPath() },
|
|
504
|
+
profile: { path: () => createSessionAPI(client).profilePath() },
|
|
505
|
+
ws: { save: (opts: any) => createSessionAPI(client).setWsSave(opts.enabled) },
|
|
506
|
+
pings: { save: (opts: any) => createSessionAPI(client).setPingsSave(opts.enabled) },
|
|
507
|
+
},
|
|
433
508
|
|
|
434
509
|
store: async (
|
|
435
510
|
data: Record<string, any> | Record<string, any>[],
|
package/piggy/tabs/index.ts
CHANGED
|
@@ -8,7 +8,8 @@ export class TabsClient {
|
|
|
8
8
|
return this.client.send("tab.new", {});
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
close(tabId: string): Promise<void> {
|
|
11
|
+
close(opts: string | { tabId: string }): Promise<void> {
|
|
12
|
+
const tabId = typeof opts === "string" ? opts : opts.tabId;
|
|
12
13
|
return this.client.send("tab.close", { tabId });
|
|
13
14
|
}
|
|
14
15
|
|
package/piggy.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// piggy.ts
|
|
1
|
+
// piggy.ts — patched: adds single:true + piggy.extend()
|
|
2
2
|
import { detectBinary, type BinaryMode } from "./piggy/launch/detect";
|
|
3
3
|
import { spawnBrowser, killBrowser, spawnBrowserOnSocket } from "./piggy/launch/spawn";
|
|
4
4
|
import { PiggyClient } from "./piggy/client";
|
|
@@ -26,12 +26,19 @@ import logger from "./piggy/logger";
|
|
|
26
26
|
|
|
27
27
|
type TabMode = "tab" | "process";
|
|
28
28
|
|
|
29
|
+
// A plugin installer is an async function that receives a site and enriches it.
|
|
30
|
+
type PluginInstaller = (site: SiteObject) => Promise<any>;
|
|
31
|
+
|
|
29
32
|
let _client: PiggyClient | null = null;
|
|
30
33
|
let _router: PiggyRouter | null = null;
|
|
31
34
|
let _tabMode: TabMode = "tab";
|
|
32
35
|
const _extraProcs: { socket: string; client: PiggyClient }[] = [];
|
|
33
36
|
const _sites: Record<string, SiteObject> = {};
|
|
34
37
|
|
|
38
|
+
// ── Single-site tracking for extend() ────────────────────────────────────────
|
|
39
|
+
// Only one site may be registered with { single: true } at a time.
|
|
40
|
+
let _singleSiteName: string | null = null;
|
|
41
|
+
|
|
35
42
|
// ── Internal guard ────────────────────────────────────────────────────────────
|
|
36
43
|
|
|
37
44
|
function guardClient(): PiggyClient {
|
|
@@ -73,37 +80,57 @@ const piggy: any = {
|
|
|
73
80
|
},
|
|
74
81
|
|
|
75
82
|
// ── HTTP client (port 2005 direct) ────────────────────────────────────────
|
|
76
|
-
// Use when you want to talk to the browser over HTTP without a socket client.
|
|
77
83
|
http: (opts: HttpClientOptions) => createHttpClient(opts),
|
|
78
84
|
|
|
79
85
|
// ── Register ──────────────────────────────────────────────────────────────
|
|
80
86
|
register: async (
|
|
81
87
|
name: string,
|
|
82
88
|
url: string,
|
|
83
|
-
opts?: { binary?: BinaryMode; pool?: number }
|
|
89
|
+
opts?: { binary?: BinaryMode; pool?: number; single?: boolean }
|
|
84
90
|
) => {
|
|
85
91
|
if (!url?.trim()) throw new Error(`No URL for site "${name}"`);
|
|
92
|
+
|
|
86
93
|
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
87
|
-
const poolSize
|
|
94
|
+
const poolSize = opts?.pool ?? 0;
|
|
95
|
+
const isSingle = opts?.single === true;
|
|
96
|
+
|
|
97
|
+
// ── single: true enforcement ───────────────────────────────────────────
|
|
98
|
+
// A single-site registration uses the default tab and blocks tab.new so
|
|
99
|
+
// the binary stays strictly single-tab. Only one site may be single.
|
|
100
|
+
if (isSingle && _singleSiteName && _singleSiteName !== name) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`piggy: site "${_singleSiteName}" is already registered as single. ` +
|
|
103
|
+
`Only one site may use { single: true } at a time.`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
88
106
|
|
|
89
107
|
if (_tabMode === "tab") {
|
|
90
108
|
const client = guardClient();
|
|
91
109
|
|
|
92
110
|
if (poolSize > 1) {
|
|
111
|
+
if (isSingle) throw new Error('piggy: { single: true } is incompatible with pool > 1');
|
|
93
112
|
const pool = new TabPool(client, poolSize, url, name);
|
|
94
113
|
await pool.init();
|
|
95
114
|
const siteObj = createSiteObject(name, url, client, "default", pool);
|
|
96
115
|
_sites[name] = siteObj;
|
|
97
|
-
piggy[name]
|
|
116
|
+
piggy[name] = siteObj;
|
|
98
117
|
logger.success(`[${name}] registered with pool of ${poolSize} tabs`);
|
|
99
118
|
} else {
|
|
100
|
-
|
|
119
|
+
// single: true → reuse the default tab, never call newTab()
|
|
120
|
+
const tabId = isSingle ? "default" : await client.newTab();
|
|
101
121
|
const siteObj = createSiteObject(name, url, client, tabId);
|
|
102
122
|
_sites[name] = siteObj;
|
|
103
|
-
piggy[name]
|
|
104
|
-
|
|
123
|
+
piggy[name] = siteObj;
|
|
124
|
+
|
|
125
|
+
if (isSingle) {
|
|
126
|
+
_singleSiteName = name;
|
|
127
|
+
logger.success(`[${name}] registered as single-tab site (default tab)`);
|
|
128
|
+
} else {
|
|
129
|
+
logger.success(`[${name}] registered as tab ${tabId}`);
|
|
130
|
+
}
|
|
105
131
|
}
|
|
106
132
|
} else {
|
|
133
|
+
if (isSingle) throw new Error('piggy: { single: true } is only supported in tab mode');
|
|
107
134
|
const socketName = `piggy_${name}`;
|
|
108
135
|
await spawnBrowserOnSocket(socketName, binaryMode);
|
|
109
136
|
await new Promise(r => setTimeout(r, 500));
|
|
@@ -112,16 +139,56 @@ const piggy: any = {
|
|
|
112
139
|
_extraProcs.push({ socket: socketName, client: c });
|
|
113
140
|
const siteObj = createSiteObject(name, url, c, "default");
|
|
114
141
|
_sites[name] = siteObj;
|
|
115
|
-
piggy[name]
|
|
142
|
+
piggy[name] = siteObj;
|
|
116
143
|
logger.success(`[${name}] registered as process on "${socketName}"`);
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
return piggy;
|
|
120
147
|
},
|
|
121
148
|
|
|
149
|
+
// ── extend() — installs plugins onto the single-flagged site ─────────────
|
|
150
|
+
//
|
|
151
|
+
// Each installer is an async function returned by a plugin factory, e.g.:
|
|
152
|
+
// innerstorage({ path: './wa-storage.json' }) → installer fn
|
|
153
|
+
//
|
|
154
|
+
// Usage:
|
|
155
|
+
// await piggy.extend(
|
|
156
|
+
// innerstorage({ path: './wa-storage.json' }),
|
|
157
|
+
// cookiesinject({ cookieFile: './wa-cookies.json' }),
|
|
158
|
+
// mediacapture({ downloadDir: './wa-media/' })
|
|
159
|
+
// );
|
|
160
|
+
extend: async (...installers: PluginInstaller[]) => {
|
|
161
|
+
if (!_singleSiteName) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
'piggy.extend() requires a site registered with { single: true }.\n' +
|
|
164
|
+
'Example: await piggy.register("mysite", url, { single: true })'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (installers.length === 0) {
|
|
168
|
+
logger.warn('[piggy] extend() called with no plugins — nothing to do');
|
|
169
|
+
return piggy;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const site = _sites[_singleSiteName];
|
|
173
|
+
if (!site) {
|
|
174
|
+
throw new Error(`piggy.extend(): site "${_singleSiteName}" not found — register it first`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const installer of installers) {
|
|
178
|
+
if (typeof installer !== 'function') {
|
|
179
|
+
throw new Error('piggy.extend(): each argument must be a plugin installer function');
|
|
180
|
+
}
|
|
181
|
+
await installer(site);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
logger.success(`[piggy] ${installers.length} plugin(s) installed on "${_singleSiteName}"`);
|
|
185
|
+
return piggy;
|
|
186
|
+
},
|
|
187
|
+
|
|
122
188
|
// ── Sub-APIs (1:1 with C++ files, available after launch/connect) ─────────
|
|
123
189
|
|
|
124
190
|
get tabs() { return _router?.tabs ?? createTabsAPI(guardClient()); },
|
|
191
|
+
get tab() { return _router?.tabs ?? createTabsAPI(guardClient()); },
|
|
125
192
|
get navigation() { return _router?.navigation ?? createNavigationAPI(guardClient()); },
|
|
126
193
|
get interactions() { return _router?.interactions ?? createInteractionsAPI(guardClient()); },
|
|
127
194
|
get media() { return _router?.media ?? createMediaAPI(guardClient()); },
|
|
@@ -144,7 +211,7 @@ const piggy: any = {
|
|
|
144
211
|
return {
|
|
145
212
|
load: (path: string) => api.load(path),
|
|
146
213
|
fetch: (url: string) => api.fetch(url),
|
|
147
|
-
ovpn: (path: string)
|
|
214
|
+
ovpn: (path: string) => api.ovpn(path),
|
|
148
215
|
set: (opts: Parameters<typeof api.set>[0]) => api.set(opts),
|
|
149
216
|
test: () => api.test(),
|
|
150
217
|
testStop: () => api.testStop(),
|
|
@@ -158,7 +225,7 @@ const piggy: any = {
|
|
|
158
225
|
rotation: (mode: "none" | "timed" | "perrequest", interval?: number) => api.rotation(mode, interval),
|
|
159
226
|
config: (opts: { skipDead?: boolean; autoCheck?: boolean }) => api.config(opts),
|
|
160
227
|
save: (path: string, filter?: "alive" | "dead" | "all") => api.save(path, filter),
|
|
161
|
-
on: (event: string, handler: (data: any) => void)
|
|
228
|
+
on: (event: string, handler: (data: any) => void) => guardClient().onProxyEvent(event, handler),
|
|
162
229
|
};
|
|
163
230
|
},
|
|
164
231
|
|
|
@@ -230,6 +297,7 @@ const piggy: any = {
|
|
|
230
297
|
// ── Shutdown ──────────────────────────────────────────────────────────────
|
|
231
298
|
close: async (opts?: { force?: boolean }) => {
|
|
232
299
|
stopServer();
|
|
300
|
+
_singleSiteName = null;
|
|
233
301
|
if (opts?.force) {
|
|
234
302
|
for (const { client: c } of _extraProcs) c.disconnect();
|
|
235
303
|
_client?.disconnect();
|
package/piggy/cache/memory.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare function get(key: string): any | null;
|
|
2
|
-
export declare function set(key: string, data: any, ttlMs: number): void;
|
|
3
|
-
export declare function del(key: string): void;
|
|
4
|
-
export declare function clear(): void;
|
|
5
|
-
export declare function size(): number;
|
|
6
|
-
export declare function keys(): string[];
|
|
7
|
-
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["memory.ts"],"names":[],"mappings":"AASA,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,GAAG,IAAI,CAQ3C;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,QAExD;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,QAE9B;AAED,wBAAgB,KAAK,SAEpB;AAED,wBAAgB,IAAI,WAEnB;AAED,wBAAgB,IAAI,aAEnB"}
|