camofox-browser 1.0.0
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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +328 -0
- package/bin/camofox-browser.js +6 -0
- package/dist/src/middleware/auth.d.ts +6 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +41 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/middleware/errors.d.ts +5 -0
- package/dist/src/middleware/errors.d.ts.map +1 -0
- package/dist/src/middleware/errors.js +33 -0
- package/dist/src/middleware/errors.js.map +1 -0
- package/dist/src/middleware/logging.d.ts +14 -0
- package/dist/src/middleware/logging.d.ts.map +1 -0
- package/dist/src/middleware/logging.js +57 -0
- package/dist/src/middleware/logging.js.map +1 -0
- package/dist/src/routes/core.d.ts +3 -0
- package/dist/src/routes/core.d.ts.map +1 -0
- package/dist/src/routes/core.js +579 -0
- package/dist/src/routes/core.js.map +1 -0
- package/dist/src/routes/openclaw.d.ts +3 -0
- package/dist/src/routes/openclaw.d.ts.map +1 -0
- package/dist/src/routes/openclaw.js +335 -0
- package/dist/src/routes/openclaw.js.map +1 -0
- package/dist/src/server.d.ts +2 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +74 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/services/browser.d.ts +5 -0
- package/dist/src/services/browser.d.ts.map +1 -0
- package/dist/src/services/browser.js +64 -0
- package/dist/src/services/browser.js.map +1 -0
- package/dist/src/services/session.d.ts +30 -0
- package/dist/src/services/session.d.ts.map +1 -0
- package/dist/src/services/session.js +204 -0
- package/dist/src/services/session.js.map +1 -0
- package/dist/src/services/tab.d.ts +85 -0
- package/dist/src/services/tab.d.ts.map +1 -0
- package/dist/src/services/tab.js +463 -0
- package/dist/src/services/tab.js.map +1 -0
- package/dist/src/types.d.ts +66 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/config.d.ts +49 -0
- package/dist/src/utils/config.d.ts.map +1 -0
- package/dist/src/utils/config.js +59 -0
- package/dist/src/utils/config.js.map +1 -0
- package/dist/src/utils/cookies.d.ts +30 -0
- package/dist/src/utils/cookies.d.ts.map +1 -0
- package/dist/src/utils/cookies.js +71 -0
- package/dist/src/utils/cookies.js.map +1 -0
- package/dist/src/utils/launcher.d.ts +19 -0
- package/dist/src/utils/launcher.d.ts.map +1 -0
- package/dist/src/utils/launcher.js +50 -0
- package/dist/src/utils/launcher.js.map +1 -0
- package/dist/src/utils/macros.d.ts +21 -0
- package/dist/src/utils/macros.d.ts.map +1 -0
- package/dist/src/utils/macros.js +29 -0
- package/dist/src/utils/macros.js.map +1 -0
- package/dist/src/utils/presets.d.ts +36 -0
- package/dist/src/utils/presets.d.ts.map +1 -0
- package/dist/src/utils/presets.js +276 -0
- package/dist/src/utils/presets.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0] - 2026-02-15
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Complete TypeScript rewrite with strict mode
|
|
7
|
+
- Modular architecture (routes/services/middleware/utils)
|
|
8
|
+
- Independent repo (no longer a fork)
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Geo preset system with 8 built-in presets (us-east, us-west, japan, uk, germany, vietnam, singapore, australia)
|
|
12
|
+
- Custom preset file support via CAMOFOX_PRESETS_FILE
|
|
13
|
+
- Composite session keys for multi-context support
|
|
14
|
+
- Tab session index for cross-session tab lookup
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
- Unused dependencies (playwright, playwright-extra, puppeteer-extra-plugin-stealth)
|
|
18
|
+
- ~200MB dependency weight reduction
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- OpenClaw /snapshot ref annotation bug (now uses shared helper)
|
|
22
|
+
- Session cleanup with prefix-based matching
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jo, Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# CamoFox Browser Server
|
|
2
|
+
|
|
3
|
+
> Anti-detection browser server for AI agents — TypeScript REST API wrapping the [Camoufox](https://github.com/daijro/camoufox) stealth browser engine
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](tsconfig.json)
|
|
7
|
+
[](package.json)
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Why CamoFox?](#why-camofox)
|
|
12
|
+
- [Features](#features)
|
|
13
|
+
- [Quick Start](#quick-start)
|
|
14
|
+
- [Architecture](#architecture)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Search Macros](#search-macros)
|
|
17
|
+
- [Geo Presets](#geo-presets)
|
|
18
|
+
- [Environment Variables](#environment-variables)
|
|
19
|
+
- [Deployment](#deployment)
|
|
20
|
+
- [Used With](#used-with)
|
|
21
|
+
- [Project Structure](#project-structure)
|
|
22
|
+
- [Contributing](#contributing)
|
|
23
|
+
- [Credits](#credits)
|
|
24
|
+
- [License](#license)
|
|
25
|
+
|
|
26
|
+
## Why CamoFox?
|
|
27
|
+
|
|
28
|
+
**The Problem**: Standard browser automation (Puppeteer, Playwright, Selenium) is easily detected by modern anti-bot systems. JavaScript-level patches are fragile and get bypassed quickly.
|
|
29
|
+
|
|
30
|
+
**The Solution**: CamoFox Browser Server wraps [Camoufox](https://github.com/daijro/camoufox), a Firefox fork with **C++ engine-level fingerprint spoofing**. No JavaScript injection — anti-detection happens at the browser engine level.
|
|
31
|
+
|
|
32
|
+
| Feature | Puppeteer/Playwright | CamoFox Browser Server |
|
|
33
|
+
|---------|---------------------|------------------------|
|
|
34
|
+
| Anti-detection | JavaScript patches (fragile) | C++ engine-level (robust) |
|
|
35
|
+
| Fingerprint spoofing | Limited | Full (engine-level) |
|
|
36
|
+
| Token efficiency | Raw HTML / screenshots | Accessibility snapshots (smaller + structured) |
|
|
37
|
+
| Integration | Direct SDK | REST API for any language / AI agent |
|
|
38
|
+
| AI agent support | Varies | MCP + OpenClaw compatible |
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **C++ Anti-Detection** — fingerprint spoofing at the Camoufox engine level (not JS injection)
|
|
43
|
+
- **REST API** — language-agnostic HTTP endpoints for browser automation and AI agent integration
|
|
44
|
+
- **Multi-Session** — concurrent isolated browser contexts per `userId` (defaults: max 50 sessions, max 10 tabs/session)
|
|
45
|
+
- **Geo Presets** — 8 built-in region presets (locale/timezone/geolocation) + custom presets file
|
|
46
|
+
- **14 Search Macros** — Google, YouTube, Amazon, Reddit (search + subreddit JSON), Wikipedia, Twitter, Yelp, Spotify, Netflix, LinkedIn, Instagram, TikTok, Twitch
|
|
47
|
+
- **Element Refs** — accessibility snapshots annotated with stable `eN` element references for precise interaction
|
|
48
|
+
- **Cookie Persistence** — import Netscape/Playwright-style cookies into a session (optional, gated by API key)
|
|
49
|
+
- **OpenClaw Plugin** — OpenClaw-compatible endpoints (`/start`, `/tabs/open`, `/act`, etc.)
|
|
50
|
+
- **TypeScript** — strict mode, typed request shapes, modular Express routes
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### From Source
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/redf0x1/camofox-browser.git
|
|
58
|
+
cd camofox-browser
|
|
59
|
+
npm install
|
|
60
|
+
npm run build
|
|
61
|
+
npm start
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Using npm (CLI)
|
|
65
|
+
|
|
66
|
+
This is a **server**, not a browser automation library. Most users should run it from source or Docker.
|
|
67
|
+
|
|
68
|
+
If you want a minimal CLI wrapper that starts the server (via the package `bin`):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install -g camofox-browser
|
|
72
|
+
camofox-browser
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or one-off:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx camofox-browser
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Using Docker
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
docker build -t camofox-browser .
|
|
85
|
+
docker run -p 9377:9377 camofox-browser
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Using Docker Compose
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
services:
|
|
92
|
+
camofox-browser:
|
|
93
|
+
build: .
|
|
94
|
+
ports:
|
|
95
|
+
- "9377:9377"
|
|
96
|
+
environment:
|
|
97
|
+
CAMOFOX_PORT: "9377"
|
|
98
|
+
# Optional auth gates
|
|
99
|
+
# CAMOFOX_API_KEY: "change-me"
|
|
100
|
+
# CAMOFOX_ADMIN_KEY: "change-me"
|
|
101
|
+
# Optional: proxy routing (also enables Camoufox geoip mode)
|
|
102
|
+
# PROXY_HOST: ""
|
|
103
|
+
# PROXY_PORT: ""
|
|
104
|
+
# PROXY_USERNAME: ""
|
|
105
|
+
# PROXY_PASSWORD: ""
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Verify
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl http://localhost:9377/health
|
|
112
|
+
# {"ok":true,"engine":"camoufox","browserConnected":true}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Architecture
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
AI Agent (MCP / OpenClaw / REST Client)
|
|
119
|
+
│
|
|
120
|
+
▼ HTTP REST API (port 9377)
|
|
121
|
+
┌──────────────────────────────────────────┐
|
|
122
|
+
│ CamoFox Browser Server │
|
|
123
|
+
│ (Express + TypeScript) │
|
|
124
|
+
├──────────────────────────────────────────┤
|
|
125
|
+
│ Routes Services │
|
|
126
|
+
│ ├── Core API ├── Browser │
|
|
127
|
+
│ └── OpenClaw compat ├── Session │
|
|
128
|
+
│ └── Tab ops │
|
|
129
|
+
├──────────────────────────────────────────┤
|
|
130
|
+
│ Camoufox Engine (anti-detect) │
|
|
131
|
+
│ Firefox fork + engine-level spoofing │
|
|
132
|
+
└──────────────────────────────────────────┘
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
|
|
137
|
+
Base URL: `http://localhost:9377`
|
|
138
|
+
|
|
139
|
+
### Core Endpoints
|
|
140
|
+
|
|
141
|
+
Note: For any endpoint that targets an existing tab (`/tabs/:tabId/...`), the server resolves `tabId` **within a `userId` scope**. If you omit `userId`, you will typically get `404 Tab not found`.
|
|
142
|
+
|
|
143
|
+
| Method | Endpoint | Description | Required | Auth |
|
|
144
|
+
|--------|----------|-------------|----------|------|
|
|
145
|
+
| POST | `/sessions/:userId/cookies` | Import cookies into a user session (Playwright cookie objects) | Path: `userId`; Body: `{ "cookies": Cookie[] }` | `Authorization: Bearer $CAMOFOX_API_KEY` |
|
|
146
|
+
| GET | `/health` | Health check (also pre-launches the browser) | None | None |
|
|
147
|
+
| GET | `/presets` | List available geo presets (built-in + custom) | None | None |
|
|
148
|
+
| POST | `/tabs` | Create a new tab (supports `preset` + per-field overrides) | Body: `userId` + (`sessionKey` or `listItemId`) | None |
|
|
149
|
+
| GET | `/tabs?userId=...` | List all tabs for a user (OpenClaw-compatible response shape) | Query: `userId` | None |
|
|
150
|
+
| POST | `/tabs/:tabId/navigate` | Navigate to a URL, or expand a search `macro` + `query` | Body: `userId` + (`url` or `macro`) | None |
|
|
151
|
+
| GET | `/tabs/:tabId/snapshot?userId=...` | Accessibility snapshot annotated with `eN` element refs | Query: `userId` | None |
|
|
152
|
+
| POST | `/tabs/:tabId/wait` | Wait for page readiness (DOM + optional network idle) | Body: `userId` | None |
|
|
153
|
+
| POST | `/tabs/:tabId/click` | Click by `ref` (e.g. `e12`) or CSS `selector` | Body: `userId` + (`ref` or `selector`) | None |
|
|
154
|
+
| POST | `/tabs/:tabId/type` | Type into an element by `ref` or CSS `selector` | Body: `userId` + (`ref` or `selector`) + `text` | None |
|
|
155
|
+
| POST | `/tabs/:tabId/press` | Press a key (e.g. `Enter`, `Escape`) | Body: `userId` + `key` | None |
|
|
156
|
+
| POST | `/tabs/:tabId/scroll` | Scroll up/down by pixels | Body: `userId` | None |
|
|
157
|
+
| POST | `/tabs/:tabId/back` | Go back | Body: `userId` | None |
|
|
158
|
+
| POST | `/tabs/:tabId/forward` | Go forward | Body: `userId` | None |
|
|
159
|
+
| POST | `/tabs/:tabId/refresh` | Refresh | Body: `userId` | None |
|
|
160
|
+
| GET | `/tabs/:tabId/links?userId=...&limit=50&offset=0` | Extract links (paginated) | Query: `userId` | None |
|
|
161
|
+
| GET | `/tabs/:tabId/screenshot?userId=...&fullPage=true` | Screenshot (PNG bytes) | Query: `userId` | None |
|
|
162
|
+
| GET | `/tabs/:tabId/stats?userId=...` | Tab stats + visited URLs | Query: `userId` | None |
|
|
163
|
+
| DELETE | `/tabs/:tabId` | Close a tab (expects JSON body: `{ "userId": "..." }`) | Body: `userId` | None |
|
|
164
|
+
| DELETE | `/tabs/group/:listItemId` | Close a tab group (expects JSON body: `{ "userId": "..." }`) | Body: `userId` | None |
|
|
165
|
+
| DELETE | `/sessions/:userId` | Close all sessions for a user | Path: `userId` | None |
|
|
166
|
+
|
|
167
|
+
### OpenClaw Endpoints
|
|
168
|
+
|
|
169
|
+
OpenClaw-compatible aliases (used by the OpenClaw plugin).
|
|
170
|
+
|
|
171
|
+
| Method | Endpoint | Description | Required | Auth |
|
|
172
|
+
|--------|----------|-------------|----------|------|
|
|
173
|
+
| GET | `/` | Status (alias of `/health`) | None | None |
|
|
174
|
+
| POST | `/tabs/open` | Open tab (OpenClaw request/response shape) | Body: `userId` + `url` | None |
|
|
175
|
+
| POST | `/start` | Start browser engine | None | None |
|
|
176
|
+
| POST | `/stop` | Stop browser engine | None | `x-admin-key: $CAMOFOX_ADMIN_KEY` |
|
|
177
|
+
| POST | `/navigate` | Navigate (OpenClaw request shape: `targetId` in body) | Body: `userId` + `targetId` + `url` | None |
|
|
178
|
+
| GET | `/snapshot?userId=...&targetId=...` | Snapshot (OpenClaw response shape) | Query: `userId` + `targetId` | None |
|
|
179
|
+
| POST | `/act` | Combined actions (`click`, `type`, `press`, `scroll`, `scrollIntoView`, `hover`, `wait`, `close`) | Body: `userId` + `targetId` + `kind` | None |
|
|
180
|
+
|
|
181
|
+
## Search Macros
|
|
182
|
+
|
|
183
|
+
Use macros via `POST /tabs/:tabId/navigate` with `{ "macro": "@google_search", "query": "..." }`.
|
|
184
|
+
|
|
185
|
+
| Macro | Engine |
|
|
186
|
+
|-------|--------|
|
|
187
|
+
| `@google_search` | Google |
|
|
188
|
+
| `@youtube_search` | YouTube |
|
|
189
|
+
| `@amazon_search` | Amazon |
|
|
190
|
+
| `@reddit_search` | Reddit (JSON) |
|
|
191
|
+
| `@reddit_subreddit` | Reddit subreddit (JSON) |
|
|
192
|
+
| `@wikipedia_search` | Wikipedia |
|
|
193
|
+
| `@twitter_search` | Twitter/X |
|
|
194
|
+
| `@yelp_search` | Yelp |
|
|
195
|
+
| `@spotify_search` | Spotify |
|
|
196
|
+
| `@netflix_search` | Netflix |
|
|
197
|
+
| `@linkedin_search` | LinkedIn |
|
|
198
|
+
| `@instagram_search` | Instagram tags |
|
|
199
|
+
| `@tiktok_search` | TikTok |
|
|
200
|
+
| `@twitch_search` | Twitch |
|
|
201
|
+
|
|
202
|
+
## Geo Presets
|
|
203
|
+
|
|
204
|
+
Built-in presets (also exposed via `GET /presets`):
|
|
205
|
+
|
|
206
|
+
| Preset | Locale | Timezone | Location |
|
|
207
|
+
|--------|--------|----------|----------|
|
|
208
|
+
| `us-east` | `en-US` | `America/New_York` | New York (40.7128, -74.0060) |
|
|
209
|
+
| `us-west` | `en-US` | `America/Los_Angeles` | Los Angeles (34.0522, -118.2437) |
|
|
210
|
+
| `japan` | `ja-JP` | `Asia/Tokyo` | Tokyo (35.6895, 139.6917) |
|
|
211
|
+
| `uk` | `en-GB` | `Europe/London` | London (51.5074, -0.1278) |
|
|
212
|
+
| `germany` | `de-DE` | `Europe/Berlin` | Berlin (52.5200, 13.4050) |
|
|
213
|
+
| `vietnam` | `vi-VN` | `Asia/Ho_Chi_Minh` | Ho Chi Minh City (10.8231, 106.6297) |
|
|
214
|
+
| `singapore` | `en-SG` | `Asia/Singapore` | Singapore (1.3521, 103.8198) |
|
|
215
|
+
| `australia` | `en-AU` | `Australia/Sydney` | Sydney (-33.8688, 151.2093) |
|
|
216
|
+
|
|
217
|
+
Create a tab with a preset:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
curl -X POST http://localhost:9377/tabs \
|
|
221
|
+
-H 'Content-Type: application/json' \
|
|
222
|
+
-d '{"userId":"agent1","sessionKey":"task1","preset":"japan","url":"https://example.com"}'
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Custom presets: set `CAMOFOX_PRESETS_FILE=/path/to/presets.json` (JSON object; keys become preset names).
|
|
226
|
+
|
|
227
|
+
## Environment Variables
|
|
228
|
+
|
|
229
|
+
| Variable | Default | Description |
|
|
230
|
+
|----------|---------|-------------|
|
|
231
|
+
| `CAMOFOX_PORT` | `9377` | Server port |
|
|
232
|
+
| `PORT` | (optional) | Alternative port env var (common in PaaS) |
|
|
233
|
+
| `NODE_ENV` | `development` | Node environment |
|
|
234
|
+
| `CAMOFOX_ADMIN_KEY` | (empty) | Required for `POST /stop` (sent via `x-admin-key`) |
|
|
235
|
+
| `CAMOFOX_API_KEY` | (empty) | Enables cookie import endpoint; sent via `Authorization: Bearer ...` |
|
|
236
|
+
| `CAMOFOX_COOKIES_DIR` | `~/.camofox/cookies` | Directory used by the OpenClaw plugin cookie tool |
|
|
237
|
+
| `CAMOFOX_PRESETS_FILE` | (unset) | Optional JSON file defining/overriding geo presets |
|
|
238
|
+
| `CAMOFOX_SESSION_TIMEOUT` | `1800000` | Session idle timeout in ms (min `60000`) |
|
|
239
|
+
| `CAMOFOX_MAX_SESSIONS` | `50` | Maximum concurrent sessions |
|
|
240
|
+
| `CAMOFOX_MAX_TABS` | `10` | Maximum tabs per session |
|
|
241
|
+
| `PROXY_HOST` | (empty) | Proxy host (enables proxy routing) |
|
|
242
|
+
| `PROXY_PORT` | (empty) | Proxy port |
|
|
243
|
+
| `PROXY_USERNAME` | (empty) | Proxy username |
|
|
244
|
+
| `PROXY_PASSWORD` | (empty) | Proxy password |
|
|
245
|
+
|
|
246
|
+
## Deployment
|
|
247
|
+
|
|
248
|
+
### Docker (Recommended)
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
docker build -t camofox-browser .
|
|
252
|
+
docker run -p 9377:9377 \
|
|
253
|
+
-e CAMOFOX_PORT=9377 \
|
|
254
|
+
camofox-browser
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Fly.io
|
|
258
|
+
|
|
259
|
+
This repo includes a starter `fly.toml` for one-command deploys.
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
fly launch
|
|
263
|
+
fly deploy
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Railway
|
|
267
|
+
|
|
268
|
+
- Create a new project → deploy from this GitHub repo
|
|
269
|
+
- Set `CAMOFOX_PORT=9377` (Railway will also provide `PORT`, which is supported)
|
|
270
|
+
- Ensure the service exposes port `9377`
|
|
271
|
+
|
|
272
|
+
### Render
|
|
273
|
+
|
|
274
|
+
- Create a new Web Service → deploy from this GitHub repo
|
|
275
|
+
- Use Docker (recommended) and expose port `9377`
|
|
276
|
+
- Set `CAMOFOX_PORT=9377` (or rely on Render `PORT`)
|
|
277
|
+
|
|
278
|
+
### System Requirements
|
|
279
|
+
|
|
280
|
+
- Node.js 18+ (20+ recommended)
|
|
281
|
+
- 2GB+ RAM (browser + contexts require significant memory)
|
|
282
|
+
- Linux recommended for production; macOS is fine for development
|
|
283
|
+
|
|
284
|
+
## Used With
|
|
285
|
+
|
|
286
|
+
| Project | Description |
|
|
287
|
+
|---------|-------------|
|
|
288
|
+
| [CamoFox MCP](https://github.com/redf0x1/camofox-mcp) | MCP (Model Context Protocol) server for Claude, Cursor, VS Code |
|
|
289
|
+
| [OpenClaw](https://openclaw.ai) | Open-source AI agent framework (compat endpoints included) |
|
|
290
|
+
| [Camoufox](https://github.com/daijro/camoufox) | Anti-detection Firefox browser engine |
|
|
291
|
+
|
|
292
|
+
## Project Structure
|
|
293
|
+
|
|
294
|
+
```text
|
|
295
|
+
src/
|
|
296
|
+
├── server.ts # Express app entry point
|
|
297
|
+
├── types.ts # Shared TypeScript interfaces
|
|
298
|
+
├── routes/
|
|
299
|
+
│ ├── core.ts # Core REST API (~21 endpoints)
|
|
300
|
+
│ └── openclaw.ts # OpenClaw compatibility (~7 endpoints)
|
|
301
|
+
├── services/
|
|
302
|
+
│ ├── browser.ts # Browser lifecycle (singleton)
|
|
303
|
+
│ ├── session.ts # Session management + limits
|
|
304
|
+
│ └── tab.ts # Tab operations (snapshot/click/type/etc.)
|
|
305
|
+
├── middleware/ # Auth, logging, errors
|
|
306
|
+
└── utils/ # Config, presets, macros
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Contributing
|
|
310
|
+
|
|
311
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
312
|
+
|
|
313
|
+
## Credits
|
|
314
|
+
|
|
315
|
+
This project is based on [camofox-browser](https://github.com/jo-inc/camofox-browser) by [Jo Inc](https://github.com/jo-inc) (YC W24) and the [Camoufox](https://github.com/daijro/camoufox) anti-detection browser engine by [daijro](https://github.com/daijro).
|
|
316
|
+
|
|
317
|
+
- [Camoufox](https://camoufox.com) - Firefox-based browser with C++ anti-detection
|
|
318
|
+
- [Donate to Camoufox's original creator daijro](https://camoufox.com/about/)
|
|
319
|
+
- [OpenClaw](https://openclaw.ai) - Open-source AI agent framework
|
|
320
|
+
|
|
321
|
+
## License
|
|
322
|
+
|
|
323
|
+
[MIT](LICENSE)
|
|
324
|
+
|
|
325
|
+
## Crypto Scam Warning
|
|
326
|
+
|
|
327
|
+
Sketchy people are doing sketchy things with crypto tokens named "Camofox" now that this project is getting attention. **Camofox is not a crypto project and will never be one.** Any token, coin, or NFT using the Camofox name has nothing to do with us.
|
|
328
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Request } from 'express';
|
|
2
|
+
export declare function timingSafeCompare(a: unknown, b: unknown): boolean;
|
|
3
|
+
export declare function getBearerToken(req: Request): string | null;
|
|
4
|
+
export declare function isAuthorizedWithApiKey(req: Request, apiKey: string): boolean;
|
|
5
|
+
export declare function isAuthorizedWithAdminKey(req: Request, adminKey: string): boolean;
|
|
6
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/middleware/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAUjE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAI1D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAI5E;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKhF"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.timingSafeCompare = timingSafeCompare;
|
|
7
|
+
exports.getBearerToken = getBearerToken;
|
|
8
|
+
exports.isAuthorizedWithApiKey = isAuthorizedWithApiKey;
|
|
9
|
+
exports.isAuthorizedWithAdminKey = isAuthorizedWithAdminKey;
|
|
10
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
|
+
function timingSafeCompare(a, b) {
|
|
12
|
+
if (typeof a !== 'string' || typeof b !== 'string')
|
|
13
|
+
return false;
|
|
14
|
+
const bufA = Buffer.from(a);
|
|
15
|
+
const bufB = Buffer.from(b);
|
|
16
|
+
if (bufA.length !== bufB.length) {
|
|
17
|
+
// Keep timing similar even for length mismatch.
|
|
18
|
+
node_crypto_1.default.timingSafeEqual(bufA, bufA);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return node_crypto_1.default.timingSafeEqual(bufA, bufB);
|
|
22
|
+
}
|
|
23
|
+
function getBearerToken(req) {
|
|
24
|
+
const auth = String(req.headers['authorization'] || '');
|
|
25
|
+
const match = auth.match(/^Bearer\s+(.+)$/i);
|
|
26
|
+
return match ? match[1] : null;
|
|
27
|
+
}
|
|
28
|
+
function isAuthorizedWithApiKey(req, apiKey) {
|
|
29
|
+
const token = getBearerToken(req);
|
|
30
|
+
if (!token)
|
|
31
|
+
return false;
|
|
32
|
+
return timingSafeCompare(token, apiKey);
|
|
33
|
+
}
|
|
34
|
+
function isAuthorizedWithAdminKey(req, adminKey) {
|
|
35
|
+
const header = req.headers['x-admin-key'];
|
|
36
|
+
if (!header)
|
|
37
|
+
return false;
|
|
38
|
+
const value = Array.isArray(header) ? header[0] : String(header);
|
|
39
|
+
return timingSafeCompare(value, adminKey);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/middleware/auth.ts"],"names":[],"mappings":";;;;;AAGA,8CAUC;AAED,wCAIC;AAED,wDAIC;AAED,4DAKC;AAhCD,8DAAiC;AAGjC,SAAgB,iBAAiB,CAAC,CAAU,EAAE,CAAU;IACvD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QACjC,gDAAgD;QAChD,qBAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,OAAO,KAAK,CAAC;IACd,CAAC;IACD,OAAO,qBAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC3C,CAAC;AAED,SAAgB,cAAc,CAAC,GAAY;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC7C,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAChC,CAAC;AAED,SAAgB,sBAAsB,CAAC,GAAY,EAAE,MAAc;IAClE,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,SAAgB,wBAAwB,CAAC,GAAY,EAAE,QAAgB;IACtE,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjE,OAAO,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
export declare function safeError(err: unknown): string;
|
|
3
|
+
export declare function errorMiddleware(err: unknown, req: Request, res: Response, _next: NextFunction): void;
|
|
4
|
+
export declare function installCrashHandlers(): void;
|
|
5
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../src/middleware/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAO/D,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAS9C;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAIpG;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAQ3C"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeError = safeError;
|
|
4
|
+
exports.errorMiddleware = errorMiddleware;
|
|
5
|
+
exports.installCrashHandlers = installCrashHandlers;
|
|
6
|
+
const config_1 = require("../utils/config");
|
|
7
|
+
const logging_1 = require("./logging");
|
|
8
|
+
const CONFIG = (0, config_1.loadConfig)();
|
|
9
|
+
function safeError(err) {
|
|
10
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
11
|
+
const stack = err instanceof Error ? err.stack : undefined;
|
|
12
|
+
if (CONFIG.nodeEnv === 'production') {
|
|
13
|
+
(0, logging_1.log)('error', 'internal error', { error: message, stack });
|
|
14
|
+
return 'Internal server error';
|
|
15
|
+
}
|
|
16
|
+
return message;
|
|
17
|
+
}
|
|
18
|
+
function errorMiddleware(err, req, res, _next) {
|
|
19
|
+
(0, logging_1.log)('error', 'request error', { reqId: req.reqId, path: req.path, error: safeError(err) });
|
|
20
|
+
if (res.headersSent)
|
|
21
|
+
return;
|
|
22
|
+
res.status(500).json({ error: safeError(err) });
|
|
23
|
+
}
|
|
24
|
+
function installCrashHandlers() {
|
|
25
|
+
process.on('uncaughtException', (err) => {
|
|
26
|
+
(0, logging_1.log)('error', 'uncaughtException', { error: err.message, stack: err.stack });
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
process.on('unhandledRejection', (reason) => {
|
|
30
|
+
(0, logging_1.log)('error', 'unhandledRejection', { reason: String(reason) });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/middleware/errors.ts"],"names":[],"mappings":";;AAOA,8BASC;AAED,0CAIC;AAED,oDAQC;AA9BD,4CAA6C;AAC7C,uCAAgC;AAEhC,MAAM,MAAM,GAAG,IAAA,mBAAU,GAAE,CAAC;AAE5B,SAAgB,SAAS,CAAC,GAAY;IACrC,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3D,IAAI,MAAM,CAAC,OAAO,KAAK,YAAY,EAAE,CAAC;QACrC,IAAA,aAAG,EAAC,OAAO,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1D,OAAO,uBAAuB,CAAC;IAChC,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,SAAgB,eAAe,CAAC,GAAY,EAAE,GAAY,EAAE,GAAa,EAAE,KAAmB;IAC7F,IAAA,aAAG,EAAC,OAAO,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3F,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO;IAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,SAAgB,oBAAoB;IACnC,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAU,EAAE,EAAE;QAC9C,IAAA,aAAG,EAAC,OAAO,EAAE,mBAAmB,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;QAC5E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAe,EAAE,EAAE;QACpD,IAAA,aAAG,EAAC,OAAO,EAAE,oBAAoB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
export type LogLevel = 'info' | 'warn' | 'error';
|
|
3
|
+
export declare function log(level: LogLevel, msg: string, fields?: Record<string, unknown>): void;
|
|
4
|
+
export declare function loggingMiddleware(req: Request, res: Response, next: NextFunction): void;
|
|
5
|
+
export interface StatsBeaconFields {
|
|
6
|
+
sessions: number;
|
|
7
|
+
tabs: number;
|
|
8
|
+
rssBytes: number;
|
|
9
|
+
heapUsedBytes: number;
|
|
10
|
+
uptimeSeconds: number;
|
|
11
|
+
browserConnected: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function startStatsBeacon(getFields: () => StatsBeaconFields): NodeJS.Timeout;
|
|
14
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../../src/middleware/logging.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAEjD,wBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI,CAa5F;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAqBvF;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,iBAAiB,GAAG,MAAM,CAAC,OAAO,CAanF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.log = log;
|
|
7
|
+
exports.loggingMiddleware = loggingMiddleware;
|
|
8
|
+
exports.startStatsBeacon = startStatsBeacon;
|
|
9
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
10
|
+
function log(level, msg, fields = {}) {
|
|
11
|
+
const entry = {
|
|
12
|
+
ts: new Date().toISOString(),
|
|
13
|
+
level,
|
|
14
|
+
msg,
|
|
15
|
+
...fields,
|
|
16
|
+
};
|
|
17
|
+
const line = JSON.stringify(entry);
|
|
18
|
+
if (level === 'error') {
|
|
19
|
+
process.stderr.write(line + '\n');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
process.stdout.write(line + '\n');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function loggingMiddleware(req, res, next) {
|
|
26
|
+
if (req.path === '/health')
|
|
27
|
+
return next();
|
|
28
|
+
const reqId = node_crypto_1.default.randomUUID().slice(0, 8);
|
|
29
|
+
req.reqId = reqId;
|
|
30
|
+
req.startTime = Date.now();
|
|
31
|
+
const bodyUserId = req.body?.userId;
|
|
32
|
+
const queryUserId = req.query?.userId;
|
|
33
|
+
const userId = (bodyUserId ?? queryUserId ?? '-');
|
|
34
|
+
log('info', 'req', { reqId, method: req.method, path: req.path, userId });
|
|
35
|
+
const origEnd = res.end.bind(res);
|
|
36
|
+
res.end = function patchedEnd(...args) {
|
|
37
|
+
const ms = Date.now() - (req.startTime ?? Date.now());
|
|
38
|
+
log('info', 'res', { reqId, status: res.statusCode, ms });
|
|
39
|
+
return origEnd(...args);
|
|
40
|
+
};
|
|
41
|
+
next();
|
|
42
|
+
}
|
|
43
|
+
function startStatsBeacon(getFields) {
|
|
44
|
+
return setInterval(() => {
|
|
45
|
+
const mem = process.memoryUsage();
|
|
46
|
+
const fields = getFields();
|
|
47
|
+
log('info', 'stats', {
|
|
48
|
+
sessions: fields.sessions,
|
|
49
|
+
tabs: fields.tabs,
|
|
50
|
+
rssBytes: mem.rss,
|
|
51
|
+
heapUsedBytes: mem.heapUsed,
|
|
52
|
+
uptimeSeconds: Math.floor(process.uptime()),
|
|
53
|
+
browserConnected: fields.browserConnected,
|
|
54
|
+
});
|
|
55
|
+
}, 5 * 60_000);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../../../src/middleware/logging.ts"],"names":[],"mappings":";;;;;AAKA,kBAaC;AAED,8CAqBC;AAWD,4CAaC;AAjED,8DAAiC;AAKjC,SAAgB,GAAG,CAAC,KAAe,EAAE,GAAW,EAAE,SAAkC,EAAE;IACrF,MAAM,KAAK,GAAG;QACb,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,KAAK;QACL,GAAG;QACH,GAAG,MAAM;KACT,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACnC,CAAC;SAAM,CAAC;QACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IACnC,CAAC;AACF,CAAC;AAED,SAAgB,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;IAChF,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,IAAI,EAAE,CAAC;IAE1C,MAAM,KAAK,GAAG,qBAAM,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC;IAClB,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE3B,MAAM,UAAU,GAAI,GAAG,CAAC,IAAoD,EAAE,MAAM,CAAC;IACrF,MAAM,WAAW,GAAI,GAAG,CAAC,KAAqD,EAAE,MAAM,CAAC;IACvF,MAAM,MAAM,GAAG,CAAC,UAAU,IAAI,WAAW,IAAI,GAAG,CAAY,CAAC;IAE7D,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAE1E,MAAM,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,GAAG,CAAC,GAAG,GAAG,SAAS,UAAU,CAAC,GAAG,IAAe;QAC/C,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QACtD,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;QAC1D,OAAO,OAAO,CAAC,GAAI,IAAoC,CAAC,CAAC;IAC1D,CAAC,CAAC;IAEF,IAAI,EAAE,CAAC;AACR,CAAC;AAWD,SAAgB,gBAAgB,CAAC,SAAkC;IAClE,OAAO,WAAW,CAAC,GAAG,EAAE;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE;YACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,QAAQ,EAAE,GAAG,CAAC,GAAG;YACjB,aAAa,EAAE,GAAG,CAAC,QAAQ;YAC3B,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YAC3C,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;SACzC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../../src/routes/core.ts"],"names":[],"mappings":"AA4CA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0iBxB,eAAe,MAAM,CAAC"}
|