@link-assistant/web-capture 1.1.2 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +17 -14
  2. package/README.md +256 -146
  3. package/bin/web-capture.js +403 -6
  4. package/package.json +11 -4
  5. package/src/animation.js +283 -0
  6. package/src/archive.js +212 -0
  7. package/src/batch.js +180 -0
  8. package/src/browser.js +150 -111
  9. package/src/docx.js +182 -0
  10. package/src/figures.js +208 -0
  11. package/src/gdocs.js +333 -0
  12. package/src/image.js +65 -8
  13. package/src/index.js +15 -0
  14. package/src/latex.js +127 -0
  15. package/src/lib.js +226 -1
  16. package/src/localize-images.js +202 -0
  17. package/src/metadata.js +333 -0
  18. package/src/pdf.js +76 -0
  19. package/src/popups.js +171 -0
  20. package/src/postprocess.js +265 -0
  21. package/src/retry.js +48 -0
  22. package/src/themed-image.js +167 -0
  23. package/src/verify.js +312 -0
  24. package/tests/e2e/docker.test.js +51 -12
  25. package/tests/e2e/process.test.js +34 -10
  26. package/tests/fixtures/habr-articles.js +45 -0
  27. package/tests/integration/api-endpoints.test.js +231 -0
  28. package/tests/integration/habr-article.test.js +337 -0
  29. package/tests/mock/index.test.js +5 -3
  30. package/tests/unit/animation.test.js +33 -0
  31. package/tests/unit/batch.test.js +114 -0
  32. package/tests/unit/browser.test.js +38 -2
  33. package/tests/unit/figures.test.js +74 -0
  34. package/tests/unit/gdocs.test.js +203 -0
  35. package/tests/unit/latex.test.js +123 -0
  36. package/tests/unit/localize-images.test.js +78 -0
  37. package/tests/unit/metadata.test.js +140 -0
  38. package/tests/unit/postprocess.test.js +108 -0
  39. package/tests/unit/retry.test.js +111 -0
  40. package/tests/unit/verify.test.js +182 -0
  41. package/.github/workflows/release.yml +0 -294
  42. package/ARCHITECTURE.md +0 -982
  43. package/LICENSE +0 -24
  44. package/apply.sh +0 -166
  45. package/docs/market-analysis-2025-05-13.md +0 -64
  46. package/scripts/changeset-version.mjs +0 -38
  47. package/scripts/create-github-release.mjs +0 -93
  48. package/scripts/create-manual-changeset.mjs +0 -88
  49. package/scripts/format-github-release.mjs +0 -83
  50. package/scripts/format-release-notes.mjs +0 -218
  51. package/scripts/instant-version-bump.mjs +0 -121
  52. package/scripts/publish-to-npm.mjs +0 -128
  53. package/scripts/setup-npm.mjs +0 -37
  54. package/scripts/validate-changeset.mjs +0 -106
  55. package/scripts/version-and-commit.mjs +0 -237
