camoufox 0.2.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +161 -0
- data/bin/camoufox +19 -0
- data/docs/native_port.md +96 -0
- data/ext/camoufox_native/camoufox_native.cpp +70 -0
- data/ext/camoufox_native/extconf.rb +12 -0
- data/lib/camoufox/__main__.rb +12 -0
- data/lib/camoufox/__version__.rb +5 -0
- data/lib/camoufox/addons.rb +21 -0
- data/lib/camoufox/async_api.rb +11 -0
- data/lib/camoufox/browserforge.yml +1 -0
- data/lib/camoufox/configuration.rb +18 -0
- data/lib/camoufox/exceptions.rb +18 -0
- data/lib/camoufox/fingerprints.rb +17 -0
- data/lib/camoufox/fonts.json +3 -0
- data/lib/camoufox/ip.rb +20 -0
- data/lib/camoufox/launchServer.js +65 -0
- data/lib/camoufox/locale.rb +23 -0
- data/lib/camoufox/native_bridge.rb +34 -0
- data/lib/camoufox/pkgman.rb +31 -0
- data/lib/camoufox/server.rb +45 -0
- data/lib/camoufox/sync_api.rb +87 -0
- data/lib/camoufox/utils.rb +69 -0
- data/lib/camoufox/virtdisplay.rb +16 -0
- data/lib/camoufox/visit.js +77 -0
- data/lib/camoufox/warnings.rb +12 -0
- data/lib/camoufox/warnings.yml +1 -0
- data/lib/camoufox/webgl/README.md +1 -0
- data/lib/camoufox.rb +55 -0
- data/lib/camoufox_native.bundle +0 -0
- metadata +134 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 00e2999e211a2e3ee480426bf3c9eed1a8c0b8760ece361bc2f1fde28db92cad
|
|
4
|
+
data.tar.gz: b10e48835271556ee652882db960a95087d3a896b2c6fe009b434c0ceb820acf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d32e355e981ed307fcd1793cab79952cb3512708d1cbeb868e1db90cf60d21e847611b4ade10c9c440f2aee2640ac56af67ea95c25420ef1768dddfe32b266bb
|
|
7
|
+
data.tar.gz: 939da8ab954c0df051fb00c678a2147ada31fc16447f23a69dc75943a79a50a638d919bf4c85e9f9d1e9f824008d956fd39410ba0b2d43f9d5a5a7826a2baa17
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
- Fix native launch options so the provided `headless` flag is respected by Playwright.
|
|
5
|
+
|
|
6
|
+
## 0.1.0
|
|
7
|
+
- Initial Ruby bridge around the Camoufox Python package (legacy).
|
|
8
|
+
- Native rewrite in progress: mirror `pythonlib/camoufox` structure, remove the Ruby Playwright
|
|
9
|
+
client dependency, provide stubbed launch options via C++, add an experimental server launcher, and
|
|
10
|
+
reintroduce a `SyncAPI` helper that drives Playwright through a Node bridge.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Camoufox contributors
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Camoufox Ruby
|
|
2
|
+
|
|
3
|
+
Camoufox Ruby is a work-in-progress native port of the
|
|
4
|
+
[Camoufox](https://github.com/daijro/camoufox) toolkit. The project is undergoing a full rewrite to
|
|
5
|
+
mirror the package layout of the reference Python implementation (`pythonlib/camoufox`) while keeping
|
|
6
|
+
all logic inside the Ruby gem.
|
|
7
|
+
|
|
8
|
+
> **Status:** Everything is stubbed. The gem exposes the same module/file structure as the Python
|
|
9
|
+
> package, but most methods simply return placeholder data or emit warnings. Real fingerprint
|
|
10
|
+
> generation, binary management, networking, and Web API spoofing are still to come.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Add the gem directly from the repository while the native rewrite is underway:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem "camoufox"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Prerequisites
|
|
21
|
+
|
|
22
|
+
- Ruby ≥ 3.0 (development targets 3.4.2)
|
|
23
|
+
- Node.js and `npm` (required by Playwright)
|
|
24
|
+
|
|
25
|
+
### Install steps
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone https://github.com/GaetanJuvin/camoufox-ruby.git
|
|
29
|
+
cd camoufox-ruby
|
|
30
|
+
bundle install
|
|
31
|
+
rake compile
|
|
32
|
+
npx install playwright
|
|
33
|
+
npx playwright install firefox
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
require "camoufox"
|
|
40
|
+
|
|
41
|
+
driver_dir = ENV.fetch("CAMOUFOX_PLAYWRIGHT_DRIVER_DIR", File.expand_path("node_modules/playwright", __dir__))
|
|
42
|
+
|
|
43
|
+
Camoufox.configure do |config|
|
|
44
|
+
config.playwright_driver_dir = driver_dir
|
|
45
|
+
config.node_path = ENV["CAMOUFOX_NODE_PATH"] if ENV["CAMOUFOX_NODE_PATH"]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Camoufox::SyncAPI::Camoufox.open(headless: true) do |browser|
|
|
49
|
+
page = browser.new_page
|
|
50
|
+
page.goto("https://example.com")
|
|
51
|
+
puts page.title
|
|
52
|
+
end
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Behind the scenes the Ruby port encodes the Camoufox launch options, hands them to a small Node.js
|
|
56
|
+
bridge, and lets Playwright do the heavy lifting. You must supply a Playwright driver bundle or
|
|
57
|
+
installation so the Node script can `require('playwright')`.
|
|
58
|
+
|
|
59
|
+
### Launching the Playwright server (experimental)
|
|
60
|
+
|
|
61
|
+
To mirror the Python helper that spins up a Playwright websocket endpoint, the Ruby port can invoke
|
|
62
|
+
Playwright's Node driver directly (no `playwright-ruby-client` dependency). You must provide the
|
|
63
|
+
location of a Playwright driver bundle that contains `lib/browserServerImpl.js`.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
export CAMOUFOX_PLAYWRIGHT_DRIVER_DIR=/path/to/playwright/driver/package
|
|
67
|
+
export CAMOUFOX_NODE_PATH=/path/to/node # optional, defaults to `node`
|
|
68
|
+
bundle exec ruby run.rb server
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The command prints the websocket endpoint and keeps the process alive, matching the Python
|
|
72
|
+
behaviour. Until the native mapper is complete, the underlying launch options remain stubbed.
|
|
73
|
+
|
|
74
|
+
## Module layout
|
|
75
|
+
|
|
76
|
+
The Ruby sources now mirror the structure of `pythonlib/camoufox`:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
lib/camoufox/
|
|
80
|
+
├── __init__ (lib/camoufox.rb)
|
|
81
|
+
├── __main__.rb
|
|
82
|
+
├── __version__.rb
|
|
83
|
+
├── addons.rb
|
|
84
|
+
├── async_api.rb
|
|
85
|
+
├── browserforge.yml
|
|
86
|
+
├── exceptions.rb
|
|
87
|
+
├── fingerprints.rb
|
|
88
|
+
├── fonts.json
|
|
89
|
+
├── ip.rb
|
|
90
|
+
├── locale.rb
|
|
91
|
+
├── pkgman.rb
|
|
92
|
+
├── server.rb
|
|
93
|
+
├── sync_api.rb
|
|
94
|
+
├── utils.rb
|
|
95
|
+
├── virtdisplay.rb
|
|
96
|
+
├── warnings.rb
|
|
97
|
+
└── webgl/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each file defines the corresponding Ruby module, currently implemented as lightweight stubs so that
|
|
101
|
+
call sites can be wired up without crashing.
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
The `bin/camoufox` executable is available, but commands only return informative placeholder
|
|
106
|
+
messages until the native implementation lands.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
camoufox version # => "Camoufox native stub v0.0.1"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The repository also includes a helper script, `run.rb`, with convenience commands:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
bundle exec ruby run.rb # show stub details and launch options
|
|
116
|
+
bundle exec ruby run.rb launch-options --locale en-US --headful
|
|
117
|
+
bundle exec ruby run.rb browse --url https://example.com
|
|
118
|
+
bundle exec ruby run.rb server
|
|
119
|
+
|
|
120
|
+
# or run the sample script directly
|
|
121
|
+
bundle exec ruby examples/sync_playwright.rb
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Configuration
|
|
125
|
+
|
|
126
|
+
`Camoufox.configure` exposes a tiny configuration object that is growing alongside the native port.
|
|
127
|
+
Today it supports the basic directories and the Playwright driver configuration:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
Camoufox.configure do |config|
|
|
131
|
+
config.data_dir = "/tmp/camoufox-data"
|
|
132
|
+
config.node_path = "/usr/local/bin/node"
|
|
133
|
+
config.playwright_driver_dir = "/opt/playwright-driver/package"
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Environment overrides:
|
|
138
|
+
|
|
139
|
+
- `CAMOUFOX_DATA_DIR` – override where Camoufox assets are stored (planned use)
|
|
140
|
+
- `CAMOUFOX_CACHE_DIR` – override the cache directory (planned use)
|
|
141
|
+
- `CAMOUFOX_NODE_PATH` – path to the Node.js binary used when spawning the Playwright server (defaults to `node`)
|
|
142
|
+
- `CAMOUFOX_PLAYWRIGHT_DRIVER_DIR` – directory containing `lib/browserServerImpl.js` (defaults to `node_modules/playwright` if present)
|
|
143
|
+
- `CAMOUFOX_PLAYWRIGHT_JS_REQUIRE` – optional module identifier passed to Node's `require()` when
|
|
144
|
+
running the synchronous Playwright bridge (defaults to `playwright`)
|
|
145
|
+
|
|
146
|
+
## Testing
|
|
147
|
+
|
|
148
|
+
Specs intentionally exercise only the pieces that exist today. Run them after compiling the native
|
|
149
|
+
extension:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
~/.rbenv/versions/3.4.2/bin/ruby -S bundle exec rspec
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Contributing
|
|
156
|
+
|
|
157
|
+
See `docs/native_port.md` for the roadmap toward feature parity with the Python Camoufox package.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT – see `LICENSE` for details.
|
data/bin/camoufox
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../lib/camoufox"
|
|
5
|
+
|
|
6
|
+
if ARGV.empty?
|
|
7
|
+
warn "Usage: camoufox <command> [args]"
|
|
8
|
+
exit 1
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
command = ARGV.shift
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
output = Camoufox::CLI.run(command, ARGV)
|
|
15
|
+
print(output) unless output.nil? || output.empty?
|
|
16
|
+
rescue Camoufox::MissingNativeExtension => e
|
|
17
|
+
warn e.message
|
|
18
|
+
exit 127
|
|
19
|
+
end
|
data/docs/native_port.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Camoufox Native Port Plan
|
|
2
|
+
|
|
3
|
+
This document tracks the work needed to replace the Python bridge with a native C++ implementation
|
|
4
|
+
that exposes the same surface area to the Ruby gem. The goal is to eventually match the behaviour of
|
|
5
|
+
the original Python package (`camoufox`) without shelling out to Python.
|
|
6
|
+
|
|
7
|
+
## Target surface area
|
|
8
|
+
|
|
9
|
+
The Ruby gem currently relies on the Python package for the following capabilities:
|
|
10
|
+
|
|
11
|
+
1. **Launch options** – `camoufox.launch_options` assembles the configuration blob passed to the
|
|
12
|
+
Camoufox Firefox binary. This touches nearly every helper in `pythonlib/camoufox/utils.py`.
|
|
13
|
+
2. **Binary management** – `camoufox fetch/remove/path/version` download the Firefox bundle from
|
|
14
|
+
GitHub releases, manage add-ons, fonts, and GeoIP data (`pkgman.py`, `addons.py`, `locale.py`).
|
|
15
|
+
3. **Warnings & validation** – type checking, config validation, leak warnings, logging.
|
|
16
|
+
4. **Virtual display and Playwright helpers** – optional Xvfb integration (Linux), environment
|
|
17
|
+
variable setup for the executable, proxy/geolocation interactions.
|
|
18
|
+
|
|
19
|
+
## Major components to port
|
|
20
|
+
|
|
21
|
+
| Python module | Responsibility | Native port considerations |
|
|
22
|
+
| ------------- | -------------- | --------------------------- |
|
|
23
|
+
| `utils.py` | Fingerprint generation, fonts, WebGL spoofing, config assembly, env var packing | Requires BrowserForge fingerprints DB, WebGL dataset, numpy-like utilities, UA parsing |
|
|
24
|
+
| `fingerprints.py` | Integrates BrowserForge logic | Need a native fingerprint generator and data loader |
|
|
25
|
+
| `pkgman.py` | Download/validate Camoufox binaries from GitHub, version constraints | Re-implement HTTP client, ZIP handling, progress reporting |
|
|
26
|
+
| `addons.py` | Default addon management, path validation | Port addon discovery + download logic |
|
|
27
|
+
| `locale.py` | GeoIP integration, locale selection | Needs MMDB parsing or a replacement library |
|
|
28
|
+
| `webgl/` | Pre-generated WebGL fingerprints | Convert dataset to native-friendly format |
|
|
29
|
+
| `virtdisplay.py` | Manages Xvfb | Detect platform, spawn background process |
|
|
30
|
+
| `cli` commands | fetch/remove/test/version | Recreate CLI front-end in Ruby or C++ |
|
|
31
|
+
|
|
32
|
+
## Architectural direction
|
|
33
|
+
|
|
34
|
+
1. **C++ shared library (`libcamoufox_native`)**
|
|
35
|
+
- Exposes a minimal API surface to Ruby (initially stubbed, later feature-complete).
|
|
36
|
+
- Organised into modules mirroring the Python package (fingerprint, config, pkgman, addons, geo).
|
|
37
|
+
- Uses modern C++ (C++20) with libraries for HTTP (e.g., libcurl), JSON (nlohmann/json), YAML
|
|
38
|
+
(yaml-cpp), ZIP handling (libzip/minizip), and MMDB (libmaxminddb).
|
|
39
|
+
|
|
40
|
+
2. **Ruby extension**
|
|
41
|
+
- Built via `extconf.rb` / `mkmf` (or CMake + rake) compiling against the shared library.
|
|
42
|
+
- Provides Ruby-friendly wrappers around the C++ API (converts to/from `VALUE`).
|
|
43
|
+
|
|
44
|
+
3. **Data assets**
|
|
45
|
+
- Ship fingerprint/WebGL datasets as JSON/YAML alongside the gem.
|
|
46
|
+
- Provide tooling to update assets from upstream Camoufox/BrowserForge releases.
|
|
47
|
+
|
|
48
|
+
4. **CLI integration**
|
|
49
|
+
- Re-implement the `camoufox` CLI commands purely in Ruby, calling into the native library for
|
|
50
|
+
heavy work.
|
|
51
|
+
- Provide an optional Node.js bridge for Playwright interactions while retaining the ability to
|
|
52
|
+
talk directly to the Playwright driver (no Ruby gem dependency).
|
|
53
|
+
|
|
54
|
+
## Milestones
|
|
55
|
+
|
|
56
|
+
1. **Bootstrap**
|
|
57
|
+
- Scaffold `ext/camoufox_native` with a shared library exposing `launch_options` (stubbed).
|
|
58
|
+
- Replace `Camoufox::PythonBridge` with `Camoufox::NativeBridge` calling the extension.
|
|
59
|
+
- Ensure existing specs pass using stubbed data.
|
|
60
|
+
|
|
61
|
+
2. **Binary manager parity**
|
|
62
|
+
- Port GitHub release fetching + ZIP extraction.
|
|
63
|
+
- Implement `fetch/remove/path/version` in C++.
|
|
64
|
+
|
|
65
|
+
3. **Fingerprint generation**
|
|
66
|
+
- Port BrowserForge fingerprint logic (initially with static samples, then full generator).
|
|
67
|
+
- Implement WebGL + fonts spoofing pipeline.
|
|
68
|
+
|
|
69
|
+
4. **Advanced features**
|
|
70
|
+
- GeoIP integration, proxy warnings, virtual display support, leak warnings.
|
|
71
|
+
|
|
72
|
+
5. **Testing & QA**
|
|
73
|
+
- Build parity test suite comparing native output with Python reference data.
|
|
74
|
+
- Automate cross-platform builds.
|
|
75
|
+
|
|
76
|
+
## Immediate next steps
|
|
77
|
+
|
|
78
|
+
- Define the data model for fingerprints and decide how to ingest BrowserForge datasets natively.
|
|
79
|
+
- Choose third-party libraries (if any) for HTTP, ZIP, MMDB, JSON, and YAML handling; prototype
|
|
80
|
+
minimal integrations.
|
|
81
|
+
- Design a stable C API surface for the C++ library so Ruby (and potentially other languages) can
|
|
82
|
+
interact with it without tight coupling to implementation details.
|
|
83
|
+
- Expand the Node bridge to support more Playwright commands (multi-page workflows, screenshotting,
|
|
84
|
+
etc.) once the native mapper delivers real launch data.
|
|
85
|
+
|
|
86
|
+
## Open questions
|
|
87
|
+
|
|
88
|
+
- How closely do we need to match BrowserForge’s fingerprint distribution? Reuse the dataset or
|
|
89
|
+
re-implement the generation algorithm?
|
|
90
|
+
- Should the native component expose a stable C API (for reuse in other languages) or remain
|
|
91
|
+
Ruby-specific for now?
|
|
92
|
+
- Packaging strategy for large assets (fonts, WebGL datasets) in a gem-friendly way.
|
|
93
|
+
- Publishing pipeline for https://github.com/GaetanJuvin/camoufox-ruby (tests, builds, release
|
|
94
|
+
artifacts).
|
|
95
|
+
|
|
96
|
+
This document will evolve as the native implementation grows. Contributions welcome.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#include <ruby.h>
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace {
|
|
5
|
+
|
|
6
|
+
VALUE build_stub_launch_options(VALUE rb_options) {
|
|
7
|
+
VALUE result = rb_hash_new();
|
|
8
|
+
|
|
9
|
+
VALUE executable_path = rb_str_new_cstr("/usr/local/bin/camoufox");
|
|
10
|
+
rb_hash_aset(result, ID2SYM(rb_intern("executable_path")), executable_path);
|
|
11
|
+
|
|
12
|
+
VALUE args = rb_ary_new();
|
|
13
|
+
rb_hash_aset(result, ID2SYM(rb_intern("args")), args);
|
|
14
|
+
|
|
15
|
+
VALUE env = rb_hash_new();
|
|
16
|
+
rb_hash_aset(env, rb_str_new_cstr("CAMOU_CONFIG_1"), rb_str_new_cstr("{}"));
|
|
17
|
+
rb_hash_aset(result, ID2SYM(rb_intern("env")), env);
|
|
18
|
+
|
|
19
|
+
ID headless_id = rb_intern("headless");
|
|
20
|
+
VALUE headless_key = ID2SYM(headless_id);
|
|
21
|
+
VALUE headless_value = rb_hash_lookup(rb_options, headless_key);
|
|
22
|
+
|
|
23
|
+
if (NIL_P(headless_value)) {
|
|
24
|
+
headless_value = Qfalse;
|
|
25
|
+
} else {
|
|
26
|
+
headless_value = RTEST(headless_value) ? Qtrue : Qfalse;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
rb_hash_aset(result, headless_key, headless_value);
|
|
30
|
+
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
VALUE build_cli_response(const char* command) {
|
|
35
|
+
if (strcmp(command, "path") == 0) {
|
|
36
|
+
return rb_str_new_cstr("/usr/local/share/camoufox\n");
|
|
37
|
+
}
|
|
38
|
+
if (strcmp(command, "version") == 0) {
|
|
39
|
+
return rb_str_new_cstr("Camoufox native stub v0.0.1\n");
|
|
40
|
+
}
|
|
41
|
+
if (strcmp(command, "fetch") == 0) {
|
|
42
|
+
return rb_str_new_cstr("Fetch command is not yet implemented in the native port.\n");
|
|
43
|
+
}
|
|
44
|
+
if (strcmp(command, "remove") == 0) {
|
|
45
|
+
return rb_str_new_cstr("Remove command is not yet implemented in the native port.\n");
|
|
46
|
+
}
|
|
47
|
+
return rb_str_new_cstr("Unknown command.\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
VALUE native_launch_options(VALUE self, VALUE rb_options) {
|
|
51
|
+
return build_stub_launch_options(rb_options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
VALUE native_cli(int argc, VALUE* argv, VALUE self) {
|
|
55
|
+
if (argc < 1) {
|
|
56
|
+
rb_raise(rb_eArgError, "command required");
|
|
57
|
+
}
|
|
58
|
+
VALUE command_val = argv[0];
|
|
59
|
+
Check_Type(command_val, T_STRING);
|
|
60
|
+
const char* command = StringValueCStr(command_val);
|
|
61
|
+
return build_cli_response(command);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} // namespace
|
|
65
|
+
|
|
66
|
+
extern "C" void Init_camoufox_native() {
|
|
67
|
+
VALUE camoufox_module = rb_define_module("CamoufoxNative");
|
|
68
|
+
rb_define_module_function(camoufox_module, "launch_options", RUBY_METHOD_FUNC(native_launch_options), 1);
|
|
69
|
+
rb_define_module_function(camoufox_module, "run_cli", RUBY_METHOD_FUNC(native_cli), -1);
|
|
70
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module CLI
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def run(command, args = [], env: {})
|
|
8
|
+
warn("[camoufox] Native CLI does not yet honour environment overrides") if env && !env.empty?
|
|
9
|
+
NativeBridge.run_cli(command, args)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module Addons
|
|
5
|
+
DefaultAddons = [].freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def default_addons
|
|
10
|
+
DefaultAddons
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def maybe_download_addons(_addons)
|
|
14
|
+
warn("[camoufox] addon management is not yet implemented in the native port")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def confirm_paths(_addons)
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder browserforge dataset for native Camoufox port.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :data_dir, :cache_dir, :node_path, :playwright_driver_dir
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
reset_defaults
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def reset_defaults
|
|
12
|
+
@data_dir = ENV['CAMOUFOX_DATA_DIR']
|
|
13
|
+
@cache_dir = ENV['CAMOUFOX_CACHE_DIR']
|
|
14
|
+
@node_path = ENV['CAMOUFOX_NODE_PATH'] || 'node'
|
|
15
|
+
@playwright_driver_dir = ENV['CAMOUFOX_PLAYWRIGHT_DRIVER_DIR']
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class MissingNativeExtension < Error; end
|
|
7
|
+
|
|
8
|
+
class MissingPlaywrightDriver < Error; end
|
|
9
|
+
|
|
10
|
+
class NodeExecutionFailed < Error
|
|
11
|
+
attr_reader :status
|
|
12
|
+
|
|
13
|
+
def initialize(message, status)
|
|
14
|
+
super(message)
|
|
15
|
+
@status = status
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module Fingerprints
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def generate(_options = {})
|
|
8
|
+
warn("[camoufox] fingerprint generation is not yet implemented in the native port")
|
|
9
|
+
{}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def from_browserforge(_fingerprint, _ff_version)
|
|
13
|
+
warn("[camoufox] BrowserForge integration is not yet implemented")
|
|
14
|
+
{}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/camoufox/ip.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module IP
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def public_ip(_proxy = nil)
|
|
8
|
+
warn("[camoufox] public IP resolution is not yet implemented")
|
|
9
|
+
"0.0.0.0"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def valid_ipv4(_value)
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def valid_ipv6(_value)
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Adapted from the Camoufox Python implementation
|
|
2
|
+
// Launches the Playwright browser server by invoking the undocumented launchServer API.
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
function resolveBrowserServerImpl() {
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const candidate = path.join(cwd, 'lib', 'browserServerImpl.js');
|
|
10
|
+
try {
|
|
11
|
+
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
12
|
+
return require(candidate);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Unable to load Playwright browserServerImpl.js from', candidate);
|
|
15
|
+
console.error('Set CAMOUFOX_PLAYWRIGHT_DRIVER_DIR to the Playwright driver directory.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { BrowserServerLauncherImpl } = resolveBrowserServerImpl();
|
|
21
|
+
|
|
22
|
+
function collectData() {
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
let data = '';
|
|
25
|
+
process.stdin.setEncoding('utf8');
|
|
26
|
+
|
|
27
|
+
process.stdin.on('data', (chunk) => {
|
|
28
|
+
data += chunk;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
process.stdin.on('end', () => {
|
|
32
|
+
const buffer = Buffer.from(data, 'base64');
|
|
33
|
+
resolve(JSON.parse(buffer.toString()));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
collectData()
|
|
39
|
+
.then((options) => {
|
|
40
|
+
if (options.executablePath && !fs.existsSync(options.executablePath)) {
|
|
41
|
+
console.warn(`camoufox: executable ${options.executablePath} not found, falling back to Playwright default`);
|
|
42
|
+
delete options.executablePath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.time('Server launched');
|
|
46
|
+
console.info('Launching server...');
|
|
47
|
+
|
|
48
|
+
const server = new BrowserServerLauncherImpl('firefox');
|
|
49
|
+
|
|
50
|
+
server
|
|
51
|
+
.launchServer(options)
|
|
52
|
+
.then((browserServer) => {
|
|
53
|
+
console.timeEnd('Server launched');
|
|
54
|
+
console.log('Websocket endpoint:\x1b[93m', browserServer.wsEndpoint(), '\x1b[0m');
|
|
55
|
+
process.stdin.resume();
|
|
56
|
+
})
|
|
57
|
+
.catch((error) => {
|
|
58
|
+
console.error('Error launching server:', error.message);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
61
|
+
})
|
|
62
|
+
.catch((error) => {
|
|
63
|
+
console.error('Error collecting data:', error.message);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module Locale
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def handle_locales(_locales, config)
|
|
8
|
+
config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def geoip_allowed?
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def download_mmdb
|
|
16
|
+
warn("[camoufox] GeoIP database download is not yet implemented")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def remove_mmdb
|
|
20
|
+
warn("[camoufox] GeoIP cleanup is not yet implemented")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module NativeBridge
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def ensure_loaded!
|
|
8
|
+
return if defined?(@loaded) && @loaded
|
|
9
|
+
|
|
10
|
+
require 'camoufox_native'
|
|
11
|
+
@loaded = true
|
|
12
|
+
rescue LoadError => e
|
|
13
|
+
raise MissingNativeExtension, "camoufox_native extension is not available: #{e.message}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def launch_options(**kwargs)
|
|
17
|
+
ensure_loaded!
|
|
18
|
+
CamoufoxNative.launch_options(kwargs)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run_cli(command, args = [])
|
|
22
|
+
ensure_loaded!
|
|
23
|
+
CamoufoxNative.run_cli(command.to_s)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def available?
|
|
27
|
+
ensure_loaded!
|
|
28
|
+
true
|
|
29
|
+
rescue MissingNativeExtension
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module Pkgman
|
|
5
|
+
InstallInfo = Struct.new(:path, :version, keyword_init: true)
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def fetch_latest
|
|
10
|
+
warn("[camoufox] binary fetch is not yet implemented in the native port")
|
|
11
|
+
InstallInfo.new(path: "/usr/local/share/camoufox", version: "0.0.0")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def install
|
|
15
|
+
fetch_latest
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def remove
|
|
19
|
+
warn("[camoufox] binary removal is not yet implemented")
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def install_dir
|
|
24
|
+
"/usr/local/share/camoufox"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def version_string
|
|
28
|
+
"0.0.0"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "open3"
|
|
6
|
+
|
|
7
|
+
module Camoufox
|
|
8
|
+
module Server
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def launch(**kwargs)
|
|
12
|
+
launch_config = Utils.launch_options(**kwargs).to_h
|
|
13
|
+
payload = Base64.strict_encode64(JSON.generate(Utils.camelize_hash(launch_config)))
|
|
14
|
+
|
|
15
|
+
driver_dir = Camoufox.configuration.playwright_driver_dir
|
|
16
|
+
raise MissingPlaywrightDriver, missing_driver_message unless driver_dir && Dir.exist?(driver_dir)
|
|
17
|
+
|
|
18
|
+
node_path = Camoufox.configuration.node_path || 'node'
|
|
19
|
+
script_path = File.expand_path("launchServer.js", __dir__)
|
|
20
|
+
|
|
21
|
+
run_node_script(node_path, script_path, driver_dir, payload)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def missing_driver_message
|
|
25
|
+
'Set CAMOUFOX_PLAYWRIGHT_DRIVER_DIR to the Playwright driver directory (contains lib/browserServerImpl.js)'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def run_node_script(node_path, script_path, working_dir, payload)
|
|
29
|
+
env = { 'CAMOUFOX_PLAYWRIGHT_DRIVER_DIR' => working_dir }
|
|
30
|
+
Open3.popen2e(env, node_path, script_path, chdir: working_dir) do |stdin, stdout_err, wait_thr|
|
|
31
|
+
stdin.write(payload)
|
|
32
|
+
stdin.close
|
|
33
|
+
|
|
34
|
+
stdout_err.each { |line| puts line }
|
|
35
|
+
|
|
36
|
+
status = wait_thr.value
|
|
37
|
+
return if status.success?
|
|
38
|
+
|
|
39
|
+
raise NodeExecutionFailed.new("Playwright server exited with status #{status.exitstatus}", status)
|
|
40
|
+
end
|
|
41
|
+
rescue Errno::ENOENT => e
|
|
42
|
+
raise NodeExecutionFailed.new("Failed to execute #{node_path}: #{e.message}", nil)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "base64"
|
|
5
|
+
require "open3"
|
|
6
|
+
|
|
7
|
+
module Camoufox
|
|
8
|
+
module SyncAPI
|
|
9
|
+
class Camoufox
|
|
10
|
+
def self.open(**kwargs)
|
|
11
|
+
browser = new(**kwargs)
|
|
12
|
+
return browser unless block_given?
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
yield browser
|
|
16
|
+
ensure
|
|
17
|
+
browser.close
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(**kwargs)
|
|
22
|
+
@launch_options = Utils.launch_options(**kwargs).to_h
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def new_page
|
|
26
|
+
Page.new(@launch_options)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def close
|
|
30
|
+
# Nothing to cleanup yet – placeholder for future native resources.
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class Page
|
|
36
|
+
attr_reader :title, :content
|
|
37
|
+
|
|
38
|
+
def initialize(launch_options)
|
|
39
|
+
@launch_options = launch_options
|
|
40
|
+
@title = nil
|
|
41
|
+
@content = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def goto(url)
|
|
45
|
+
result = NodeRunner.visit(@launch_options, url)
|
|
46
|
+
@title = result['title']
|
|
47
|
+
@content = result['content']&.to_s
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module NodeRunner
|
|
53
|
+
module_function
|
|
54
|
+
|
|
55
|
+
def visit(launch_options, url)
|
|
56
|
+
node_path = ::Camoufox.configuration.node_path || 'node'
|
|
57
|
+
script_path = File.expand_path('visit.js', __dir__)
|
|
58
|
+
|
|
59
|
+
payload = Base64.strict_encode64(
|
|
60
|
+
JSON.generate(
|
|
61
|
+
options: Utils.camelize_hash(launch_options),
|
|
62
|
+
url: url,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
env = {}
|
|
67
|
+
if (driver_dir = ::Camoufox.configuration.playwright_driver_dir)
|
|
68
|
+
env['NODE_PATH'] = [driver_dir, ENV['NODE_PATH']].compact.join(File::PATH_SEPARATOR)
|
|
69
|
+
env['CAMOUFOX_PLAYWRIGHT_DRIVER_DIR'] = driver_dir
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
stdout, stderr, status = Open3.capture3(env, node_path, script_path, stdin_data: payload)
|
|
73
|
+
|
|
74
|
+
unless status.success?
|
|
75
|
+
message = stderr.empty? ? stdout : stderr
|
|
76
|
+
raise NodeExecutionFailed.new("Playwright visit failed: #{message.strip}", status)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
JSON.parse(stdout)
|
|
80
|
+
rescue Errno::ENOENT => e
|
|
81
|
+
raise NodeExecutionFailed.new("Failed to execute #{node_path}: #{e.message}", nil)
|
|
82
|
+
rescue JSON::ParserError => e
|
|
83
|
+
raise NodeExecutionFailed.new("Invalid response from Playwright visit: #{e.message}", nil)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Camoufox
|
|
4
|
+
module Utils
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def launch_options(**kwargs)
|
|
8
|
+
LaunchOptions.new(NativeBridge.launch_options(**kwargs))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def camel_case(key)
|
|
12
|
+
segments = key.to_s.split('_')
|
|
13
|
+
return key.to_s if segments.length < 2
|
|
14
|
+
|
|
15
|
+
[segments.first, *segments[1..].map { |segment| segment[0].to_s.upcase + segment[1..].to_s }].join
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def camelize_hash(hash)
|
|
19
|
+
camelize(hash)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def camelize(value)
|
|
23
|
+
case value
|
|
24
|
+
when Hash
|
|
25
|
+
value.each_with_object({}) do |(k, v), acc|
|
|
26
|
+
acc[camel_case(k)] = camelize(v)
|
|
27
|
+
end
|
|
28
|
+
when Array
|
|
29
|
+
value.map { |element| camelize(element) }
|
|
30
|
+
else
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class LaunchOptions
|
|
37
|
+
attr_reader :raw
|
|
38
|
+
|
|
39
|
+
def initialize(raw_hash)
|
|
40
|
+
@raw = symbolize_top_level(raw_hash)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_h
|
|
44
|
+
raw.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def symbolize_top_level(hash)
|
|
50
|
+
hash.each_with_object({}) do |(key, value), acc|
|
|
51
|
+
sym_key = key.respond_to?(:to_sym) ? key.to_sym : key
|
|
52
|
+
acc[sym_key] = deep_dup(value)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def deep_dup(value)
|
|
57
|
+
case value
|
|
58
|
+
when Hash
|
|
59
|
+
value.each_with_object({}) do |(k, v), acc|
|
|
60
|
+
acc[k] = deep_dup(v)
|
|
61
|
+
end
|
|
62
|
+
when Array
|
|
63
|
+
value.map { |element| deep_dup(element) }
|
|
64
|
+
else
|
|
65
|
+
value
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
function readStdinAsBase64() {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
process.stdin.setEncoding('utf8');
|
|
8
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
9
|
+
process.stdin.on('end', () => resolve(chunks.join('')));
|
|
10
|
+
process.stdin.on('error', (error) => reject(error));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function loadPlaywright() {
|
|
15
|
+
const override = process.env.CAMOUFOX_PLAYWRIGHT_JS_REQUIRE;
|
|
16
|
+
if (override) {
|
|
17
|
+
return require(override);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
return require('playwright');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// fall through
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const driverDir = process.env.CAMOUFOX_PLAYWRIGHT_DRIVER_DIR;
|
|
27
|
+
if (driverDir) {
|
|
28
|
+
try {
|
|
29
|
+
return require(path.join(driverDir, 'package'));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// fall through
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.error('Unable to require Playwright. Install the `playwright` npm package or set CAMOUFOX_PLAYWRIGHT_JS_REQUIRE.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function main() {
|
|
40
|
+
const payloadB64 = await readStdinAsBase64();
|
|
41
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64').toString());
|
|
42
|
+
const { options, url } = payload;
|
|
43
|
+
|
|
44
|
+
if (options.executablePath && !fs.existsSync(options.executablePath)) {
|
|
45
|
+
console.warn(`camoufox: executable ${options.executablePath} not found, falling back to Playwright default`);
|
|
46
|
+
delete options.executablePath;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const playwright = loadPlaywright();
|
|
50
|
+
const browserType = playwright.firefox;
|
|
51
|
+
if (!browserType) {
|
|
52
|
+
console.error('Playwright module does not expose `firefox`.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const browser = await browserType.launch(options);
|
|
57
|
+
const page = await browser.newPage();
|
|
58
|
+
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
59
|
+
try {
|
|
60
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.warn(`camoufox: waitForLoadState(networkidle) warning: ${error.message || error}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const [title, content] = await Promise.all([
|
|
66
|
+
page.title(),
|
|
67
|
+
page.content(),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
console.log(JSON.stringify({ title, content }));
|
|
71
|
+
await browser.close();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
main().catch((error) => {
|
|
75
|
+
console.error(error.message || error);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Placeholder warnings configuration for the Camoufox native port.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Placeholder WebGL dataset for the Camoufox native port.
|
data/lib/camoufox.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "camoufox/__version__"
|
|
4
|
+
require_relative "camoufox/exceptions"
|
|
5
|
+
require_relative "camoufox/configuration"
|
|
6
|
+
# Core helpers
|
|
7
|
+
require_relative "camoufox/utils"
|
|
8
|
+
require_relative "camoufox/addons"
|
|
9
|
+
require_relative "camoufox/fingerprints"
|
|
10
|
+
require_relative "camoufox/ip"
|
|
11
|
+
require_relative "camoufox/locale"
|
|
12
|
+
require_relative "camoufox/pkgman"
|
|
13
|
+
require_relative "camoufox/sync_api"
|
|
14
|
+
require_relative "camoufox/async_api"
|
|
15
|
+
require_relative "camoufox/server"
|
|
16
|
+
require_relative "camoufox/virtdisplay"
|
|
17
|
+
require_relative "camoufox/warnings"
|
|
18
|
+
require_relative "camoufox/native_bridge"
|
|
19
|
+
require_relative "camoufox/__main__"
|
|
20
|
+
|
|
21
|
+
module Camoufox
|
|
22
|
+
class << self
|
|
23
|
+
def configure
|
|
24
|
+
yield configuration
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def configuration
|
|
28
|
+
@configuration ||= Configuration.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def reset_configuration!
|
|
32
|
+
@configuration = Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def launch_options(**kwargs)
|
|
36
|
+
Utils.launch_options(**kwargs)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fetch(update_browserforge: false, env: {})
|
|
40
|
+
CLI.run("fetch", update_browserforge ? ["--browserforge"] : [], env: env)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def remove(env: {})
|
|
44
|
+
CLI.run("remove", [], env: env)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def path(env: {})
|
|
48
|
+
CLI.run("path", [], env: env).strip
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def version(env: {})
|
|
52
|
+
CLI.run("version", [], env: env)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: camoufox
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Camoufox contributors
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2025-11-03 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rspec
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.12'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.12'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rubocop
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.60'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.60'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake-compiler
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.2'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.2'
|
|
68
|
+
description: Reimplements the pythonlib/camoufox package structure in Ruby with a
|
|
69
|
+
native extension stub while the full feature set is ported over.
|
|
70
|
+
email:
|
|
71
|
+
- opensource@camoufox.dev
|
|
72
|
+
executables:
|
|
73
|
+
- camoufox
|
|
74
|
+
extensions:
|
|
75
|
+
- ext/camoufox_native/extconf.rb
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- CHANGELOG.md
|
|
79
|
+
- Gemfile
|
|
80
|
+
- LICENSE
|
|
81
|
+
- README.md
|
|
82
|
+
- bin/camoufox
|
|
83
|
+
- docs/native_port.md
|
|
84
|
+
- ext/camoufox_native/camoufox_native.cpp
|
|
85
|
+
- ext/camoufox_native/extconf.rb
|
|
86
|
+
- lib/camoufox.rb
|
|
87
|
+
- lib/camoufox/__main__.rb
|
|
88
|
+
- lib/camoufox/__version__.rb
|
|
89
|
+
- lib/camoufox/addons.rb
|
|
90
|
+
- lib/camoufox/async_api.rb
|
|
91
|
+
- lib/camoufox/browserforge.yml
|
|
92
|
+
- lib/camoufox/configuration.rb
|
|
93
|
+
- lib/camoufox/exceptions.rb
|
|
94
|
+
- lib/camoufox/fingerprints.rb
|
|
95
|
+
- lib/camoufox/fonts.json
|
|
96
|
+
- lib/camoufox/ip.rb
|
|
97
|
+
- lib/camoufox/launchServer.js
|
|
98
|
+
- lib/camoufox/locale.rb
|
|
99
|
+
- lib/camoufox/native_bridge.rb
|
|
100
|
+
- lib/camoufox/pkgman.rb
|
|
101
|
+
- lib/camoufox/server.rb
|
|
102
|
+
- lib/camoufox/sync_api.rb
|
|
103
|
+
- lib/camoufox/utils.rb
|
|
104
|
+
- lib/camoufox/virtdisplay.rb
|
|
105
|
+
- lib/camoufox/visit.js
|
|
106
|
+
- lib/camoufox/warnings.rb
|
|
107
|
+
- lib/camoufox/warnings.yml
|
|
108
|
+
- lib/camoufox/webgl/README.md
|
|
109
|
+
- lib/camoufox_native.bundle
|
|
110
|
+
homepage: https://github.com/daijro/camoufox
|
|
111
|
+
licenses:
|
|
112
|
+
- MIT
|
|
113
|
+
metadata:
|
|
114
|
+
homepage_uri: https://github.com/daijro/camoufox
|
|
115
|
+
source_code_uri: https://github.com/daijro/camoufox-ruby
|
|
116
|
+
changelog_uri: https://github.com/daijro/camoufox-ruby/blob/main/CHANGELOG.md
|
|
117
|
+
rdoc_options: []
|
|
118
|
+
require_paths:
|
|
119
|
+
- lib
|
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
|
+
requirements:
|
|
122
|
+
- - ">="
|
|
123
|
+
- !ruby/object:Gem::Version
|
|
124
|
+
version: '3.0'
|
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '0'
|
|
130
|
+
requirements: []
|
|
131
|
+
rubygems_version: 3.6.2
|
|
132
|
+
specification_version: 4
|
|
133
|
+
summary: Native rewrite of the Camoufox stealth Firefox toolkit
|
|
134
|
+
test_files: []
|