@rxflex/rom 0.0.5 → 0.0.6
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/Cargo.toml +3 -1
- package/README.md +5 -0
- package/native/lib.rs +87 -1
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/rom_node_native.node +0 -0
- package/prebuilds/darwin-x64/rom_node_native.node +0 -0
- package/prebuilds/linux-x64-gnu/rom_node_native.node +0 -0
- package/prebuilds/win32-x64-msvc/rom_node_native.node +0 -0
- package/scripts/smoke.mjs +7 -1
- package/src/index.d.ts +4 -0
- package/src/index.js +86 -7
- package/src/native.js +2 -2
package/Cargo.toml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "rom-node-native"
|
|
3
3
|
description = "Native napi-rs bridge for the ROM Node.js bindings"
|
|
4
|
-
version = "0.0.
|
|
4
|
+
version = "0.0.6"
|
|
5
5
|
edition = "2024"
|
|
6
6
|
homepage = "https://github.com/Rxflex/rom"
|
|
7
7
|
license = "MIT"
|
|
@@ -16,6 +16,8 @@ crate-type = ["cdylib"]
|
|
|
16
16
|
napi = "3"
|
|
17
17
|
napi-derive = "3"
|
|
18
18
|
rom-runtime = { path = "../../crates/rom-runtime" }
|
|
19
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
20
|
+
serde_json = "1.0"
|
|
19
21
|
|
|
20
22
|
[build-dependencies]
|
|
21
23
|
napi-build = "2"
|
package/README.md
CHANGED
|
@@ -27,6 +27,7 @@ import { RomRuntime, hasNativeBinding } from "@rxflex/rom";
|
|
|
27
27
|
|
|
28
28
|
const runtime = new RomRuntime({
|
|
29
29
|
href: "https://example.test/",
|
|
30
|
+
referrer: "https://referrer.example/",
|
|
30
31
|
cors_enabled: false,
|
|
31
32
|
proxy_url: process.env.ROM_PROXY_URL ?? null,
|
|
32
33
|
});
|
|
@@ -37,10 +38,14 @@ const snapshot = await runtime.surfaceSnapshot();
|
|
|
37
38
|
console.log("native:", hasNativeBinding());
|
|
38
39
|
console.log(href);
|
|
39
40
|
console.log(snapshot.fetch);
|
|
41
|
+
|
|
42
|
+
await runtime.evalAsync("(async () => { globalThis.__romValue = 42; return 'ok'; })()");
|
|
43
|
+
console.log(await runtime.evalAsync("(async () => String(globalThis.__romValue))()"));
|
|
40
44
|
```
|
|
41
45
|
|
|
42
46
|
Config keys use the Rust runtime field names, so use snake_case such as `cors_enabled` and `proxy_url`.
|
|
43
47
|
`cors_enabled` is `false` by default.
|
|
48
|
+
When the native addon is loaded, one `RomRuntime` instance keeps JS globals alive across multiple `eval()` and `evalAsync()` calls.
|
|
44
49
|
|
|
45
50
|
## Optional native build
|
|
46
51
|
|
package/native/lib.rs
CHANGED
|
@@ -1,7 +1,93 @@
|
|
|
1
|
-
use napi::Result;
|
|
1
|
+
use napi::{Error, Result};
|
|
2
2
|
use napi_derive::napi;
|
|
3
|
+
use rom_runtime::{RomRuntime, RuntimeConfig};
|
|
4
|
+
use serde::Serialize;
|
|
5
|
+
use serde_json::Value;
|
|
6
|
+
|
|
7
|
+
fn parse_config(config_json: String) -> Result<RuntimeConfig> {
|
|
8
|
+
serde_json::from_str(&config_json)
|
|
9
|
+
.map_err(|error| Error::from_reason(format!("Invalid ROM config JSON: {error}")))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fn to_json<T>(value: &T) -> Result<String>
|
|
13
|
+
where
|
|
14
|
+
T: Serialize,
|
|
15
|
+
{
|
|
16
|
+
serde_json::to_string(value)
|
|
17
|
+
.map_err(|error| Error::from_reason(format!("Failed to serialize ROM value: {error}")))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
fn map_runtime_error<T>(result: rom_runtime::Result<T>) -> Result<T> {
|
|
21
|
+
result.map_err(|error| Error::from_reason(error.to_string()))
|
|
22
|
+
}
|
|
3
23
|
|
|
4
24
|
#[napi]
|
|
5
25
|
pub fn execute_bridge(request_json: String) -> Result<String> {
|
|
6
26
|
Ok(rom_runtime::execute_bridge_request_json(&request_json))
|
|
7
27
|
}
|
|
28
|
+
|
|
29
|
+
#[napi]
|
|
30
|
+
pub struct NativeRomRuntime {
|
|
31
|
+
runtime: RomRuntime,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
#[napi]
|
|
35
|
+
impl NativeRomRuntime {
|
|
36
|
+
#[napi(constructor)]
|
|
37
|
+
pub fn new(config_json: String) -> Result<Self> {
|
|
38
|
+
Ok(Self {
|
|
39
|
+
runtime: map_runtime_error(RomRuntime::new(parse_config(config_json)?))?,
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[napi(js_name = "eval")]
|
|
44
|
+
pub fn eval(&self, script: String) -> Result<String> {
|
|
45
|
+
map_runtime_error(self.runtime.eval_as_string(&script))
|
|
46
|
+
.map(|value| value.to_owned())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[napi(js_name = "evalAsync")]
|
|
50
|
+
pub fn eval_async(&self, script: String) -> Result<String> {
|
|
51
|
+
map_runtime_error(self.runtime.eval_async_as_string(&script))
|
|
52
|
+
.map(|value| value.to_owned())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[napi(js_name = "surfaceSnapshotJson")]
|
|
56
|
+
pub fn surface_snapshot_json(&self) -> Result<String> {
|
|
57
|
+
to_json(&map_runtime_error(self.runtime.surface_snapshot())?)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[napi(js_name = "fingerprintProbeJson")]
|
|
61
|
+
pub fn fingerprint_probe_json(&self) -> Result<String> {
|
|
62
|
+
to_json(&map_runtime_error(self.runtime.fingerprint_probe())?)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[napi(js_name = "fingerprintJsHarnessJson")]
|
|
66
|
+
pub fn fingerprint_js_harness_json(&self) -> Result<String> {
|
|
67
|
+
to_json(&map_runtime_error(self.runtime.run_fingerprintjs_harness())?)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[napi(js_name = "fingerprintJsVersion")]
|
|
71
|
+
pub fn fingerprint_js_version(&self) -> Result<String> {
|
|
72
|
+
Ok(self.runtime.fingerprintjs_version().to_owned())
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[napi(js_name = "exportCookieStore")]
|
|
76
|
+
pub fn export_cookie_store(&self) -> Result<String> {
|
|
77
|
+
map_runtime_error(self.runtime.export_cookie_store())
|
|
78
|
+
.map(|value| value.to_owned())
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[napi(js_name = "evalJson")]
|
|
82
|
+
pub fn eval_json(&self, script: String, asynchronous: bool) -> Result<String> {
|
|
83
|
+
let value = if asynchronous {
|
|
84
|
+
map_runtime_error(self.runtime.eval_async_as_string(&script))?
|
|
85
|
+
} else {
|
|
86
|
+
map_runtime_error(self.runtime.eval_as_string(&script))?
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let parsed: Value = serde_json::from_str(&value)
|
|
90
|
+
.map_err(|error| Error::from_reason(format!("ROM eval did not return JSON: {error}")))?;
|
|
91
|
+
to_json(&parsed)
|
|
92
|
+
}
|
|
93
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/scripts/smoke.mjs
CHANGED
|
@@ -3,6 +3,8 @@ import { RomRuntime, hasNativeBinding } from "../src/index.js";
|
|
|
3
3
|
async function main() {
|
|
4
4
|
const runtime = new RomRuntime({ href: "https://example.test/" });
|
|
5
5
|
const href = await runtime.evalAsync("(async () => location.href)()");
|
|
6
|
+
await runtime.evalAsync("(async () => { globalThis.__romSmokeValue = 42; return 'ok'; })()");
|
|
7
|
+
const persisted = await runtime.evalAsync("(async () => String(globalThis.__romSmokeValue))()");
|
|
6
8
|
const snapshot = await runtime.surfaceSnapshot();
|
|
7
9
|
|
|
8
10
|
if (!hasNativeBinding()) {
|
|
@@ -17,7 +19,11 @@ async function main() {
|
|
|
17
19
|
throw new Error("Surface snapshot did not expose window.");
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
if (persisted !== "42") {
|
|
23
|
+
throw new Error(`Expected persisted global state, got: ${persisted}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(JSON.stringify({ native: true, href, persisted }));
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
main().catch((error) => {
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export interface RuntimeConfig {
|
|
2
2
|
href?: string;
|
|
3
|
+
referrer?: string;
|
|
3
4
|
user_agent?: string;
|
|
4
5
|
app_name?: string;
|
|
5
6
|
platform?: string;
|
|
@@ -8,6 +9,9 @@ export interface RuntimeConfig {
|
|
|
8
9
|
hardware_concurrency?: number;
|
|
9
10
|
device_memory?: number;
|
|
10
11
|
webdriver?: boolean;
|
|
12
|
+
cors_enabled?: boolean;
|
|
13
|
+
proxy_url?: string | null;
|
|
14
|
+
cookie_store?: string | null;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export declare class RomRuntime {
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
8
|
const repoRoot = path.resolve(__dirname, "..", "..", "..");
|
|
9
9
|
const nativeBridge = loadNativeBridge();
|
|
10
|
+
const NativeRomRuntime = nativeBridge?.NativeRomRuntime ?? null;
|
|
10
11
|
|
|
11
12
|
function resolveBridgeCommand() {
|
|
12
13
|
if (process.env.ROM_BRIDGE_BIN) {
|
|
@@ -44,7 +45,10 @@ function parseBridgeResponse(stdout, stderr, error) {
|
|
|
44
45
|
throw new Error(response.error || error?.message || "ROM bridge command failed.");
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
return
|
|
48
|
+
return {
|
|
49
|
+
result: response.result,
|
|
50
|
+
state: response.state ?? null,
|
|
51
|
+
};
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
function runNativeBridge(command, payload) {
|
|
@@ -85,17 +89,92 @@ function runBridge(command, payload) {
|
|
|
85
89
|
return runNativeBridge(command, payload) ?? runCliBridge(command, payload);
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
function applyBridgeState(targetConfig, state) {
|
|
93
|
+
if (!state || typeof state.cookie_store !== "string") {
|
|
94
|
+
return targetConfig;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
...targetConfig,
|
|
99
|
+
cookie_store: state.cookie_store,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
88
103
|
export class RomRuntime {
|
|
104
|
+
#nativeRuntime = null;
|
|
105
|
+
|
|
89
106
|
constructor(config = {}) {
|
|
90
107
|
this.config = config;
|
|
108
|
+
if (typeof NativeRomRuntime === "function") {
|
|
109
|
+
this.#nativeRuntime = new NativeRomRuntime(JSON.stringify(this.config));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#applyCookieStore(cookieStore) {
|
|
114
|
+
if (typeof cookieStore !== "string") {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.config = {
|
|
119
|
+
...this.config,
|
|
120
|
+
cookie_store: cookieStore,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
#syncNativeState() {
|
|
125
|
+
if (!this.#nativeRuntime || typeof this.#nativeRuntime.exportCookieStore !== "function") {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
this.#applyCookieStore(this.#nativeRuntime.exportCookieStore());
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async #runNative(method, ...args) {
|
|
133
|
+
if (!this.#nativeRuntime || typeof this.#nativeRuntime[method] !== "function") {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await Promise.resolve(this.#nativeRuntime[method](...args));
|
|
138
|
+
this.#syncNativeState();
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async #run(command, payload = {}) {
|
|
143
|
+
if (this.#nativeRuntime) {
|
|
144
|
+
if (command === "eval") {
|
|
145
|
+
return this.#runNative("eval", payload.script);
|
|
146
|
+
}
|
|
147
|
+
if (command === "eval-async") {
|
|
148
|
+
return this.#runNative("evalAsync", payload.script);
|
|
149
|
+
}
|
|
150
|
+
if (command === "surface-snapshot") {
|
|
151
|
+
return JSON.parse(await this.#runNative("surfaceSnapshotJson"));
|
|
152
|
+
}
|
|
153
|
+
if (command === "fingerprint-probe") {
|
|
154
|
+
return JSON.parse(await this.#runNative("fingerprintProbeJson"));
|
|
155
|
+
}
|
|
156
|
+
if (command === "fingerprint-js-harness") {
|
|
157
|
+
return JSON.parse(await this.#runNative("fingerprintJsHarnessJson"));
|
|
158
|
+
}
|
|
159
|
+
if (command === "fingerprint-js-version") {
|
|
160
|
+
return this.#runNative("fingerprintJsVersion");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const response = await runBridge(command, {
|
|
165
|
+
config: this.config,
|
|
166
|
+
...payload,
|
|
167
|
+
});
|
|
168
|
+
this.config = applyBridgeState(this.config, response.state);
|
|
169
|
+
return response.result;
|
|
91
170
|
}
|
|
92
171
|
|
|
93
172
|
eval(script) {
|
|
94
|
-
return
|
|
173
|
+
return this.#run("eval", { script });
|
|
95
174
|
}
|
|
96
175
|
|
|
97
176
|
evalAsync(script) {
|
|
98
|
-
return
|
|
177
|
+
return this.#run("eval-async", { script });
|
|
99
178
|
}
|
|
100
179
|
|
|
101
180
|
async evalJson(script, { async = true } = {}) {
|
|
@@ -104,19 +183,19 @@ export class RomRuntime {
|
|
|
104
183
|
}
|
|
105
184
|
|
|
106
185
|
surfaceSnapshot() {
|
|
107
|
-
return
|
|
186
|
+
return this.#run("surface-snapshot");
|
|
108
187
|
}
|
|
109
188
|
|
|
110
189
|
fingerprintProbe() {
|
|
111
|
-
return
|
|
190
|
+
return this.#run("fingerprint-probe");
|
|
112
191
|
}
|
|
113
192
|
|
|
114
193
|
runFingerprintJsHarness() {
|
|
115
|
-
return
|
|
194
|
+
return this.#run("fingerprint-js-harness");
|
|
116
195
|
}
|
|
117
196
|
|
|
118
197
|
fingerprintJsVersion() {
|
|
119
|
-
return
|
|
198
|
+
return this.#run("fingerprint-js-version");
|
|
120
199
|
}
|
|
121
200
|
}
|
|
122
201
|
|
package/src/native.js
CHANGED
|
@@ -16,13 +16,13 @@ function candidatePaths() {
|
|
|
16
16
|
candidates.push(process.env.ROM_NATIVE_NODE_BINDING);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
candidates.push(path.join(packageRoot, "rom_node_native.node"));
|
|
20
|
-
|
|
21
19
|
const prebuildId = detectNativePrebuildId();
|
|
22
20
|
if (prebuildId !== null) {
|
|
23
21
|
candidates.push(path.join(packageRoot, "prebuilds", prebuildId, "rom_node_native.node"));
|
|
24
22
|
}
|
|
25
23
|
|
|
24
|
+
candidates.push(path.join(packageRoot, "rom_node_native.node"));
|
|
25
|
+
|
|
26
26
|
return candidates;
|
|
27
27
|
}
|
|
28
28
|
|