nothing-browser 0.0.1 → 0.0.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/README.md +69 -32
- package/package.json +5 -5
- package/piggy/launch/detect.ts +37 -27
- package/piggy/launch/spawn.ts +73 -75
- package/piggy.ts +13 -12
package/README.md
CHANGED
|
@@ -20,7 +20,21 @@ A scraper-first headless browser library powered by the Nothing Browser Qt6/Chro
|
|
|
20
20
|
## Requirements
|
|
21
21
|
|
|
22
22
|
- [Bun](https://bun.sh) ≥ 1.0
|
|
23
|
-
- Nothing Browser
|
|
23
|
+
- A Nothing Browser binary placed in your **project root** (see below)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Binaries
|
|
28
|
+
|
|
29
|
+
There are three binaries. All are downloaded from the same place — [GitHub Releases](https://github.com/BunElysiaReact/nothing-browser/releases).
|
|
30
|
+
|
|
31
|
+
| Binary | What it is | Where it goes |
|
|
32
|
+
|--------|-----------|---------------|
|
|
33
|
+
| `nothing-browser` | Full UI browser app — DevTools, YouTube tab, Plugins, etc. | Install system-wide |
|
|
34
|
+
| `nothing-browser-headless` | No window, no GPU. Runs as a background daemon for the scraping lib. | **Your project root** |
|
|
35
|
+
| `nothing-browser-headful` | Visible browser window, script-controlled. Useful when a site needs a real display. | **Your project root** |
|
|
36
|
+
|
|
37
|
+
The `nothingbrowser` npm/Bun lib talks to whichever binary is in your project root over a local socket. You pick headless or headful depending on your use case.
|
|
24
38
|
|
|
25
39
|
---
|
|
26
40
|
|
|
@@ -30,17 +44,38 @@ A scraper-first headless browser library powered by the Nothing Browser Qt6/Chro
|
|
|
30
44
|
bun add nothing-browser
|
|
31
45
|
```
|
|
32
46
|
|
|
33
|
-
Then download the
|
|
47
|
+
Then download the binary for your platform from [GitHub Releases](https://github.com/BunElysiaReact/nothing-browser/releases) and place it in your project root.
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
### Linux
|
|
50
|
+
|
|
51
|
+
**Headless** (no visible window — most common for scraping)
|
|
36
52
|
```bash
|
|
37
53
|
tar -xzf nothing-browser-headless-*-linux-x86_64.tar.gz
|
|
38
54
|
chmod +x nothing-browser-headless
|
|
39
55
|
```
|
|
40
56
|
|
|
41
|
-
**
|
|
57
|
+
**Headful** (visible window, script-controlled)
|
|
58
|
+
```bash
|
|
59
|
+
tar -xzf nothing-browser-headful-*-linux-x86_64.tar.gz
|
|
60
|
+
chmod +x nothing-browser-headful
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Full browser** (system-wide install, for using the UI)
|
|
64
|
+
```bash
|
|
65
|
+
sudo dpkg -i nothing-browser_*_amd64.deb
|
|
66
|
+
# or
|
|
67
|
+
tar -xzf nothing-browser-*-linux-x86_64.tar.gz
|
|
68
|
+
cd nothing-browser-*-linux-x86_64
|
|
69
|
+
./nothing-browser
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Windows
|
|
73
|
+
|
|
74
|
+
Download the `.zip` for your chosen binary → extract → place `nothing-browser-headless.exe` or `nothing-browser-headful.exe` in your project root. The JRE is bundled in the full browser zip.
|
|
42
75
|
|
|
43
|
-
|
|
76
|
+
### macOS
|
|
77
|
+
|
|
78
|
+
Download the `.tar.gz` for your chosen binary → extract → place the binary in your project root.
|
|
44
79
|
|
|
45
80
|
---
|
|
46
81
|
|
|
@@ -86,6 +121,24 @@ await piggy.launch({ mode: "process" });
|
|
|
86
121
|
|
|
87
122
|
---
|
|
88
123
|
|
|
124
|
+
## Headless vs Headful
|
|
125
|
+
|
|
126
|
+
**Headless** — no display needed, runs anywhere including CI. Use this by default.
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
nothing-browser-headless ← in your project root
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Headful** — opens a real visible Chromium window that your script drives. Use this when a site detects headless mode or requires a real display (canvas fingerprinting, certain login flows, etc).
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
nothing-browser-headful ← in your project root
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Both binaries expose the exact same socket API. Switching is just swapping which binary is in your project root.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
89
142
|
## Examples
|
|
90
143
|
|
|
91
144
|
### Scrape a site and expose it as an API
|
|
@@ -96,7 +149,6 @@ import piggy from "nothing-browser";
|
|
|
96
149
|
await piggy.launch({ mode: "tab" });
|
|
97
150
|
await piggy.register("books", "https://books.toscrape.com");
|
|
98
151
|
|
|
99
|
-
// Block ads/trackers before any navigation
|
|
100
152
|
await piggy.books.intercept.block("*google-analytics*");
|
|
101
153
|
await piggy.books.intercept.block("*doubleclick*");
|
|
102
154
|
await piggy.books.intercept.block("*facebook*");
|
|
@@ -127,8 +179,6 @@ piggy.books.api("/list", async (_params, query) => {
|
|
|
127
179
|
|
|
128
180
|
piggy.books.noclose();
|
|
129
181
|
await piggy.serve(3000);
|
|
130
|
-
// GET http://localhost:3000/books/list
|
|
131
|
-
// GET http://localhost:3000/books/list?page=2
|
|
132
182
|
```
|
|
133
183
|
|
|
134
184
|
---
|
|
@@ -167,8 +217,6 @@ piggy.books.api("/search", async (_params, query) => {
|
|
|
167
217
|
|
|
168
218
|
return { query: query.q, count: books.length, books };
|
|
169
219
|
}, { ttl: 120_000, before: [logMiddleware, authMiddleware] });
|
|
170
|
-
|
|
171
|
-
// curl -H 'x-api-key: piggy-secret' 'http://localhost:3000/books/search?q=light'
|
|
172
220
|
```
|
|
173
221
|
|
|
174
222
|
---
|
|
@@ -178,11 +226,11 @@ piggy.books.api("/search", async (_params, query) => {
|
|
|
178
226
|
```ts
|
|
179
227
|
await piggy.books.capture.clear();
|
|
180
228
|
await piggy.books.capture.start();
|
|
181
|
-
await piggy.books.wait(300);
|
|
229
|
+
await piggy.books.wait(300);
|
|
182
230
|
|
|
183
231
|
await piggy.books.navigate("https://books.toscrape.com");
|
|
184
232
|
await piggy.books.waitForSelector("body", 10000);
|
|
185
|
-
await piggy.books.wait(2000);
|
|
233
|
+
await piggy.books.wait(2000);
|
|
186
234
|
|
|
187
235
|
await piggy.books.capture.stop();
|
|
188
236
|
|
|
@@ -203,13 +251,11 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
|
203
251
|
|
|
204
252
|
const SESSION_FILE = "./session.json";
|
|
205
253
|
|
|
206
|
-
// Restore on startup
|
|
207
254
|
if (existsSync(SESSION_FILE)) {
|
|
208
255
|
const saved = JSON.parse(readFileSync(SESSION_FILE, "utf8"));
|
|
209
256
|
await piggy.books.session.import(saved);
|
|
210
257
|
}
|
|
211
258
|
|
|
212
|
-
// Save on shutdown — always BEFORE piggy.close()
|
|
213
259
|
process.on("SIGINT", async () => {
|
|
214
260
|
const session = await piggy.books.session.export();
|
|
215
261
|
writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
@@ -222,8 +268,6 @@ process.on("SIGINT", async () => {
|
|
|
222
268
|
|
|
223
269
|
### Human mode
|
|
224
270
|
|
|
225
|
-
Makes interactions look less robotic — random delays, simulated typos + self-correction.
|
|
226
|
-
|
|
227
271
|
```ts
|
|
228
272
|
piggy.actHuman(true);
|
|
229
273
|
|
|
@@ -232,18 +276,14 @@ await piggy.books.type("#search", "mystery novels");
|
|
|
232
276
|
await piggy.books.scroll.by(400);
|
|
233
277
|
```
|
|
234
278
|
|
|
235
|
-
Affects: `click`, `type`, `hover`, `scroll.by`, `wait`.
|
|
236
|
-
|
|
237
279
|
---
|
|
238
280
|
|
|
239
281
|
### Screenshot / PDF
|
|
240
282
|
|
|
241
283
|
```ts
|
|
242
|
-
// Save to disk
|
|
243
284
|
await piggy.books.screenshot("./out/page.png");
|
|
244
285
|
await piggy.books.pdf("./out/page.pdf");
|
|
245
286
|
|
|
246
|
-
// Or get base64
|
|
247
287
|
const b64 = await piggy.books.screenshot();
|
|
248
288
|
```
|
|
249
289
|
|
|
@@ -255,11 +295,8 @@ const b64 = await piggy.books.screenshot();
|
|
|
255
295
|
await piggy.register("site1", "https://example.com");
|
|
256
296
|
await piggy.register("site2", "https://example.org");
|
|
257
297
|
|
|
258
|
-
// Same method on both sites in parallel
|
|
259
298
|
const titles = await piggy.all([piggy.site1, piggy.site2]).title();
|
|
260
|
-
|
|
261
|
-
// Keyed results by site name
|
|
262
|
-
const h1s = await piggy.diff([piggy.site1, piggy.site2]).fetchText("h1");
|
|
299
|
+
const h1s = await piggy.diff([piggy.site1, piggy.site2]).fetchText("h1");
|
|
263
300
|
// → { site1: "...", site2: "..." }
|
|
264
301
|
```
|
|
265
302
|
|
|
@@ -351,7 +388,7 @@ site.api(path, handler, opts?)
|
|
|
351
388
|
// opts: { ttl?, method?, before?: middleware[] }
|
|
352
389
|
// handler: (params, query, body) => Promise<any>
|
|
353
390
|
|
|
354
|
-
site.noclose()
|
|
391
|
+
site.noclose()
|
|
355
392
|
site.screenshot(filePath?) / site.pdf(filePath?)
|
|
356
393
|
```
|
|
357
394
|
|
|
@@ -359,12 +396,12 @@ site.screenshot(filePath?) / site.pdf(filePath?)
|
|
|
359
396
|
|
|
360
397
|
## Binary download
|
|
361
398
|
|
|
362
|
-
| Platform |
|
|
363
|
-
|
|
364
|
-
| Linux x86_64 (deb) | `nothing-browser-headless_*_amd64.deb` |
|
|
365
|
-
| Linux x86_64 (tar.gz) | `nothing-browser-headless-*-linux-x86_64.tar.gz` |
|
|
366
|
-
| Windows x64 | `nothing-browser-headless
|
|
367
|
-
| macOS | `nothing-browser-headless
|
|
399
|
+
| Platform | Headless | Headful | Full Browser |
|
|
400
|
+
|----------|----------|---------|--------------|
|
|
401
|
+
| Linux x86_64 (deb) | `nothing-browser-headless_*_amd64.deb` | `nothing-browser-headful_*_amd64.deb` | `nothing-browser_*_amd64.deb` |
|
|
402
|
+
| Linux x86_64 (tar.gz) | `nothing-browser-headless-*-linux-x86_64.tar.gz` | `nothing-browser-headful-*-linux-x86_64.tar.gz` | `nothing-browser-*-linux-x86_64.tar.gz` |
|
|
403
|
+
| Windows x64 | `nothing-browser-headless-*-windows-x64.zip` | `nothing-browser-headful-*-windows-x64.zip` | `nothing-browser-*-windows-x64.zip` |
|
|
404
|
+
| macOS | `nothing-browser-headless-*-macos.tar.gz` | `nothing-browser-headful-*-macos.tar.gz` | `nothing-browser-*-macos.dmg` |
|
|
368
405
|
|
|
369
406
|
→ [All releases](https://github.com/BunElysiaReact/nothing-browser/releases)
|
|
370
407
|
|
|
@@ -372,4 +409,4 @@ site.screenshot(filePath?) / site.pdf(filePath?)
|
|
|
372
409
|
|
|
373
410
|
## License
|
|
374
411
|
|
|
375
|
-
MIT © [Ernest Tech House](https://github.com/BunElysiaReact)
|
|
412
|
+
MIT © [Ernest Tech House](https://github.com/BunElysiaReact/nothing-browser)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothing-browser",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Scraper-first headless browser library — control real tabs, intercept network traffic, capture WebSockets, spoof fingerprints. Powered by Qt6/Chromium.",
|
|
5
5
|
"module": "piggy.ts",
|
|
6
6
|
"main": "piggy.ts",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"fingerprint-spoofing",
|
|
25
25
|
"network-interception"
|
|
26
26
|
],
|
|
27
|
-
"author": "Ernest Tech House
|
|
27
|
+
"author": "Ernest Tech House",
|
|
28
28
|
"license": "MIT",
|
|
29
|
-
"homepage": "https://github.com/
|
|
29
|
+
"homepage": "https://github.com/ernest-tech-house-co-operation/nothing-browser#readme",
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/
|
|
32
|
+
"url": "https://github.com/ernest-tech-house-co-operation/nothing-browser.git"
|
|
33
33
|
},
|
|
34
34
|
"bugs": {
|
|
35
|
-
"url": "https://github.com/
|
|
35
|
+
"url": "https://github.com/ernest-tech-house-co-operation/nothing-browser/issues"
|
|
36
36
|
},
|
|
37
37
|
"files": [
|
|
38
38
|
"piggy.ts",
|
package/piggy/launch/detect.ts
CHANGED
|
@@ -2,32 +2,42 @@ import { existsSync } from 'fs';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import logger from '../logger';
|
|
4
4
|
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
logger.error("❌ Binary not found in project root");
|
|
23
|
-
logger.error("");
|
|
24
|
-
logger.error("Download from:");
|
|
25
|
-
logger.error(" https://github.com/BunElysiaReact/nothing-browser/releases/");
|
|
26
|
-
logger.error("");
|
|
27
|
-
logger.error(`Place in: ${cwd}/nothing-browser-headless${process.platform === 'win32' ? '.exe' : ''}`);
|
|
28
|
-
if (process.platform !== 'win32') {
|
|
29
|
-
logger.error("Then run: chmod +x nothing-browser-headless");
|
|
5
|
+
export type BinaryMode = 'headless' | 'headful';
|
|
6
|
+
|
|
7
|
+
const BINARY_NAMES: Record<BinaryMode, string> = {
|
|
8
|
+
headless: 'nothing-browser-headless',
|
|
9
|
+
headful: 'nothing-browser-headful',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function detectBinary(mode: BinaryMode = 'headless'): string | null {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
const name = BINARY_NAMES[mode];
|
|
15
|
+
|
|
16
|
+
// Windows
|
|
17
|
+
if (process.platform === 'win32') {
|
|
18
|
+
const p = join(cwd, `${name}.exe`);
|
|
19
|
+
if (existsSync(p)) {
|
|
20
|
+
logger.success(`Binary found (${mode}): ${p}`);
|
|
21
|
+
return p;
|
|
30
22
|
}
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Linux / macOS
|
|
26
|
+
const p = join(cwd, name);
|
|
27
|
+
if (existsSync(p)) {
|
|
28
|
+
logger.success(`Binary found (${mode}): ${p}`);
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
logger.error(`❌ Binary not found in project root: ${name}`);
|
|
33
|
+
logger.error('');
|
|
34
|
+
logger.error('Download from:');
|
|
35
|
+
logger.error(' https://github.com/BunElysiaReact/nothing-browser/releases/');
|
|
36
|
+
logger.error('');
|
|
37
|
+
logger.error(`Place in: ${cwd}/${name}${process.platform === 'win32' ? '.exe' : ''}`);
|
|
38
|
+
if (process.platform !== 'win32') {
|
|
39
|
+
logger.error(`Then run: chmod +x ${name}`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null;
|
|
33
43
|
}
|
package/piggy/launch/spawn.ts
CHANGED
|
@@ -1,101 +1,99 @@
|
|
|
1
1
|
import { spawn } from 'bun';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
-
import { detectBinary } from './detect';
|
|
3
|
+
import { detectBinary, type BinaryMode } from './detect';
|
|
4
4
|
import logger from '../logger';
|
|
5
5
|
|
|
6
6
|
let activeProcess: any = null;
|
|
7
7
|
const extraProcesses: any[] = [];
|
|
8
8
|
|
|
9
9
|
export function killAllBrowsers(): void {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
try {
|
|
11
|
+
logger.info('Cleaning up existing browser processes...');
|
|
12
|
+
execSync('pkill -f nothing-browser-headless 2>/dev/null || true', { stdio: 'ignore' });
|
|
13
|
+
execSync('pkill -f nothing-browser-headful 2>/dev/null || true', { stdio: 'ignore' });
|
|
14
|
+
execSync('pkill -f QtWebEngineProcess 2>/dev/null || true', { stdio: 'ignore' });
|
|
15
|
+
execSync('rm -f /tmp/piggy', { stdio: 'ignore' });
|
|
16
|
+
} catch {
|
|
17
|
+
// no processes to kill
|
|
18
|
+
}
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
export async function spawnBrowser(): Promise<string> {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!done) {
|
|
43
|
-
const output = new TextDecoder().decode(value);
|
|
44
|
-
logger.debug(`[Browser] ${output}`);
|
|
45
|
-
read();
|
|
46
|
-
}
|
|
47
|
-
};
|
|
21
|
+
export async function spawnBrowser(mode: BinaryMode = 'headless'): Promise<string> {
|
|
22
|
+
killAllBrowsers();
|
|
23
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
24
|
+
|
|
25
|
+
const binaryPath = detectBinary(mode);
|
|
26
|
+
if (!binaryPath) {
|
|
27
|
+
throw new Error(`Binary not found (${mode}). Cannot launch.`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
logger.info(`Spawning Nothing Browser (${mode}) from: ${binaryPath}`);
|
|
31
|
+
|
|
32
|
+
activeProcess = spawn([binaryPath], {
|
|
33
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
34
|
+
env: process.env,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (activeProcess.stdout) {
|
|
38
|
+
const reader = activeProcess.stdout.getReader();
|
|
39
|
+
const read = async () => {
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (!done) {
|
|
42
|
+
logger.debug(`[Browser] ${new TextDecoder().decode(value)}`);
|
|
48
43
|
read();
|
|
49
|
-
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
read();
|
|
47
|
+
}
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
activeProcess.exited.then((code: number | null) => {
|
|
50
|
+
logger.warn(`Browser process exited with code: ${code}`);
|
|
51
|
+
activeProcess = null;
|
|
52
|
+
});
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
if (activeProcess) {
|
|
57
|
+
logger.success(`Browser spawned and running (${mode})`);
|
|
58
|
+
} else {
|
|
59
|
+
logger.error('Browser started but exited immediately');
|
|
60
|
+
}
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
return binaryPath;
|
|
65
63
|
}
|
|
66
64
|
|
|
67
|
-
export async function spawnBrowserOnSocket(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
export async function spawnBrowserOnSocket(
|
|
66
|
+
socketName: string,
|
|
67
|
+
mode: BinaryMode = 'headless'
|
|
68
|
+
): Promise<void> {
|
|
69
|
+
const binaryPath = detectBinary(mode);
|
|
70
|
+
if (!binaryPath) {
|
|
71
|
+
throw new Error(`Binary not found (${mode}). Cannot launch.`);
|
|
72
|
+
}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
logger.info(`Spawning browser (${mode}) on socket: ${socketName}`);
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
const proc = spawn([binaryPath], {
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
env: { ...process.env, PIGGY_SOCKET: socketName },
|
|
79
|
+
});
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
extraProcesses.push(proc);
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
proc.exited.then((code: number | null) => {
|
|
84
|
+
logger.warn(`Browser on socket ${socketName} exited with code: ${code}`);
|
|
85
|
+
});
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
88
|
+
logger.success(`Browser spawned (${mode}) on socket: ${socketName}`);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
export function killBrowser(): void {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
proc.kill();
|
|
99
|
-
}
|
|
100
|
-
extraProcesses.length = 0;
|
|
92
|
+
if (activeProcess) {
|
|
93
|
+
logger.info('Killing browser process...');
|
|
94
|
+
activeProcess.kill();
|
|
95
|
+
activeProcess = null;
|
|
96
|
+
}
|
|
97
|
+
for (const proc of extraProcesses) proc.kill();
|
|
98
|
+
extraProcesses.length = 0;
|
|
101
99
|
}
|
package/piggy.ts
CHANGED
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
// piggy.ts
|
|
2
|
-
import { detectBinary } from "./piggy/launch/detect";
|
|
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";
|
|
5
5
|
import { setClient, setHumanMode, createSiteObject } from "./piggy/register";
|
|
6
6
|
import { routeRegistry, keepAliveSites, startServer, stopServer } from "./piggy/server";
|
|
7
7
|
import logger from "./piggy/logger";
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type TabMode = "tab" | "process";
|
|
10
10
|
type SiteObject = ReturnType<typeof createSiteObject>;
|
|
11
11
|
|
|
12
12
|
let _client: PiggyClient | null = null;
|
|
13
|
-
let
|
|
13
|
+
let _tabMode: TabMode = "tab";
|
|
14
14
|
const _extraProcs: { socket: string; client: PiggyClient }[] = [];
|
|
15
15
|
const _sites: Record<string, SiteObject> = {};
|
|
16
16
|
|
|
17
17
|
const piggy: any = {
|
|
18
18
|
// ── Lifecycle ───────────────────────────────────────────────────────────────
|
|
19
19
|
|
|
20
|
-
launch: async (opts?: { mode?:
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
launch: async (opts?: { mode?: TabMode; binary?: BinaryMode }) => {
|
|
21
|
+
_tabMode = opts?.mode ?? "tab";
|
|
22
|
+
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
23
|
+
await spawnBrowser(binaryMode);
|
|
23
24
|
await new Promise(r => setTimeout(r, 500));
|
|
24
25
|
_client = new PiggyClient();
|
|
25
26
|
await _client.connect();
|
|
26
27
|
setClient(_client);
|
|
27
|
-
logger.info(`[piggy] launched
|
|
28
|
+
logger.info(`[piggy] launched — tab mode: "${_tabMode}", binary: "${binaryMode}"`);
|
|
28
29
|
return piggy;
|
|
29
30
|
},
|
|
30
31
|
|
|
31
|
-
register: async (name: string, url: string, opts?:
|
|
32
|
+
register: async (name: string, url: string, opts?: { binary?: BinaryMode }) => {
|
|
32
33
|
if (!url?.trim()) throw new Error(`No URL for site "${name}"`);
|
|
34
|
+
const binaryMode: BinaryMode = opts?.binary ?? "headless";
|
|
33
35
|
|
|
34
36
|
let tabId = "default";
|
|
35
|
-
if (
|
|
37
|
+
if (_tabMode === "tab") {
|
|
36
38
|
tabId = await _client!.newTab();
|
|
37
39
|
_sites[name] = createSiteObject(name, url, _client!, tabId);
|
|
38
40
|
piggy[name] = _sites[name];
|
|
39
41
|
logger.success(`[${name}] registered as tab ${tabId}`);
|
|
40
42
|
} else {
|
|
41
43
|
const socketName = `piggy_${name}`;
|
|
42
|
-
await spawnBrowserOnSocket(socketName);
|
|
44
|
+
await spawnBrowserOnSocket(socketName, binaryMode);
|
|
43
45
|
await new Promise(r => setTimeout(r, 500));
|
|
44
46
|
const c = new PiggyClient(socketName);
|
|
45
47
|
await c.connect();
|
|
@@ -49,7 +51,6 @@ const piggy: any = {
|
|
|
49
51
|
logger.success(`[${name}] registered as process on "${socketName}"`);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
|
-
if (opts?.mode) logger.info(`[${name}] mode: ${opts.mode}`);
|
|
53
54
|
return piggy;
|
|
54
55
|
},
|
|
55
56
|
|
|
@@ -61,7 +62,7 @@ const piggy: any = {
|
|
|
61
62
|
return piggy;
|
|
62
63
|
},
|
|
63
64
|
|
|
64
|
-
mode: (m:
|
|
65
|
+
mode: (m: TabMode) => { _tabMode = m; return piggy; },
|
|
65
66
|
|
|
66
67
|
// ── Elysia server ────────────────────────────────────────────────────────────
|
|
67
68
|
|