nothing-browser 0.0.17 → 0.0.19
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 +224 -40
- package/dist/launch/detect.js +8 -0
- package/dist/launch/spawn.js +8 -0
- package/dist/piggy/client/index.d.ts +77 -2
- package/dist/piggy/client/index.d.ts.map +1 -1
- package/dist/piggy/launch/detect.d.ts +1 -1
- package/dist/piggy/launch/detect.d.ts.map +1 -1
- package/dist/piggy.d.ts +1 -0
- package/dist/piggy.d.ts.map +1 -1
- package/dist/piggy.js +329 -48
- package/package.json +1 -1
- package/piggy/client/index.ts +325 -54
- package/piggy/launch/detect.ts +13 -4
- package/piggy.ts +46 -22
package/piggy/client/index.ts
CHANGED
|
@@ -5,78 +5,221 @@ import { dirname } from "path";
|
|
|
5
5
|
import { platform } from "os";
|
|
6
6
|
import logger from "../logger";
|
|
7
7
|
|
|
8
|
-
const
|
|
9
|
-
?
|
|
10
|
-
:
|
|
8
|
+
const DEFAULT_SOCKET_PATH = platform() === "win32"
|
|
9
|
+
? "\\\\.\\pipe\\piggy"
|
|
10
|
+
: "/tmp/piggy";
|
|
11
|
+
|
|
12
|
+
// ── Transport interface ────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
interface Transport {
|
|
15
|
+
send(data: string): void;
|
|
16
|
+
on(event: "data", handler: (chunk: string) => void): void;
|
|
17
|
+
on(event: "error", handler: (e: Error) => void): void;
|
|
18
|
+
on(event: "close", handler: () => void): void;
|
|
19
|
+
destroy(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ── Socket transport ───────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
class SocketTransport implements Transport {
|
|
25
|
+
private sock: Socket;
|
|
26
|
+
|
|
27
|
+
constructor(sock: Socket) {
|
|
28
|
+
this.sock = sock;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
send(data: string) {
|
|
32
|
+
this.sock.write(data);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
on(event: string, handler: any) {
|
|
36
|
+
this.sock.on(event, handler);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
destroy() {
|
|
40
|
+
this.sock.destroy();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── HTTP transport ─────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
class HttpTransport implements Transport {
|
|
47
|
+
private host: string;
|
|
48
|
+
private key: string;
|
|
49
|
+
private dataHandlers: ((chunk: string) => void)[] = [];
|
|
50
|
+
private errorHandlers: ((e: Error) => void)[] = [];
|
|
51
|
+
private closeHandlers: (() => void)[] = [];
|
|
52
|
+
private _destroyed = false;
|
|
53
|
+
|
|
54
|
+
constructor(host: string, key: string) {
|
|
55
|
+
this.host = host.replace(/\/$/, "");
|
|
56
|
+
this.key = key;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
on(event: string, handler: any) {
|
|
60
|
+
if (event === "data") this.dataHandlers.push(handler);
|
|
61
|
+
if (event === "error") this.errorHandlers.push(handler);
|
|
62
|
+
if (event === "close") this.closeHandlers.push(handler);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
send(data: string) {
|
|
66
|
+
if (this._destroyed) return;
|
|
67
|
+
|
|
68
|
+
fetch(this.host, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: {
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
"X-Piggy-Key": this.key,
|
|
73
|
+
},
|
|
74
|
+
body: data,
|
|
75
|
+
})
|
|
76
|
+
.then(async (res) => {
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const text = await res.text().catch(() => `HTTP ${res.status}`);
|
|
79
|
+
this.errorHandlers.forEach(h =>
|
|
80
|
+
h(new Error(`HTTP ${res.status}: ${text}`))
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const text = await res.text();
|
|
85
|
+
const lines = text.split("\n").filter(l => l.trim());
|
|
86
|
+
for (const line of lines) {
|
|
87
|
+
this.dataHandlers.forEach(h => h(line + "\n"));
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
.catch((e: Error) => {
|
|
91
|
+
if (!this._destroyed) {
|
|
92
|
+
this.errorHandlers.forEach(h => h(e));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
destroy() {
|
|
98
|
+
this._destroyed = true;
|
|
99
|
+
this.closeHandlers.forEach(h => h());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── PiggyClient ────────────────────────────────────────────────────────────────
|
|
11
104
|
|
|
12
105
|
export class PiggyClient {
|
|
13
106
|
private socketPath: string;
|
|
14
|
-
private
|
|
107
|
+
private httpHost: string | null = null;
|
|
108
|
+
private httpKey: string | null = null;
|
|
109
|
+
private transport: Transport | null = null;
|
|
15
110
|
private reqId = 0;
|
|
16
111
|
private pending = new Map<string, { resolve: (v: any) => void; reject: (e: Error) => void }>();
|
|
17
112
|
private buf = "";
|
|
18
|
-
private eventBuffer = "";
|
|
19
113
|
private eventHandlers = new Map<string, Map<string, (data: any) => Promise<any>>>();
|
|
20
114
|
private globalEventHandlers = new Map<string, Set<(data: any) => void>>();
|
|
21
115
|
|
|
22
|
-
constructor(socketPath
|
|
23
|
-
|
|
116
|
+
constructor(socketPath?: string);
|
|
117
|
+
constructor(opts: { host: string; key: string });
|
|
118
|
+
|
|
119
|
+
constructor(arg?: string | { host: string; key: string }) {
|
|
120
|
+
if (arg && typeof arg === "object") {
|
|
121
|
+
this.socketPath = "";
|
|
122
|
+
this.httpHost = arg.host.replace(/\/$/, "");
|
|
123
|
+
this.httpKey = arg.key;
|
|
124
|
+
} else {
|
|
125
|
+
this.socketPath = (arg as string | undefined) ?? DEFAULT_SOCKET_PATH;
|
|
126
|
+
}
|
|
24
127
|
this.eventHandlers.set("default", new Map());
|
|
25
128
|
}
|
|
26
129
|
|
|
130
|
+
// ── Connect ───────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
27
132
|
connect(): Promise<void> {
|
|
133
|
+
if (this.httpHost) return this._connectHttp();
|
|
134
|
+
return this._connectSocket();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private _connectSocket(): Promise<void> {
|
|
28
138
|
return new Promise((resolve, reject) => {
|
|
29
139
|
logger.info(`Connecting to socket: ${this.socketPath}`);
|
|
30
140
|
const sock = connect(this.socketPath);
|
|
31
141
|
sock.setEncoding("utf8");
|
|
32
142
|
|
|
33
143
|
sock.on("connect", () => {
|
|
34
|
-
this.
|
|
35
|
-
|
|
144
|
+
this.transport = new SocketTransport(sock);
|
|
145
|
+
this._wireTransport();
|
|
146
|
+
logger.success("Connected to Piggy server (socket)");
|
|
36
147
|
resolve();
|
|
37
148
|
});
|
|
38
149
|
|
|
39
|
-
sock.on("data", (chunk: string) => {
|
|
40
|
-
this.eventBuffer += chunk;
|
|
41
|
-
const lines = this.eventBuffer.split("\n");
|
|
42
|
-
this.eventBuffer = lines.pop()!;
|
|
43
|
-
|
|
44
|
-
for (const line of lines) {
|
|
45
|
-
if (!line.trim()) continue;
|
|
46
|
-
try {
|
|
47
|
-
const msg = JSON.parse(line);
|
|
48
|
-
|
|
49
|
-
if (msg.type === "event") {
|
|
50
|
-
this.handleEvent(msg);
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const p = this.pending.get(msg.id);
|
|
55
|
-
if (p) {
|
|
56
|
-
this.pending.delete(msg.id);
|
|
57
|
-
msg.ok ? p.resolve(msg.data) : p.reject(new Error(msg.data ?? "command failed"));
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
logger.error(`Bad JSON from server: ${line}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
150
|
sock.on("error", (e) => {
|
|
66
151
|
for (const p of this.pending.values()) p.reject(e);
|
|
67
152
|
this.pending.clear();
|
|
68
153
|
reject(e);
|
|
69
154
|
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
70
157
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
158
|
+
private async _connectHttp(): Promise<void> {
|
|
159
|
+
logger.info(`Connecting to Piggy server (HTTP): ${this.httpHost}`);
|
|
160
|
+
try {
|
|
161
|
+
const res = await fetch(this.httpHost!, {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
"X-Piggy-Key": this.httpKey!,
|
|
166
|
+
},
|
|
167
|
+
body: "hello",
|
|
74
168
|
});
|
|
169
|
+
if (res.status === 401) {
|
|
170
|
+
throw new Error("Unauthorized — invalid X-Piggy-Key");
|
|
171
|
+
}
|
|
172
|
+
this.transport = new HttpTransport(this.httpHost!, this.httpKey!);
|
|
173
|
+
this._wireTransport();
|
|
174
|
+
logger.success(`Connected to Piggy server (HTTP): ${this.httpHost}`);
|
|
175
|
+
} catch (e: any) {
|
|
176
|
+
throw new Error(`Failed to connect to Piggy HTTP server: ${e.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private _wireTransport() {
|
|
181
|
+
if (!this.transport) return;
|
|
182
|
+
|
|
183
|
+
this.transport.on("data", (chunk: string) => {
|
|
184
|
+
this.buf += chunk;
|
|
185
|
+
const lines = this.buf.split("\n");
|
|
186
|
+
this.buf = lines.pop()!;
|
|
187
|
+
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
if (!line.trim()) continue;
|
|
190
|
+
try {
|
|
191
|
+
const msg = JSON.parse(line);
|
|
192
|
+
|
|
193
|
+
if (msg.type === "event") {
|
|
194
|
+
this.handleEvent(msg);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const p = this.pending.get(msg.id);
|
|
199
|
+
if (p) {
|
|
200
|
+
this.pending.delete(msg.id);
|
|
201
|
+
msg.ok ? p.resolve(msg.data) : p.reject(new Error(msg.data ?? "command failed"));
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
logger.error(`Bad JSON from server: ${line}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
this.transport.on("error", (e: Error) => {
|
|
210
|
+
for (const p of this.pending.values()) p.reject(e);
|
|
211
|
+
this.pending.clear();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
this.transport.on("close", () => {
|
|
215
|
+
for (const p of this.pending.values()) p.reject(new Error("Connection closed"));
|
|
216
|
+
this.pending.clear();
|
|
75
217
|
});
|
|
76
218
|
}
|
|
77
219
|
|
|
220
|
+
// ── Event handling ────────────────────────────────────────────────────────
|
|
221
|
+
|
|
78
222
|
private handleEvent(event: any) {
|
|
79
|
-
// ── exposed_call ──────────────────────────────────────────────────────────
|
|
80
223
|
if (event.event === "exposed_call") {
|
|
81
224
|
const { tabId, name, callId, data } = event;
|
|
82
225
|
const effectiveTabId = tabId || "default";
|
|
@@ -84,12 +227,11 @@ export class PiggyClient {
|
|
|
84
227
|
const handler = handlers?.get(name);
|
|
85
228
|
|
|
86
229
|
if (handler) {
|
|
87
|
-
|
|
88
|
-
let parsedData;
|
|
230
|
+
let parsedData: any;
|
|
89
231
|
try {
|
|
90
232
|
parsedData = JSON.parse(data || "null");
|
|
91
233
|
} catch {
|
|
92
|
-
parsedData = data;
|
|
234
|
+
parsedData = data;
|
|
93
235
|
}
|
|
94
236
|
|
|
95
237
|
Promise.resolve(handler(parsedData))
|
|
@@ -99,14 +241,14 @@ export class PiggyClient {
|
|
|
99
241
|
tabId: effectiveTabId,
|
|
100
242
|
callId,
|
|
101
243
|
result: response.success ? JSON.stringify(response.result) : (response.error || "Unknown error"),
|
|
102
|
-
isError: !response.success
|
|
244
|
+
isError: !response.success,
|
|
103
245
|
}).catch(e => logger.error(`Failed to send exposed result: ${e}`));
|
|
104
246
|
} else {
|
|
105
247
|
this.send("exposed.result", {
|
|
106
248
|
tabId: effectiveTabId,
|
|
107
249
|
callId,
|
|
108
250
|
result: JSON.stringify(response),
|
|
109
|
-
isError: false
|
|
251
|
+
isError: false,
|
|
110
252
|
}).catch(e => logger.error(`Failed to send exposed result: ${e}`));
|
|
111
253
|
}
|
|
112
254
|
})
|
|
@@ -115,7 +257,7 @@ export class PiggyClient {
|
|
|
115
257
|
tabId: effectiveTabId,
|
|
116
258
|
callId,
|
|
117
259
|
result: err.message || "Handler error",
|
|
118
|
-
isError: true
|
|
260
|
+
isError: true,
|
|
119
261
|
}).catch(e => logger.error(`Failed to send exposed error: ${e}`));
|
|
120
262
|
});
|
|
121
263
|
} else {
|
|
@@ -124,7 +266,6 @@ export class PiggyClient {
|
|
|
124
266
|
return;
|
|
125
267
|
}
|
|
126
268
|
|
|
127
|
-
// ── navigate ──────────────────────────────────────────────────────────────
|
|
128
269
|
if (event.event === "navigate") {
|
|
129
270
|
const handlers = this.globalEventHandlers.get(`navigate:${event.tabId}`);
|
|
130
271
|
if (handlers) {
|
|
@@ -132,39 +273,37 @@ export class PiggyClient {
|
|
|
132
273
|
try { h(event.url); } catch (e) { logger.error(`navigate handler error: ${e}`); }
|
|
133
274
|
}
|
|
134
275
|
}
|
|
135
|
-
// Also fire wildcard listeners (no tabId filter)
|
|
136
276
|
const wildcard = this.globalEventHandlers.get("navigate:*");
|
|
137
277
|
if (wildcard) {
|
|
138
278
|
for (const h of wildcard) {
|
|
139
279
|
try { h({ url: event.url, tabId: event.tabId }); } catch {}
|
|
140
280
|
}
|
|
141
281
|
}
|
|
142
|
-
return;
|
|
143
282
|
}
|
|
144
283
|
}
|
|
145
284
|
|
|
146
|
-
// ── Global event subscription ─────────────────────────────────────────────
|
|
147
285
|
onEvent(eventName: string, tabId: string, handler: (data: any) => void): () => void {
|
|
148
286
|
const key = `${eventName}:${tabId}`;
|
|
149
287
|
if (!this.globalEventHandlers.has(key)) {
|
|
150
288
|
this.globalEventHandlers.set(key, new Set());
|
|
151
289
|
}
|
|
152
290
|
this.globalEventHandlers.get(key)!.add(handler);
|
|
153
|
-
// Return unsubscribe fn
|
|
154
291
|
return () => this.globalEventHandlers.get(key)?.delete(handler);
|
|
155
292
|
}
|
|
156
293
|
|
|
157
294
|
disconnect() {
|
|
158
|
-
this.
|
|
159
|
-
this.
|
|
295
|
+
this.transport?.destroy();
|
|
296
|
+
this.transport = null;
|
|
160
297
|
}
|
|
161
298
|
|
|
299
|
+
// ── Core send ─────────────────────────────────────────────────────────────
|
|
300
|
+
|
|
162
301
|
send<T = any>(cmd: string, payload: Record<string, any> = {}): Promise<T> {
|
|
163
302
|
return new Promise((resolve, reject) => {
|
|
164
|
-
if (!this.
|
|
303
|
+
if (!this.transport) return reject(new Error("Not connected"));
|
|
165
304
|
const id = String(++this.reqId);
|
|
166
305
|
this.pending.set(id, { resolve, reject });
|
|
167
|
-
this.
|
|
306
|
+
this.transport.send(JSON.stringify({ id, cmd, payload }) + "\n");
|
|
168
307
|
});
|
|
169
308
|
}
|
|
170
309
|
|
|
@@ -268,6 +407,56 @@ export class PiggyClient {
|
|
|
268
407
|
async sessionExport(tabId = "default"): Promise<any> { return this.send("session.export", { tabId }); }
|
|
269
408
|
async sessionImport(data: any, tabId = "default"): Promise<void> { await this.send("session.import", { data, tabId }); }
|
|
270
409
|
|
|
410
|
+
// ── Session persistence (opt-in) ──────────────────────────────────────────
|
|
411
|
+
// WS frames and pings are NOT saved by default — you must opt in.
|
|
412
|
+
// Files are written to cwd (same folder as cookies.json / profile.json).
|
|
413
|
+
|
|
414
|
+
/** Enable or disable saving WebSocket frames to ws.json in cwd */
|
|
415
|
+
async sessionWsSave(enabled = true): Promise<void> {
|
|
416
|
+
await this.send("session.ws.save", { enabled });
|
|
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
|
+
}
|
|
459
|
+
|
|
271
460
|
// ── Expose Function ───────────────────────────────────────────────────────
|
|
272
461
|
async exposeFunction(name: string, handler: (data: any) => Promise<any> | any, tabId = "default"): Promise<void> {
|
|
273
462
|
if (!this.eventHandlers.has(tabId)) this.eventHandlers.set(tabId, new Map());
|
|
@@ -294,4 +483,86 @@ export class PiggyClient {
|
|
|
294
483
|
this.eventHandlers.set(tabId, new Map());
|
|
295
484
|
logger.info(`[${tabId}] cleared all exposed functions`);
|
|
296
485
|
}
|
|
486
|
+
|
|
487
|
+
// ── Proxy ─────────────────────────────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
async proxyLoad(path: string): Promise<void> {
|
|
490
|
+
await this.send("proxy.load", { path });
|
|
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
|
+
}
|
|
500
|
+
|
|
501
|
+
async proxySet(opts: {
|
|
502
|
+
host?: string;
|
|
503
|
+
port?: number;
|
|
504
|
+
type?: "http" | "https" | "socks5" | "socks4";
|
|
505
|
+
user?: string;
|
|
506
|
+
pass?: string;
|
|
507
|
+
proxy?: string;
|
|
508
|
+
}): Promise<void> {
|
|
509
|
+
await this.send("proxy.set", opts as Record<string, any>);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async proxyTest(): Promise<void> {
|
|
513
|
+
await this.send("proxy.test", {});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async proxyTestStop(): Promise<void> {
|
|
517
|
+
await this.send("proxy.test.stop", {});
|
|
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
|
+
}
|
|
564
|
+
|
|
565
|
+
onProxyEvent(event: string, handler: (data: any) => void): () => void {
|
|
566
|
+
return this.onEvent(event, "*", handler);
|
|
567
|
+
}
|
|
297
568
|
}
|
package/piggy/launch/detect.ts
CHANGED
|
@@ -2,16 +2,26 @@ import { existsSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import logger from '../logger';
|
|
4
4
|
|
|
5
|
-
export type BinaryMode = 'headless' | 'headful';
|
|
5
|
+
export type BinaryMode = 'headless' | 'headful' | (string & {});
|
|
6
6
|
|
|
7
|
-
const BINARY_NAMES: Record<
|
|
7
|
+
const BINARY_NAMES: Record<'headless' | 'headful', string> = {
|
|
8
8
|
headless: 'nothing-browser-headless',
|
|
9
9
|
headful: 'nothing-browser-headful',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export function detectBinary(mode: BinaryMode = 'headless'): string | null {
|
|
13
|
+
// If it's an arbitrary path, just verify it exists and return it as-is
|
|
14
|
+
if (mode !== 'headless' && mode !== 'headful') {
|
|
15
|
+
if (existsSync(mode)) {
|
|
16
|
+
logger.success(`Binary found (custom path): ${mode}`);
|
|
17
|
+
return mode;
|
|
18
|
+
}
|
|
19
|
+
logger.error(`❌ Binary not found at custom path: ${mode}`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
const cwd = process.cwd();
|
|
14
|
-
|
|
24
|
+
const name = BINARY_NAMES[mode as 'headless' | 'headful'];
|
|
15
25
|
|
|
16
26
|
// Windows
|
|
17
27
|
if (process.platform === 'win32') {
|
|
@@ -38,6 +48,5 @@ export function detectBinary(mode: BinaryMode = 'headless'): string | null {
|
|
|
38
48
|
if (process.platform !== 'win32') {
|
|
39
49
|
logger.error(`Then run: chmod +x ${name}`);
|
|
40
50
|
}
|
|
41
|
-
|
|
42
51
|
return null;
|
|
43
52
|
}
|