Skip to content

Commit 7893417

Browse files
fix: improve timeout handling, abort behavior, and project linting (#6)
* fix: improve timeout handling, abort behavior, and project linting * refactor: move native runtime bindings into src/native * refactor: remove dead code
1 parent 8ddc4c5 commit 7893417

39 files changed

Lines changed: 583 additions & 247 deletions

.github/workflows/test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ jobs:
7979
- name: Install dependencies
8080
run: npm install
8181

82+
- name: Install Rust linting components
83+
uses: dtolnay/rust-toolchain@stable
84+
with:
85+
components: rustfmt, clippy
86+
8287
- name: Check TypeScript types
8388
run: npx tsc --noEmit
8489

@@ -87,3 +92,9 @@ jobs:
8792

8893
- name: Check code formatting
8994
run: npm run format:check
95+
96+
- name: Check Rust formatting
97+
run: npm run format:rust:check
98+
99+
- name: Run clippy
100+
run: npm run lint:rust

docs/BUILD.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ npm run build:rust
147147
npm install
148148

149149
# Clean and rebuild
150-
rm -rf rust/target
150+
npx rimraf rust/target
151151
npm run build:rust
152152
```
153153

package-lock.json

Lines changed: 83 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,21 @@
1717
"scripts": {
1818
"build": "npm run build:rust && npm run build:ts",
1919
"build:rust": "napi build --platform --release --cargo-cwd rust rust",
20-
"build:ts": "node ./scripts/generate-browser-profiles.mjs && tsc && node ./scripts/postbuild.mjs",
20+
"build:ts": "rimraf dist && node ./scripts/generate-browser-profiles.mjs && tsc && node ./scripts/postbuild.mjs",
2121
"deps:wreq": "node ./scripts/update-wreq-upstream.mjs",
2222
"prepare": "node ./scripts/install-git-hooks.mjs",
2323
"prepare:publish:main": "node ./scripts/prepare-main-package.mjs",
2424
"prepare:publish:platform": "node ./scripts/prepare-platform-package.mjs",
2525
"artifacts": "napi artifacts",
26-
"clean": "rm -rf dist rust/target rust/*.node",
26+
"clean": "rimraf dist rust/target && rimraf --glob \"rust/*.node\"",
2727
"test": "npm run build && node --test dist/test/node-wreq.spec.js",
28-
"lint": "oxlint --deny-warnings src",
29-
"lint:fix": "oxlint --fix src",
30-
"format": "oxfmt --write \"src/**/*.ts\"",
31-
"format:check": "oxfmt --check \"src/**/*.ts\""
28+
"lint": "oxlint --deny-warnings src scripts",
29+
"lint:fix": "oxlint --fix src scripts",
30+
"lint:rust": "cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings",
31+
"format": "oxfmt --write src scripts",
32+
"format:check": "oxfmt --check src scripts",
33+
"format:rust": "cargo fmt --manifest-path rust/Cargo.toml",
34+
"format:rust:check": "cargo fmt --manifest-path rust/Cargo.toml --check"
3235
},
3336
"keywords": [
3437
"anti-bot",
@@ -44,7 +47,7 @@
4447
"scrapper",
4548
"fingerprint",
4649
"tls",
47-
"tls-fingerprint",
50+
"tls-fingerprint",
4851
"http2",
4952
"fetch",
5053
"websocket",
@@ -72,6 +75,7 @@
7275
"@types/ws": "^8.18.1",
7376
"oxfmt": "^0.45.0",
7477
"oxlint": "^1.60.0",
78+
"rimraf": "^6.1.3",
7579
"typescript": "^5.0.0",
7680
"ws": "^8.20.0"
7781
},

rust/src/emulation/builders.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ fn build_settings_order(settings_order: Vec<String>) -> Result<SettingsOrder> {
264264

265265
for setting in settings_order {
266266
let setting_id = parse_http2_setting_id(&setting)?;
267-
if !seen.insert(setting_id.clone()) {
267+
if !seen.insert(setting_id) {
268268
bail!("Duplicate emulation http2Options.settingsOrder entry: {setting}");
269269
}
270270
builder = builder.push(setting_id);

rust/src/napi/body.rs

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::store::body_store::{cancel_body, read_body_all, read_body_chunk};
1+
use crate::store::body_store::{cancel_body, read_body_chunk};
22
use neon::prelude::*;
33
use neon::types::JsBuffer;
44

@@ -32,32 +32,13 @@ fn read_body_chunk_js(mut cx: FunctionContext) -> JsResult<JsPromise> {
3232
Ok(promise)
3333
}
3434

35-
fn read_body_all_js(mut cx: FunctionContext) -> JsResult<JsPromise> {
36-
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;
37-
38-
let channel = cx.channel();
39-
let (deferred, promise) = cx.promise();
40-
41-
std::thread::spawn(move || {
42-
let result = read_body_all(handle);
43-
44-
deferred.settle_with(&channel, move |mut cx| match result {
45-
Ok(bytes) => JsBuffer::from_slice(&mut cx, &bytes),
46-
Err(error) => cx.throw_error(format!("{:#}", error)),
47-
});
48-
});
49-
50-
Ok(promise)
51-
}
52-
5335
fn cancel_body_js(mut cx: FunctionContext) -> JsResult<JsBoolean> {
5436
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;
5537
Ok(cx.boolean(cancel_body(handle)))
5638
}
5739

5840
pub fn register(cx: &mut ModuleContext) -> NeonResult<()> {
5941
cx.export_function("readBodyChunk", read_body_chunk_js)?;
60-
cx.export_function("readBodyAll", read_body_all_js)?;
6142
cx.export_function("cancelBody", cancel_body_js)?;
6243
Ok(())
6344
}

rust/src/napi/convert.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@ use crate::transport::types::{
55
WebSocketConnectOptions, WebSocketConnection,
66
};
77
use neon::prelude::*;
8-
use neon::types::JsBuffer;
98
use neon::types::buffer::TypedArray;
9+
use neon::types::JsBuffer;
10+
11+
fn js_value_to_timeout_ms(cx: &mut FunctionContext, value: Handle<JsValue>) -> NeonResult<u64> {
12+
let value = value.downcast::<JsNumber, _>(cx).or_throw(cx)?.value(cx);
13+
14+
if !value.is_finite() || value < 0.0 {
15+
return cx.throw_type_error("timeout must be a finite non-negative number");
16+
}
17+
18+
Ok(if value == 0.0 { 0 } else { value.ceil() as u64 })
19+
}
1020

1121
pub(crate) fn js_value_to_string_array(
1222
cx: &mut FunctionContext,
@@ -101,12 +111,16 @@ pub(crate) fn js_object_to_request_options(
101111
.map(|v| v.value(cx))
102112
.unwrap_or(false);
103113
let dns = js_object_to_dns_options(cx, obj)?;
104-
105114
let timeout = obj
106115
.get_opt(cx, "timeout")?
107-
.and_then(|v: Handle<JsValue>| v.downcast::<JsNumber, _>(cx).ok())
108-
.map(|v| v.value(cx) as u64)
109-
.unwrap_or(30000);
116+
.map(|v| js_value_to_timeout_ms(cx, v))
117+
.transpose()?;
118+
119+
let timeout = match timeout {
120+
Some(0) => None,
121+
Some(timeout) => Some(timeout),
122+
None => Some(30000),
123+
};
110124

111125
let disable_default_headers = obj
112126
.get_opt(cx, "disableDefaultHeaders")?
@@ -186,12 +200,16 @@ pub(crate) fn js_object_to_websocket_options(
186200
.map(|v| v.value(cx))
187201
.unwrap_or(false);
188202
let dns = js_object_to_dns_options(cx, obj)?;
189-
190203
let timeout = obj
191204
.get_opt(cx, "timeout")?
192-
.and_then(|v: Handle<JsValue>| v.downcast::<JsNumber, _>(cx).ok())
193-
.map(|v| v.value(cx) as u64)
194-
.unwrap_or(30000);
205+
.map(|v| js_value_to_timeout_ms(cx, v))
206+
.transpose()?;
207+
208+
let timeout = match timeout {
209+
Some(0) => None,
210+
Some(timeout) => Some(timeout),
211+
None => Some(30000),
212+
};
195213

196214
let disable_default_headers = obj
197215
.get_opt(cx, "disableDefaultHeaders")?

rust/src/napi/request.rs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,53 @@
11
use crate::napi::convert::{js_object_to_request_options, response_to_js_object};
2-
use crate::transport::execute_request;
2+
use crate::store::request_store::{
3+
cancel_request as cancel_request_handle, insert_request, remove_request,
4+
};
5+
use crate::store::runtime::runtime;
6+
use crate::transport::make_request;
37
use neon::prelude::*;
48

5-
fn request(mut cx: FunctionContext) -> JsResult<JsPromise> {
9+
fn request(mut cx: FunctionContext) -> JsResult<JsObject> {
610
let options_obj = cx.argument::<JsObject>(0)?;
711
let options = js_object_to_request_options(&mut cx, options_obj)?;
812

913
let channel = cx.channel();
1014
let (deferred, promise) = cx.promise();
15+
let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
16+
let handle = insert_request(cancel_tx);
1117

1218
std::thread::spawn(move || {
13-
let result = execute_request(options);
19+
let result = runtime().block_on(async move {
20+
tokio::select! {
21+
result = make_request(options) => result,
22+
_ = cancel_rx => Err(anyhow::anyhow!("Request aborted")),
23+
}
24+
});
25+
26+
remove_request(handle);
1427

1528
deferred.settle_with(&channel, move |mut cx| match result {
1629
Ok(response) => response_to_js_object(&mut cx, response),
1730
Err(error) => cx.throw_error(format!("{:#}", error)),
1831
});
1932
});
2033

21-
Ok(promise)
34+
let result = JsObject::new(&mut cx);
35+
let handle_value = cx.number(handle as f64);
36+
37+
result.set(&mut cx, "handle", handle_value)?;
38+
result.set(&mut cx, "promise", promise)?;
39+
40+
Ok(result)
41+
}
42+
43+
fn cancel_request(mut cx: FunctionContext) -> JsResult<JsBoolean> {
44+
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;
45+
46+
Ok(cx.boolean(cancel_request_handle(handle)))
2247
}
2348

2449
pub fn register(cx: &mut ModuleContext) -> NeonResult<()> {
2550
cx.export_function("request", request)?;
51+
cx.export_function("cancelRequest", cancel_request)?;
2652
Ok(())
2753
}

0 commit comments

Comments
 (0)