nothing-browser 0.0.2 → 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 +72 -33
- package/package.json +1 -1
- package/piggy/launch/detect.ts +37 -27
- package/piggy/launch/spawn.ts +73 -75
- package/piggy.ts +13 -12
package/README.md
CHANGED
|
@@ -7,24 +7,34 @@
|
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
9
|
<a href="https://www.npmjs.com/package/nothing-browser"><img src="https://img.shields.io/npm/v/nothing-browser" alt="npm version"/></a>
|
|
10
|
-
<a href="LICENSE"><img src="https://img.shields.io/github/license/
|
|
11
|
-
<a href="https://github.com/BunElysiaReact/nothing-browser/releases"><img src="https://img.shields.io/github/v/release/BunElysiaReact/nothing-browser" alt="
|
|
10
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/BunElysiaReact/nothing-browser" alt="license"/></a>
|
|
11
|
+
<a href="https://github.com/BunElysiaReact/nothing-browser/releases"><img src="https://img.shields.io/github/v/release/BunElysiaReact/nothing-browser" alt="releases"/></a>
|
|
12
12
|
</p>
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
A scraper-first headless browser library powered by the Nothing Browser Qt6/Chromium engine. Control real browser tabs, intercept network traffic, spoof fingerprints, capture WebSockets — all from Bun + TypeScript.
|
|
17
17
|
|
|
18
|
-
> **Two repos:**
|
|
19
|
-
> - This package (npm lib) → [ernest-tech-house-co-operation/nothing-browser](https://github.com/ernest-tech-house-co-operation/nothing-browser)
|
|
20
|
-
> - Headless binary → [BunElysiaReact/nothing-browser](https://github.com/BunElysiaReact/nothing-browser/releases)
|
|
21
|
-
|
|
22
18
|
---
|
|
23
19
|
|
|
24
20
|
## Requirements
|
|
25
21
|
|
|
26
22
|
- [Bun](https://bun.sh) ≥ 1.0
|
|
27
|
-
- 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.
|
|
28
38
|
|
|
29
39
|
---
|
|
30
40
|
|
|
@@ -34,17 +44,38 @@ A scraper-first headless browser library powered by the Nothing Browser Qt6/Chro
|
|
|
34
44
|
bun add nothing-browser
|
|
35
45
|
```
|
|
36
46
|
|
|
37
|
-
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.
|
|
48
|
+
|
|
49
|
+
### Linux
|
|
38
50
|
|
|
39
|
-
**
|
|
51
|
+
**Headless** (no visible window — most common for scraping)
|
|
40
52
|
```bash
|
|
41
53
|
tar -xzf nothing-browser-headless-*-linux-x86_64.tar.gz
|
|
42
54
|
chmod +x nothing-browser-headless
|
|
43
55
|
```
|
|
44
56
|
|
|
45
|
-
**
|
|
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.
|
|
75
|
+
|
|
76
|
+
### macOS
|
|
46
77
|
|
|
47
|
-
|
|
78
|
+
Download the `.tar.gz` for your chosen binary → extract → place the binary in your project root.
|
|
48
79
|
|
|
49
80
|
---
|
|
50
81
|
|
|
@@ -90,6 +121,24 @@ await piggy.launch({ mode: "process" });
|
|
|
90
121
|
|
|
91
122
|
---
|
|
92
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
|
+
|
|
93
142
|
## Examples
|
|
94
143
|
|
|
95
144
|
### Scrape a site and expose it as an API
|
|
@@ -130,8 +179,6 @@ piggy.books.api("/list", async (_params, query) => {
|
|
|
130
179
|
|
|
131
180
|
piggy.books.noclose();
|
|
132
181
|
await piggy.serve(3000);
|
|
133
|
-
// GET http://localhost:3000/books/list
|
|
134
|
-
// GET http://localhost:3000/books/list?page=2
|
|
135
182
|
```
|
|
136
183
|
|
|
137
184
|
---
|
|
@@ -170,8 +217,6 @@ piggy.books.api("/search", async (_params, query) => {
|
|
|
170
217
|
|
|
171
218
|
return { query: query.q, count: books.length, books };
|
|
172
219
|
}, { ttl: 120_000, before: [logMiddleware, authMiddleware] });
|
|
173
|
-
|
|
174
|
-
// curl -H 'x-api-key: piggy-secret' 'http://localhost:3000/books/search?q=light'
|
|
175
220
|
```
|
|
176
221
|
|
|
177
222
|
---
|
|
@@ -181,11 +226,11 @@ piggy.books.api("/search", async (_params, query) => {
|
|
|
181
226
|
```ts
|
|
182
227
|
await piggy.books.capture.clear();
|
|
183
228
|
await piggy.books.capture.start();
|
|
184
|
-
await piggy.books.wait(300);
|
|
229
|
+
await piggy.books.wait(300);
|
|
185
230
|
|
|
186
231
|
await piggy.books.navigate("https://books.toscrape.com");
|
|
187
232
|
await piggy.books.waitForSelector("body", 10000);
|
|
188
|
-
await piggy.books.wait(2000);
|
|
233
|
+
await piggy.books.wait(2000);
|
|
189
234
|
|
|
190
235
|
await piggy.books.capture.stop();
|
|
191
236
|
|
|
@@ -206,13 +251,11 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
|
206
251
|
|
|
207
252
|
const SESSION_FILE = "./session.json";
|
|
208
253
|
|
|
209
|
-
// Restore on startup
|
|
210
254
|
if (existsSync(SESSION_FILE)) {
|
|
211
255
|
const saved = JSON.parse(readFileSync(SESSION_FILE, "utf8"));
|
|
212
256
|
await piggy.books.session.import(saved);
|
|
213
257
|
}
|
|
214
258
|
|
|
215
|
-
// Save on shutdown — always BEFORE piggy.close()
|
|
216
259
|
process.on("SIGINT", async () => {
|
|
217
260
|
const session = await piggy.books.session.export();
|
|
218
261
|
writeFileSync(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
@@ -233,8 +276,6 @@ await piggy.books.type("#search", "mystery novels");
|
|
|
233
276
|
await piggy.books.scroll.by(400);
|
|
234
277
|
```
|
|
235
278
|
|
|
236
|
-
Affects: `click`, `type`, `hover`, `scroll.by`, `wait`.
|
|
237
|
-
|
|
238
279
|
---
|
|
239
280
|
|
|
240
281
|
### Screenshot / PDF
|
|
@@ -243,7 +284,6 @@ Affects: `click`, `type`, `hover`, `scroll.by`, `wait`.
|
|
|
243
284
|
await piggy.books.screenshot("./out/page.png");
|
|
244
285
|
await piggy.books.pdf("./out/page.pdf");
|
|
245
286
|
|
|
246
|
-
// or base64
|
|
247
287
|
const b64 = await piggy.books.screenshot();
|
|
248
288
|
```
|
|
249
289
|
|
|
@@ -256,8 +296,7 @@ await piggy.register("site1", "https://example.com");
|
|
|
256
296
|
await piggy.register("site2", "https://example.org");
|
|
257
297
|
|
|
258
298
|
const titles = await piggy.all([piggy.site1, piggy.site2]).title();
|
|
259
|
-
|
|
260
|
-
const h1s = await piggy.diff([piggy.site1, piggy.site2]).fetchText("h1");
|
|
299
|
+
const h1s = await piggy.diff([piggy.site1, piggy.site2]).fetchText("h1");
|
|
261
300
|
// → { site1: "...", site2: "..." }
|
|
262
301
|
```
|
|
263
302
|
|
|
@@ -272,7 +311,7 @@ const h1s = await piggy.diff([piggy.site1, piggy.site2]).fetchText("h1");
|
|
|
272
311
|
| `mode` | `"tab" \| "process"` | `"tab"` |
|
|
273
312
|
|
|
274
313
|
### `piggy.register(name, url)`
|
|
275
|
-
Registers a site. Accessible as `piggy.<
|
|
314
|
+
Registers a site. Accessible as `piggy.<name>` after registration.
|
|
276
315
|
|
|
277
316
|
### `piggy.actHuman(enable)`
|
|
278
317
|
Toggles human-like interaction timing globally.
|
|
@@ -357,17 +396,17 @@ site.screenshot(filePath?) / site.pdf(filePath?)
|
|
|
357
396
|
|
|
358
397
|
## Binary download
|
|
359
398
|
|
|
360
|
-
| Platform |
|
|
361
|
-
|
|
362
|
-
| Linux x86_64 (deb) | `nothing-browser-headless_*_amd64.deb` |
|
|
363
|
-
| Linux x86_64 (tar.gz) | `nothing-browser-headless-*-linux-x86_64.tar.gz` |
|
|
364
|
-
| Windows x64 | `nothing-browser-headless
|
|
365
|
-
| 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` |
|
|
366
405
|
|
|
367
|
-
→ [
|
|
406
|
+
→ [All releases](https://github.com/BunElysiaReact/nothing-browser/releases)
|
|
368
407
|
|
|
369
408
|
---
|
|
370
409
|
|
|
371
410
|
## License
|
|
372
411
|
|
|
373
|
-
MIT © [Ernest Tech House](https://github.com/
|
|
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",
|
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
|
|