package/ARCHITECTURE.md DELETED
@@ -1,982 +0,0 @@
1
- # Web Capture Architecture
2
-
3
- ## Overview
4
-
5
- Web Capture is a lightweight microservice designed to fetch web pages and render them in multiple formats: HTML, Markdown, and PNG screenshots. It supports both Puppeteer and Playwright browser engines for headless browser automation to handle JavaScript-heavy websites and provides clean, processed output suitable for AI/LLM consumption, documentation, and archival purposes.
6
-
7
- **Version**: 1.0.0
8
- **License**: Unlicense (public domain)
9
- **Runtime**: Node.js 22.x
10
- **Module System**: ES Modules (type: "module")
11
-
12
- ---
13
-
14
- ## System Architecture
15
-
16
- ### High-Level Architecture
17
-
18
- ```mermaid
19
- graph TB
20
- subgraph Clients[Client Applications]
21
- DirectAPI[Direct HTTP Clients]
22
- PythonClient[Python Scripts]
23
- JSClient[JavaScript/Node.js]
24
- end
25
-
26
- subgraph WebCaptureService[Web Capture Microservice]
27
- ExpressAPI[Express.js API Server]
28
-
29
- subgraph Handlers[Request Handlers]
30
- HTMLHandler[HTML Handler]
31
- MarkdownHandler[Markdown Handler]
32
- ImageHandler[Image Handler]
33
- StreamHandler[Stream Handler]
34
- FetchHandler[Fetch Handler]
35
- end
36
-
37
- subgraph CoreLib[Core Library]
38
- FetchLogic[Fetch HTML Logic]
39
- HTMLParser[HTML Parser<br/>Cheerio]
40
- MarkdownConverter[Markdown Converter<br/>Turndown]
41
- URLConverter[URL Absolutifier]
42
- CharsetConverter[Charset Converter<br/>iconv-lite]
43
- end
44
-
45
- subgraph Browser[Headless Browser]
46
- Puppeteer[Puppeteer<br/>Chromium]
47
- end
48
- end
49
-
50
- subgraph External[External Resources]
51
- TargetWebsites[Target Websites]
52
- end
53
-
54
- Clients --> ExpressAPI
55
- ExpressAPI --> Handlers
56
- Handlers --> CoreLib
57
- Handlers --> Puppeteer
58
- CoreLib --> Puppeteer
59
- Puppeteer --> TargetWebsites
60
- FetchLogic --> TargetWebsites
61
-
62
- style Clients fill:#0088cc,stroke:#006699,stroke-width:3px,color:#fff
63
- style WebCaptureService fill:#1b5e20,stroke:#4caf50,stroke-width:3px,color:#fff
64
- style Handlers fill:#e65100,stroke:#ff6f00,stroke-width:3px,color:#fff
65
- style CoreLib fill:#4a148c,stroke:#9c27b0,stroke-width:3px,color:#fff
66
- style Browser fill:#01579b,stroke:#0277bd,stroke-width:3px,color:#fff
67
- style External fill:#b71c1c,stroke:#f44336,stroke-width:3px,color:#fff
68
- ```
69
-
70
- ---
71
-
72
- ## Directory Structure
73
-
74
- ```
75
- web-capture/
76
- ├── src/
77
- │ ├── index.js # Express server entry point
78
- │ ├── browser.js # Browser abstraction layer (Puppeteer & Playwright)
79
- │ ├── html.js # HTML endpoint handler
80
- │ ├── markdown.js # Markdown endpoint handler
81
- │ ├── image.js # Screenshot endpoint handler
82
- │ ├── stream.js # Stream endpoint handler (stub)
83
- │ ├── fetch.js # Fetch endpoint handler (stub)
84
- │ └── lib.js # Core utility functions
85
- │
86
- ├── tests/
87
- │ ├── unit/ # Unit tests
88
- │ │ ├── html2md.test.js
89
- │ │ ├── convertRelativeUrls.test.js
90
- │ │ └── browser.test.js
91
- │ ├── integration/ # Integration tests
92
- │ │ └── browser-engines.test.js
93
- │ ├── e2e/ # End-to-end tests
94
- │ │ ├── process.test.js
95
- │ │ └── docker.test.js
96
- │ ├── mock/ # Mock tests
97
- │ │ └── index.test.js
98
- │ └── jest.setup.mjs # Jest setup
99
- │
100
- ├── examples/
101
- │ ├── js/ # JavaScript examples
102
- │ │ ├── html_download.js
103
- │ │ ├── markdown_download.js
104
- │ │ ├── image_download.js
105
- │ │ ├── playwright_example.js
106
- │ │ └── engine_comparison.js
107
- │ └── python/ # Python examples
108
- │ ├── html_download.py
109
- │ ├── markdown_download.py
110
- │ ├── image_download.py
111
- │ └── playwright_example.py
112
- │
113
- ├── docs/
114
- │ └── market-analysis-2025-05-13.md
115
- │
116
- ├── Dockerfile # Container definition
117
- ├── docker-compose.yml # Docker orchestration
118
- ├── package.json # Dependencies & scripts
119
- ├── jest.config.mjs # Jest configuration
120
- ├── babel.config.cjs # Babel configuration
121
- ├── apply.sh # Deployment/apply script
122
- └── LICENSE # Unlicense
123
- ```
124
-
125
- ---
126
-
127
- ## Core Components
128
-
129
- ### 1. Express Server (`src/index.js`)
130
-
131
- **Entry Point**: Main HTTP server with Express.js
132
-
133
- **Endpoints**:
134
-
135
- - `GET /html?url=<URL>` - Raw HTML or Puppeteer-rendered HTML
136
- - `GET /markdown?url=<URL>` - Markdown conversion
137
- - `GET /image?url=<URL>` - PNG screenshot
138
- - `GET /stream?url=<URL>` - Stream handler (future)
139
- - `GET /fetch?url=<URL>` - Fetch handler (future)
140
-
141
- **Features**:
142
-
143
- - Graceful shutdown handling (SIGTERM, SIGINT)
144
- - Process PID logging
145
- - Uncaught exception handling
146
- - Docker-friendly timeout (2s force exit)
147
-
148
- **Server Configuration**:
149
-
150
- ```javascript
151
- const port = process.env.PORT || 3000;
152
- app.listen(port, () => {
153
- console.log(`Renderer service listening on http://localhost:${port}`);
154
- });
155
- ```
156
-
157
- ---
158
-
159
- ### 2. HTML Handler (`src/html.js`)
160
-
161
- **Purpose**: Fetch and return HTML content, with JavaScript rendering support
162
-
163
- #### Processing Flow
164
-
165
- ```mermaid
166
- flowchart TD
167
- A[Receive URL] --> B{URL valid?}
168
- B -->|No| C[400 Bad Request]
169
- B -->|Yes| D[Ensure absolute URL]
170
- D --> E[Fetch HTML directly]
171
- E --> F{Contains JavaScript<br/>or not HTML?}
172
- F -->|Yes| G[Launch Puppeteer]
173
- F -->|No| H[Convert to UTF-8]
174
- G --> I[Navigate to page]
175
- I --> J[Wait for networkidle0]
176
- J --> K[Wait 5 seconds]
177
- K --> L[Get rendered content]
178
- L --> M[Convert to UTF-8]
179
- H --> N[Convert relative URLs to absolute]
180
- M --> N
181
- N --> O[Return HTML]
182
-
183
- style F fill:#e65100,stroke:#ff6f00,stroke-width:2px,color:#fff
184
- style G fill:#01579b,stroke:#0277bd,stroke-width:2px,color:#fff
185
- style O fill:#1b5e20,stroke:#4caf50,stroke-width:2px,color:#fff
186
- ```
187
-
188
- **Key Features**:
189
-
190
- - **Smart Detection**: Checks if HTML contains JavaScript or is not valid HTML
191
- - **Puppeteer Rendering**: For JS-heavy sites, renders with headless Chrome
192
- - **UTF-8 Conversion**: Ensures proper character encoding
193
- - **URL Absolutification**: Converts all relative URLs to absolute
194
- - **Runtime JS Hook**: Injects JavaScript to fix dynamically loaded URLs
195
-
196
- **Puppeteer Configuration**:
197
-
198
- ```javascript
199
- const browser = await puppeteer.launch({
200
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
201
- });
202
- await page.setViewport({ width: 1280, height: 800 });
203
- await page.goto(url, {
204
- waitUntil: 'networkidle0',
205
- timeout: 30000,
206
- });
207
- ```
208
-
209
- ---
210
-
211
- ### 3. Markdown Handler (`src/markdown.js`)
212
-
213
- **Purpose**: Convert web pages to clean, readable Markdown
214
-
215
- **Process**:
216
-
217
- 1. Fetch HTML (via `fetchHtml`)
218
- 2. Convert to Markdown (via `convertHtmlToMarkdown`)
219
- 3. Return as `text/markdown`
220
-
221
- **Output**: Clean Markdown suitable for:
222
-
223
- - AI/LLM ingestion
224
- - Documentation
225
- - Note-taking systems
226
- - Text analysis
227
-
228
- ---
229
-
230
- ### 4. Image Handler (`src/image.js`)
231
-
232
- **Purpose**: Capture PNG screenshots of web pages
233
-
234
- **Features**:
235
-
236
- - Viewport-only screenshot (not full page)
237
- - Fixed viewport size: 1280x800
238
- - 5-second wait for dynamic content
239
- - PNG format output
240
-
241
- **Process**:
242
-
243
- ```mermaid
244
- sequenceDiagram
245
- participant Client
246
- participant Handler
247
- participant Puppeteer
248
- participant Website
249
-
250
- Client->>Handler: GET /image?url=example.com
251
- Handler->>Puppeteer: Launch browser
252
- Puppeteer->>Website: Navigate to URL
253
- Website-->>Puppeteer: Page loaded
254
- Puppeteer->>Puppeteer: Wait 5 seconds
255
- Puppeteer->>Puppeteer: Take screenshot
256
- Puppeteer-->>Handler: PNG buffer
257
- Handler->>Puppeteer: Close browser
258
- Handler-->>Client: image/png response
259
- ```
260
-
261
- **Response Headers**:
262
-
263
- ```javascript
264
- res.set('Content-Type', 'image/png');
265
- res.set('Content-Disposition', 'inline; filename="screenshot.png"');
266
- ```
267
-
268
- ---
269
-
270
- ### 5. Core Library (`src/lib.js`)
271
-
272
- #### Key Functions
273
-
274
- **`fetchHtml(url)`**
275
-
276
- - Simple fetch wrapper using node-fetch
277
- - Returns HTML as text
278
-
279
- **`convertHtmlToMarkdown(html, baseUrl)`**
280
-
281
- - Converts HTML to Markdown using Turndown
282
- - Cleans HTML before conversion
283
- - Absolutifies all URLs
284
-
285
- **HTML Cleaning Operations**:
286
-
287
- 1. Remove `<style>`, `<script>`, `<noscript>` tags
288
- 2. Remove inline event handlers (`onclick`, `onload`, etc.)
289
- 3. Remove `javascript:` links
290
- 4. Remove inline `style` attributes
291
- 5. Remove empty headings
292
- 6. Remove empty links
293
- 7. Remove links with only images (no alt text)
294
- 8. Convert ARIA role tables to semantic `<table>` elements
295
-
296
- **`convertRelativeUrls(html, baseUrl)`**
297
-
298
- - Converts all relative URLs to absolute
299
- - Handles: `<a>`, `<img>`, `<script>`, `<link>`, `<form>`, `<video>`, `<audio>`, `<iframe>`, etc.
300
- - Processes inline `style` with `url()` references
301
- - Injects runtime JavaScript hook for dynamic content
302
-
303
- **Runtime JavaScript Hook**:
304
-
305
- ```javascript
306
- // Injected script that fixes URLs dynamically
307
- fixAllUrls(document);
308
- const observer = new MutationObserver((mutations) => {
309
- // Fix URLs in newly added elements
310
- });
311
- observer.observe(document.body, { childList: true, subtree: true });
312
- ```
313
-
314
- **`convertToUtf8(html)`**
315
-
316
- - Detects charset from `<meta>` tag
317
- - Converts HTML to UTF-8 using iconv-lite
318
- - Updates charset meta tag
319
-
320
- **Charset Detection**:
321
-
322
- ```javascript
323
- const charsetMatch = html.match(/<meta[^>]+charset=["']?([^"'>\s]+)/i);
324
- const currentCharset = charsetMatch ? charsetMatch[1].toLowerCase() : 'utf-8';
325
- ```
326
-
327
- ---
328
-
329
- ### 6. Browser Abstraction Layer (`src/browser.js`)
330
-
331
- **Purpose**: Unified interface supporting both Puppeteer and Playwright browser engines
332
-
333
- #### Key Features
334
-
335
- - **Engine Selection**: Choose between Puppeteer and Playwright via query parameter or environment variable
336
- - **Unified API**: Same interface for both engines, enabling easy switching
337
- - **Automatic Adaptation**: Translates engine-specific options (e.g., `networkidle0` to `networkidle` for Playwright)
338
- - **Backward Compatibility**: Defaults to Puppeteer for existing deployments
339
-
340
- #### Browser Factory
341
-
342
- **`createBrowser(engine, options)`**
343
-
344
- - Creates a browser instance using the specified engine
345
- - Returns a unified `BrowserAdapter` object
346
- - Supported engines: `'puppeteer'` or `'playwright'`
347
-
348
- **`getBrowserEngine(req)`**
349
-
350
- - Determines which browser engine to use
351
- - Priority: query parameter > environment variable > default (puppeteer)
352
- - Supported query params: `engine` or `browser`
353
- - Supported values: `puppeteer`, `pptr`, `playwright`, `pw` (case-insensitive)
354
-
355
- #### Browser Adapter Interface
356
-
357
- ```javascript
358
- {
359
- async newPage() // Create a new page
360
- async close() // Close the browser
361
- type // Engine type ('puppeteer' or 'playwright')
362
- _browser // Original browser object (internal)
363
- }
364
- ```
365
-
366
- #### Page Adapter Interface
367
-
368
- ```javascript
369
- {
370
- async setExtraHTTPHeaders(headers) // Set HTTP headers
371
- async setUserAgent(userAgent) // Set user agent
372
- async setViewport(viewport) // Set viewport size
373
- async goto(url, options) // Navigate to URL
374
- async content() // Get page HTML
375
- async screenshot(options) // Take screenshot
376
- async close() // Close the page
377
- _page // Original page object (internal)
378
- _type // Engine type
379
- }
380
- ```
381
-
382
- #### Engine Differences Handled
383
-
384
- The abstraction layer automatically handles differences between engines:
385
-
386
- 1. **Viewport Setting**:
387
- - Puppeteer: `page.setViewport()`
388
- - Playwright: `page.setViewportSize()`
389
-
390
- 2. **Network Idle Strategy**:
391
- - Puppeteer: `waitUntil: 'networkidle0'`
392
- - Playwright: `waitUntil: 'networkidle'`
393
-
394
- 3. **Browser Launch**:
395
- - Puppeteer: `puppeteer.launch()`
396
- - Playwright: `playwright.chromium.launch()`
397
-
398
- #### Usage Examples
399
-
400
- **Using Puppeteer (default)**:
401
-
402
- ```javascript
403
- const browser = await createBrowser('puppeteer');
404
- const page = await browser.newPage();
405
- await page.goto('https://example.com');
406
- const html = await page.content();
407
- await browser.close();
408
- ```
409
-
410
- **Using Playwright**:
411
-
412
- ```javascript
413
- const browser = await createBrowser('playwright');
414
- const page = await browser.newPage();
415
- await page.goto('https://example.com');
416
- const html = await page.content();
417
- await browser.close();
418
- ```
419
-
420
- **In Request Handlers**:
421
-
422
- ```javascript
423
- export async function htmlHandler(req, res) {
424
- const engine = getBrowserEngine(req); // From query param or env
425
- const browser = await createBrowser(engine);
426
- // ... rest of implementation
427
- }
428
- ```
429
-
430
- ---
431
-
432
- ## API Documentation
433
-
434
- ### GET /html
435
-
436
- **Description**: Fetch and return HTML content
437
-
438
- **Parameters**:
439
-
440
- - `url` (required): Target URL
441
- - `engine` (optional): Browser engine (`puppeteer` or `playwright`). Default: `puppeteer`
442
-
443
- **Response**:
444
-
445
- - `Content-Type: text/html; charset=utf-8`
446
- - Body: HTML content
447
-
448
- **Examples**:
449
-
450
- ```bash
451
- # Using default Puppeteer engine
452
- curl "http://localhost:3000/html?url=https://example.com"
453
-
454
- # Using Playwright engine
455
- curl "http://localhost:3000/html?url=https://example.com&engine=playwright"
456
- ```
457
-
458
- **Behavior**:
459
-
460
- - Plain HTML sites: Direct fetch + UTF-8 conversion + URL absolutification
461
- - JavaScript sites: Browser rendering (Puppeteer or Playwright) + 5s wait + URL absolutification
462
-
463
- ---
464
-
465
- ### GET /markdown
466
-
467
- **Description**: Convert web page to Markdown
468
-
469
- **Parameters**:
470
-
471
- - `url` (required): Target URL
472
-
473
- **Response**:
474
-
475
- - `Content-Type: text/markdown`
476
- - Body: Markdown content
477
-
478
- **Example**:
479
-
480
- ```bash
481
- curl "http://localhost:3000/markdown?url=https://example.com" > page.md
482
- ```
483
-
484
- **Features**:
485
-
486
- - GitHub Flavored Markdown (GFM)
487
- - Clean, readable output
488
- - Absolute URLs preserved
489
- - Code blocks with fenced syntax
490
-
491
- ---
492
-
493
- ### GET /image
494
-
495
- **Description**: Capture PNG screenshot
496
-
497
- **Parameters**:
498
-
499
- - `url` (required): Target URL
500
- - `engine` (optional): Browser engine (`puppeteer` or `playwright`). Default: `puppeteer`
501
-
502
- **Response**:
503
-
504
- - `Content-Type: image/png`
505
- - `Content-Disposition: inline; filename="screenshot.png"`
506
- - Body: PNG image buffer
507
-
508
- **Examples**:
509
-
510
- ```bash
511
- # Using default Puppeteer engine
512
- curl "http://localhost:3000/image?url=https://example.com" > screenshot.png
513
-
514
- # Using Playwright engine
515
- curl "http://localhost:3000/image?url=https://example.com&engine=playwright" > screenshot.png
516
- ```
517
-
518
- **Screenshot Specs**:
519
-
520
- - Viewport: 1280x800
521
- - Wait time: 5 seconds after load
522
- - Format: PNG
523
- - Scope: Viewport only (not full page)
524
-
525
- ---
526
-
527
- ## Deployment
528
-
529
- ### Docker
530
-
531
- **Dockerfile** (Node 22 + Chromium):
532
-
533
- ```dockerfile
534
- FROM node:22-bullseye
535
-
536
- # Install Chromium and dependencies
537
- RUN apt-get update && apt-get install -y \
538
- chromium \
539
- fonts-liberation \
540
- # ... more dependencies
541
- --no-install-recommends
542
-
543
- # Puppeteer environment
544
- ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
545
- ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
546
- ENV PUPPETEER_ARGS="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage"
547
-
548
- COPY package.json yarn.lock ./
549
- RUN yarn install --frozen-lockfile
550
-
551
- COPY . .
552
- EXPOSE 3000
553
- ENTRYPOINT ["node", "bin/web-capture.js", "--serve"]
554
- ```
555
-
556
- ### Docker Compose
557
-
558
- ```yaml
559
- version: '3.8'
560
- services:
561
- web-capture:
562
- build: .
563
- ports:
564
- - '3000:3000'
565
- restart: unless-stopped
566
- environment:
567
- - PORT=3000
568
- ```
569
-
570
- **Commands**:
571
-
572
- ```bash
573
- # Build and start
574
- yarn start # or docker compose up -d
575
-
576
- # Build manually
577
- docker build -t web-capture .
578
- docker run -p 3000:3000 web-capture
579
- ```
580
-
581
- ---
582
-
583
- ## Usage Examples
584
-
585
- ### JavaScript (Node.js)
586
-
587
- ```javascript
588
- // examples/js/markdown_download.js
589
- import fetch from 'node-fetch';
590
- import fs from 'fs';
591
-
592
- const url = 'https://example.com';
593
- const response = await fetch(
594
- `http://localhost:3000/markdown?url=${encodeURIComponent(url)}`
595
- );
596
- const markdown = await response.text();
597
- fs.writeFileSync('downloaded.md', markdown);
598
- ```
599
-
600
- ### Python
601
-
602
- ```python
603
- # examples/python/markdown_download.py
604
- import requests
605
-
606
- url = 'https://example.com'
607
- response = requests.get(f'http://localhost:3000/markdown', params={'url': url})
608
- with open('downloaded.md', 'w') as f:
609
- f.write(response.text)
610
- ```
611
-
612
- ---
613
-
614
- ## Testing
615
-
616
- ### Test Structure
617
-
618
- **Unit Tests** (`tests/unit/`):
619
-
620
- - `html2md.test.js` - Markdown conversion tests
621
- - `convertRelativeUrls.test.js` - URL absolutification tests
622
-
623
- **E2E Tests** (`tests/e2e/`):
624
-
625
- - `process.test.js` - Local process tests
626
- - `docker.test.js` - Docker container tests
627
-
628
- **Mock Tests** (`tests/mock/`):
629
-
630
- - `index.test.js` - Mocked HTTP tests with nock
631
-
632
- ### Running Tests
633
-
634
- ```bash
635
- # All unit tests
636
- yarn test
637
-
638
- # Watch mode
639
- yarn test:watch
640
-
641
- # E2E tests (local process)
642
- yarn test:e2e:process
643
-
644
- # E2E tests (Docker)
645
- yarn test:e2e:docker
646
-
647
- # All tests (build + unit + e2e)
648
- yarn test:all
649
- ```
650
-
651
- ### Test Framework
652
-
653
- **Jest Configuration**:
654
-
655
- ```javascript
656
- export default {
657
- testEnvironment: 'node',
658
- transform: {
659
- '^.+\\.m?js$': 'babel-jest',
660
- },
661
- moduleNameMapper: {
662
- '^(\\.{1,2}/.*)\\.js$': '$1',
663
- },
664
- testMatch: ['**/tests/**/*.test.js'],
665
- };
666
- ```
667
-
668
- **Tools**:
669
-
670
- - Jest 29.7.0
671
- - Supertest (HTTP assertions)
672
- - Nock (HTTP mocking)
673
- - Babel (ES modules support)
674
-
675
- ---
676
-
677
- ## Configuration
678
-
679
- ### Environment Variables
680
-
681
- **General**:
682
-
683
- - `PORT` - Server port (default: 3000)
684
- - `BROWSER_ENGINE` - Default browser engine (`puppeteer` or `playwright`)
685
-
686
- **Puppeteer**:
687
-
688
- - `PUPPETEER_EXECUTABLE_PATH` - Chromium path (Docker: `/usr/bin/chromium`)
689
- - `PUPPETEER_SKIP_CHROMIUM_DOWNLOAD` - Skip bundled Chromium (Docker: `true`)
690
- - `PUPPETEER_ARGS` - Additional Puppeteer launch args
691
-
692
- **Playwright**:
693
-
694
- - `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` - Chromium path (Docker: `/usr/bin/chromium`)
695
- - `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` - Skip browser download (Docker: `1`)
696
-
697
- ### Browser Configuration
698
-
699
- **Browser Launch Args** (both engines):
700
-
701
- ```javascript
702
- args: [
703
- '--no-sandbox', // Required for Docker
704
- '--disable-setuid-sandbox', // Required for Docker
705
- '--disable-dev-shm-usage', // Avoid shared memory issues
706
- ];
707
- ```
708
-
709
- **Page Options** (both engines):
710
-
711
- - User Agent: Chrome 120 on Windows 10
712
- - Viewport: 1280x800
713
- - Wait strategy: `networkidle0` (Puppeteer) / `networkidle` (Playwright)
714
- - Timeout: 30 seconds
715
- - Additional wait: 5 seconds for dynamic content
716
-
717
- ---
718
-
719
- ## Dependencies
720
-
721
- ### Production
722
-
723
- ```json
724
- {
725
- "cheerio": "^1.0.0", // HTML parsing
726
- "express": "^4.18.2", // Web server
727
- "node-fetch": "^2.7.0", // HTTP client
728
- "playwright": "^1.49.0", // Headless browser (Playwright)
729
- "puppeteer": "^24.8.2", // Headless browser (Puppeteer)
730
- "turndown": "^7.1.1" // HTML to Markdown
731
- }
732
- ```
733
-
734
- ### Development
735
-
736
- ```json
737
- {
738
- "@babel/core": "^7.24.0", // Babel core
739
- "@babel/preset-env": "^7.24.0", // Babel preset
740
- "babel-jest": "^29.7.0", // Jest Babel integration
741
- "get-port": "^7.1.0", // Find available port
742
- "jest": "^29.7.0", // Test framework
743
- "nock": "^13.5.4", // HTTP mocking
744
- "supertest": "^6.3.4", // HTTP assertions
745
- "turndown-plugin-gfm": "^1.0.2" // GitHub Flavored Markdown
746
- }
747
- ```
748
-
749
- ---
750
-
751
- ## Performance
752
-
753
- ### Resource Usage
754
-
755
- - **Memory**: ~200-500MB (Puppeteer + Chromium)
756
- - **CPU**: Moderate during rendering
757
- - **Disk**: Minimal (no storage)
758
- - **Network**: Depends on target website
759
-
760
- ### Optimization Opportunities
761
-
762
- 1. **Browser Pooling**: Reuse Puppeteer browser instances instead of launching for each request
763
- 2. **Caching**: Cache rendered HTML/screenshots with TTL
764
- 3. **Queue System**: Background processing for slow requests
765
- 4. **Resource Limits**: Set memory/CPU limits for Puppeteer
766
- 5. **Parallel Processing**: Handle multiple requests concurrently
767
- 6. **Data Storage**: Migrate to [links-notation](https://github.com/link-foundation/links-notation) for caching results, then to [Doublets](https://github.com/linksplatform/Data.Doublets) for efficient Associative Knowledge Network storage of web content relationships
768
-
769
- ### Timeouts
770
-
771
- - Page navigation: 30 seconds
772
- - Post-load wait: 5 seconds
773
- - Server shutdown: 2 seconds
774
-
775
- ---
776
-
777
- ## Security Considerations
778
-
779
- ### Input Validation
780
-
781
- - URL parameter is required
782
- - URL is validated and made absolute
783
- - Malicious URLs (javascript:, data:, blob:) are handled
784
-
785
- ### Puppeteer Security
786
-
787
- - `--no-sandbox` required for Docker (but reduces isolation)
788
- - User agent spoofing (appears as regular browser)
789
- - No persistent browser data
790
- - Fresh browser instance per request
791
-
792
- ### Content Sanitization
793
-
794
- **Markdown Conversion**:
795
-
796
- - Removes `<script>` tags
797
- - Removes inline event handlers
798
- - Removes `javascript:` links
799
- - Removes inline styles
800
-
801
- **HTML Output**:
802
-
803
- - Preserves original content (intentionally)
804
- - Converts to UTF-8 for consistency
805
-
806
- ### Docker Security
807
-
808
- - Non-root user execution (recommended but not implemented)
809
- - Read-only filesystem (can be configured)
810
- - Resource limits (can be configured)
811
-
812
- ---
813
-
814
- ## Known Issues & TODOs
815
-
816
- ### Active Issues
817
-
818
- 1. **Stream/Fetch Endpoints**: Stub implementations (not functional)
819
- 2. **Browser Reuse**: New browser launched per request (inefficient)
820
- 3. **No Caching**: Every request hits the target website
821
- 4. **No Rate Limiting**: Can be abused
822
- 5. **Large Pages**: Full page screenshots not supported
823
-
824
- ### Recommended Improvements
825
-
826
- 1. **Browser Pool**: Implement Puppeteer browser pooling for efficiency
827
- 2. **Caching Layer**: Redis or in-memory cache for frequent URLs
828
- 3. **Rate Limiting**: Per-IP or per-URL rate limiting
829
- 4. **Authentication**: Optional API key authentication
830
- 5. **Metrics**: Prometheus metrics for monitoring
831
- 6. **Logging**: Structured logging (Winston/Pino)
832
- 7. **Error Handling**: More granular error responses
833
- 8. **Full Page Screenshots**: Option for full-page captures
834
- 9. **PDF Export**: Add `/pdf` endpoint
835
- 10. **Content Storage**: Migrate from ephemeral to [links-notation](https://github.com/link-foundation/links-notation) (human-readable) then [Doublets](https://github.com/linksplatform/Data.Doublets) (Associative Knowledge Network) for persistent storage and content relationship mapping
836
-
837
- ---
838
-
839
- ## Troubleshooting
840
-
841
- ### Common Issues
842
-
843
- **1. "Error fetching HTML"**
844
-
845
- - Check if target URL is accessible
846
- - Verify network connectivity
847
- - Check if URL requires authentication
848
-
849
- **2. "Error capturing screenshot"**
850
-
851
- - Target website may block headless browsers
852
- - Page may have infinite loading scripts
853
- - Try increasing timeout
854
-
855
- **3. Puppeteer launch failed**
856
-
857
- - In Docker: Ensure Chromium dependencies are installed
858
- - Check `--no-sandbox` flag is present
859
- - Verify `PUPPETEER_EXECUTABLE_PATH`
860
-
861
- **4. Memory issues**
862
-
863
- - Chromium is memory-intensive
864
- - Consider increasing Docker memory limits
865
- - Implement browser pooling
866
-
867
- **5. Timeout errors**
868
-
869
- - Increase `timeout` in page.goto()
870
- - Some sites take > 30s to load
871
- - Check if site requires user interaction
872
-
873
- ### Debug Mode
874
-
875
- ```bash
876
- # Enable Puppeteer debugging
877
- DEBUG=puppeteer:* node src/index.js
878
-
879
- # Verbose logging
880
- NODE_ENV=development node src/index.js
881
- ```
882
-
883
- ---
884
-
885
- ## Development
886
-
887
- ### Local Setup
888
-
889
- ```bash
890
- # Install dependencies
891
- yarn install
892
-
893
- # Run in development mode (with nodemon)
894
- yarn dev
895
-
896
- # Access the service
897
- curl http://localhost:3000/html?url=https://example.com
898
- ```
899
-
900
- ### Running Examples
901
-
902
- ```bash
903
- # JavaScript examples
904
- yarn examples:javascript
905
-
906
- # Python examples
907
- yarn examples:python
908
-
909
- # All examples (requires Docker build)
910
- yarn examples
911
- ```
912
-
913
- ### Adding a New Endpoint
914
-
915
- 1. Create handler in `src/`:
916
-
917
- ```javascript
918
- // src/myEndpoint.js
919
- export async function myEndpointHandler(req, res) {
920
- const url = req.query.url;
921
- // Implementation
922
- res.send(result);
923
- }
924
- ```
925
-
926
- 2. Register in `src/index.js`:
927
-
928
- ```javascript
929
- import { myEndpointHandler } from './myEndpoint.js';
930
- app.get('/my-endpoint', myEndpointHandler);
931
- ```
932
-
933
- 3. Add tests in `tests/unit/`:
934
-
935
- ```javascript
936
- // tests/unit/myEndpoint.test.js
937
- test('myEndpoint works', async () => {
938
- // Test implementation
939
- });
940
- ```
941
-
942
- ---
943
-
944
- ## License
945
-
946
- This software is released into the **public domain** under the [Unlicense](https://unlicense.org).
947
-
948
- **Important**: The Unlicense is NOT the same as having no license or being "unlicensed". The Unlicense is a specific public domain dedication that explicitly grants everyone the freedom to use, modify, and distribute this software without restrictions.
949
-
950
- Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
951
-
952
- For the full license text, see the [LICENSE](LICENSE) file or visit [unlicense.org](https://unlicense.org).
953
-
954
- ---
955
-
956
- ## Contributors
957
-
958
- Deep.Assistant Team
959
-
960
- ---
961
-
962
- ## Glossary
963
-
964
- - **Puppeteer**: Node library for controlling headless Chrome/Chromium
965
- - **Playwright**: Cross-browser automation library supporting Chromium, Firefox, and WebKit
966
- - **Cheerio**: Fast, flexible HTML parser for Node.js
967
- - **Turndown**: HTML to Markdown converter
968
- - **iconv-lite**: Character encoding conversion library
969
- - **Headless Browser**: Browser without GUI, controlled programmatically
970
- - **networkidle0**: Page load state in Puppeteer (no network activity for 500ms)
971
- - **networkidle**: Page load state in Playwright (similar to networkidle0)
972
- - **UTF-8**: Universal character encoding standard
973
- - **GFM**: GitHub Flavored Markdown
974
- - **ARIA**: Accessible Rich Internet Applications (web accessibility standard)
975
- - **Viewport**: Visible area of a web page
976
- - **ES Modules**: JavaScript module system (import/export)
977
- - **Browser Abstraction Layer**: Unified interface supporting multiple browser engines
978
-
979
- ---
980
-
981
- _Document generated: 2025-10-25_
982
- _Version: 1.0.0_