Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ jobs:
- name: Install dependencies
run: npm install

- name: Install Rust linting components
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Check TypeScript types
run: npx tsc --noEmit

Expand All @@ -87,3 +92,9 @@ jobs:

- name: Check code formatting
run: npm run format:check

- name: Check Rust formatting
run: npm run format:rust:check

- name: Run clippy
run: npm run lint:rust
2 changes: 1 addition & 1 deletion docs/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ npm run build:rust
npm install

# Clean and rebuild
rm -rf rust/target
npx rimraf rust/target
npm run build:rust
```

Expand Down
86 changes: 83 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 11 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@
"scripts": {
"build": "npm run build:rust && npm run build:ts",
"build:rust": "napi build --platform --release --cargo-cwd rust rust",
"build:ts": "node ./scripts/generate-browser-profiles.mjs && tsc && node ./scripts/postbuild.mjs",
"build:ts": "rimraf dist && node ./scripts/generate-browser-profiles.mjs && tsc && node ./scripts/postbuild.mjs",
"deps:wreq": "node ./scripts/update-wreq-upstream.mjs",
"prepare": "node ./scripts/install-git-hooks.mjs",
"prepare:publish:main": "node ./scripts/prepare-main-package.mjs",
"prepare:publish:platform": "node ./scripts/prepare-platform-package.mjs",
"artifacts": "napi artifacts",
"clean": "rm -rf dist rust/target rust/*.node",
"clean": "rimraf dist rust/target && rimraf --glob \"rust/*.node\"",
"test": "npm run build && node --test dist/test/node-wreq.spec.js",
"lint": "oxlint --deny-warnings src",
"lint:fix": "oxlint --fix src",
"format": "oxfmt --write \"src/**/*.ts\"",
"format:check": "oxfmt --check \"src/**/*.ts\""
"lint": "oxlint --deny-warnings src scripts",
"lint:fix": "oxlint --fix src scripts",
"lint:rust": "cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings",
"format": "oxfmt --write src scripts",
"format:check": "oxfmt --check src scripts",
"format:rust": "cargo fmt --manifest-path rust/Cargo.toml",
"format:rust:check": "cargo fmt --manifest-path rust/Cargo.toml --check"
},
"keywords": [
"anti-bot",
Expand All @@ -44,7 +47,7 @@
"scrapper",
"fingerprint",
"tls",
"tls-fingerprint",
"tls-fingerprint",
"http2",
"fetch",
"websocket",
Expand Down Expand Up @@ -72,6 +75,7 @@
"@types/ws": "^8.18.1",
"oxfmt": "^0.45.0",
"oxlint": "^1.60.0",
"rimraf": "^6.1.3",
"typescript": "^5.0.0",
"ws": "^8.20.0"
},
Expand Down
2 changes: 1 addition & 1 deletion rust/src/emulation/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ fn build_settings_order(settings_order: Vec<String>) -> Result<SettingsOrder> {

for setting in settings_order {
let setting_id = parse_http2_setting_id(&setting)?;
if !seen.insert(setting_id.clone()) {
if !seen.insert(setting_id) {
bail!("Duplicate emulation http2Options.settingsOrder entry: {setting}");
}
builder = builder.push(setting_id);
Expand Down
21 changes: 1 addition & 20 deletions rust/src/napi/body.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::store::body_store::{cancel_body, read_body_all, read_body_chunk};
use crate::store::body_store::{cancel_body, read_body_chunk};
use neon::prelude::*;
use neon::types::JsBuffer;

Expand Down Expand Up @@ -32,32 +32,13 @@ fn read_body_chunk_js(mut cx: FunctionContext) -> JsResult<JsPromise> {
Ok(promise)
}

fn read_body_all_js(mut cx: FunctionContext) -> JsResult<JsPromise> {
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;

let channel = cx.channel();
let (deferred, promise) = cx.promise();

std::thread::spawn(move || {
let result = read_body_all(handle);

deferred.settle_with(&channel, move |mut cx| match result {
Ok(bytes) => JsBuffer::from_slice(&mut cx, &bytes),
Err(error) => cx.throw_error(format!("{:#}", error)),
});
});

Ok(promise)
}

fn cancel_body_js(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;
Ok(cx.boolean(cancel_body(handle)))
}

pub fn register(cx: &mut ModuleContext) -> NeonResult<()> {
cx.export_function("readBodyChunk", read_body_chunk_js)?;
cx.export_function("readBodyAll", read_body_all_js)?;
cx.export_function("cancelBody", cancel_body_js)?;
Ok(())
}
36 changes: 27 additions & 9 deletions rust/src/napi/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,18 @@ use crate::transport::types::{
WebSocketConnectOptions, WebSocketConnection,
};
use neon::prelude::*;
use neon::types::JsBuffer;
use neon::types::buffer::TypedArray;
use neon::types::JsBuffer;

fn js_value_to_timeout_ms(cx: &mut FunctionContext, value: Handle<JsValue>) -> NeonResult<u64> {
let value = value.downcast::<JsNumber, _>(cx).or_throw(cx)?.value(cx);

if !value.is_finite() || value < 0.0 {
return cx.throw_type_error("timeout must be a finite non-negative number");
}

Ok(if value == 0.0 { 0 } else { value.ceil() as u64 })
}

pub(crate) fn js_value_to_string_array(
cx: &mut FunctionContext,
Expand Down Expand Up @@ -101,12 +111,16 @@ pub(crate) fn js_object_to_request_options(
.map(|v| v.value(cx))
.unwrap_or(false);
let dns = js_object_to_dns_options(cx, obj)?;

let timeout = obj
.get_opt(cx, "timeout")?
.and_then(|v: Handle<JsValue>| v.downcast::<JsNumber, _>(cx).ok())
.map(|v| v.value(cx) as u64)
.unwrap_or(30000);
.map(|v| js_value_to_timeout_ms(cx, v))
.transpose()?;

let timeout = match timeout {
Some(0) => None,
Some(timeout) => Some(timeout),
None => Some(30000),
};

let disable_default_headers = obj
.get_opt(cx, "disableDefaultHeaders")?
Expand Down Expand Up @@ -186,12 +200,16 @@ pub(crate) fn js_object_to_websocket_options(
.map(|v| v.value(cx))
.unwrap_or(false);
let dns = js_object_to_dns_options(cx, obj)?;

let timeout = obj
.get_opt(cx, "timeout")?
.and_then(|v: Handle<JsValue>| v.downcast::<JsNumber, _>(cx).ok())
.map(|v| v.value(cx) as u64)
.unwrap_or(30000);
.map(|v| js_value_to_timeout_ms(cx, v))
.transpose()?;

let timeout = match timeout {
Some(0) => None,
Some(timeout) => Some(timeout),
None => Some(30000),
};

let disable_default_headers = obj
.get_opt(cx, "disableDefaultHeaders")?
Expand Down
34 changes: 30 additions & 4 deletions rust/src/napi/request.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,53 @@
use crate::napi::convert::{js_object_to_request_options, response_to_js_object};
use crate::transport::execute_request;
use crate::store::request_store::{
cancel_request as cancel_request_handle, insert_request, remove_request,
};
use crate::store::runtime::runtime;
use crate::transport::make_request;
use neon::prelude::*;

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

let channel = cx.channel();
let (deferred, promise) = cx.promise();
let (cancel_tx, cancel_rx) = tokio::sync::oneshot::channel::<()>();
let handle = insert_request(cancel_tx);

std::thread::spawn(move || {
let result = execute_request(options);
let result = runtime().block_on(async move {
tokio::select! {
result = make_request(options) => result,
_ = cancel_rx => Err(anyhow::anyhow!("Request aborted")),
}
});

remove_request(handle);

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

Ok(promise)
let result = JsObject::new(&mut cx);
let handle_value = cx.number(handle as f64);

result.set(&mut cx, "handle", handle_value)?;
result.set(&mut cx, "promise", promise)?;

Ok(result)
}

fn cancel_request(mut cx: FunctionContext) -> JsResult<JsBoolean> {
let handle = cx.argument::<JsNumber>(0)?.value(&mut cx) as u64;

Ok(cx.boolean(cancel_request_handle(handle)))
}

pub fn register(cx: &mut ModuleContext) -> NeonResult<()> {
cx.export_function("request", request)?;
cx.export_function("cancelRequest", cancel_request)?;
Ok(())
}
Loading
Loading