Skip to content

Commit e2104d1

Browse files
authored
chore(refactor): extract cookies into separate module (#306)
Refactors `impit-node` by splitting the `request.rs` module into multiple separate ones.
1 parent c780df3 commit e2104d1

5 files changed

Lines changed: 180 additions & 160 deletions

File tree

impit-node/src/cookies.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use napi::bindgen_prelude::{Function, JsObjectValue, Object};
2+
3+
use napi::Env;
4+
5+
use crate::utils::await_promise;
6+
7+
use reqwest::Url;
8+
9+
use reqwest::cookie::CookieStore;
10+
use reqwest::header::HeaderValue;
11+
12+
use napi::Status;
13+
14+
use napi::bindgen_prelude::Promise;
15+
16+
use napi::threadsafe_function::ThreadsafeFunction;
17+
18+
pub struct NodeCookieJar {
19+
pub(crate) set_cookie_tsfn:
20+
ThreadsafeFunction<(String, String), Promise<()>, (String, String), Status, false>,
21+
pub(crate) get_cookies_tsfn: ThreadsafeFunction<String, Promise<String>, String, Status, false>,
22+
}
23+
24+
impl CookieStore for NodeCookieJar {
25+
fn set_cookies(
26+
&self,
27+
cookie_headers: &mut dyn Iterator<Item = &reqwest::header::HeaderValue>,
28+
url: &Url,
29+
) {
30+
for header in cookie_headers {
31+
let header = header.to_str().unwrap_or_default().to_string();
32+
let url = url.as_str().to_string();
33+
34+
let _ = await_promise(&self.set_cookie_tsfn, (header.clone(), url.clone()));
35+
}
36+
}
37+
38+
fn cookies(&self, url: &Url) -> Option<reqwest::header::HeaderValue> {
39+
let url = url.as_str().to_string();
40+
41+
await_promise(&self.get_cookies_tsfn, url.clone())
42+
.ok()
43+
.and_then(|header| {
44+
if header.is_empty() {
45+
return None;
46+
}
47+
48+
HeaderValue::from_str(&header).ok()
49+
})
50+
}
51+
}
52+
53+
impl NodeCookieJar {
54+
pub fn new(env: &Env, tough_cookie: Object) -> Result<Self, napi::Error> {
55+
let set_cookie_js_method = match tough_cookie
56+
.get_named_property::<Function<'_, (String, String), Promise<()>>>("setCookie")
57+
{
58+
Ok(method) => method,
59+
Err(e) => {
60+
return Err(napi::Error::new(
61+
napi::Status::GenericFailure,
62+
format!("[impit] Couldn't find `setCookie` method on the external cookie store: {e}"),
63+
));
64+
}
65+
};
66+
67+
let get_cookie_js_method = match tough_cookie
68+
.get_named_property::<Function<'_, String, Promise<String>>>("getCookieString")
69+
{
70+
Ok(method) => method,
71+
Err(e) => {
72+
return Err(napi::Error::new(
73+
napi::Status::GenericFailure,
74+
format!(
75+
"[impit] Couldn't find `getCookieString` method on the external cookie store: {e}"
76+
),
77+
));
78+
}
79+
};
80+
81+
let mut set_cookie = set_cookie_js_method
82+
.build_threadsafe_function::<(std::string::String, std::string::String)>()
83+
.build_callback(|ctx| Ok(ctx.value))?;
84+
85+
let mut get_cookies = get_cookie_js_method
86+
.build_threadsafe_function::<std::string::String>()
87+
.build_callback(|ctx| Ok(ctx.value))?;
88+
89+
// Unless the `ThreadsafeFunction` is unreferenced, the Node.JS application will hang on exit
90+
// https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs
91+
#[allow(deprecated)]
92+
let _ = set_cookie.unref(env);
93+
#[allow(deprecated)]
94+
let _ = get_cookies.unref(env);
95+
96+
Ok(Self {
97+
set_cookie_tsfn: set_cookie,
98+
get_cookies_tsfn: get_cookies,
99+
})
100+
}
101+
}

impit-node/src/impit_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use impit::{
77
use napi::{bindgen_prelude::Object, Env};
88
use napi_derive::napi;
99

10-
use crate::request::NodeCookieJar;
10+
use crate::cookies::NodeCookieJar;
1111

1212
/// Supported browsers for emulation.
1313
///

impit-node/src/lib.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ use impit::{
88
use napi::Env;
99
use napi_derive::napi;
1010

11+
mod cookies;
1112
mod impit_builder;
1213
mod request;
1314
mod response;
15+
mod utils;
1416

1517
use self::response::ImpitResponse;
1618
use impit_builder::ImpitOptions;
17-
use request::{HttpMethod, NodeCookieJar, RequestInit};
19+
use request::{HttpMethod, RequestInit};
1820

1921
/// The main class of the `impit` package
2022
///
@@ -36,7 +38,7 @@ use request::{HttpMethod, NodeCookieJar, RequestInit};
3638
/// resources (e.g. cookie jar and connection pool), and other settings.
3739
#[napi(js_name = "Impit")]
3840
pub struct ImpitWrapper {
39-
inner: Impit<NodeCookieJar>,
41+
inner: Impit<cookies::NodeCookieJar>,
4042
}
4143

4244
#[napi]
@@ -60,7 +62,7 @@ impl ImpitWrapper {
6062
/// ```
6163
#[napi(constructor)]
6264
pub fn new(env: &Env, options: Option<ImpitOptions>) -> Result<Self, napi::Error> {
63-
let config: Result<ImpitBuilder<NodeCookieJar>, napi::Error> =
65+
let config: Result<ImpitBuilder<cookies::NodeCookieJar>, napi::Error> =
6466
options.unwrap_or_default().into_builder(env);
6567

6668
// `quinn` for h3 requires existing async runtime.

impit-node/src/request.rs

Lines changed: 1 addition & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
1-
use std::{
2-
thread::{self, sleep},
3-
time::Duration,
4-
};
5-
6-
use napi::{
7-
bindgen_prelude::{
8-
FromNapiValue, Function, JsObjectValue, JsValuesTupleIntoVec, Object, Promise, Uint8Array,
9-
},
10-
threadsafe_function::ThreadsafeFunction,
11-
Env, Status,
12-
};
1+
use napi::bindgen_prelude::Uint8Array;
132

143
use napi_derive::napi;
15-
use reqwest::{cookie::CookieStore, header::HeaderValue, Url};
16-
use tokio::sync::oneshot;
174

185
#[derive(Default, Clone)]
196
#[napi(string_enum = "UPPERCASE")]
@@ -59,145 +46,3 @@ pub struct RequestInit {
5946
/// Force the request to use HTTP/3. If the server doesn't expect HTTP/3 or the Impit instance doesn't have HTTP/3 enabled (via the {@link ImpitOptions.http3} option), the request will fail.
6047
pub force_http3: Option<bool>,
6148
}
62-
63-
fn await_promise<
64-
T: Send,
65-
CallbackArgs: JsValuesTupleIntoVec,
66-
RustReturn: FromNapiValue + std::fmt::Debug + Sync + Send,
67-
>(
68-
tsfn: &ThreadsafeFunction<T, Promise<RustReturn>, CallbackArgs, Status, false>,
69-
args: T,
70-
) -> Result<RustReturn, napi::Error> {
71-
thread::scope(|scope| {
72-
let (tx, mut rx) = oneshot::channel();
73-
74-
scope.spawn(move || match tokio::runtime::Runtime::new() {
75-
Ok(runtime) => {
76-
runtime.block_on(async {
77-
match tsfn.call_async(args).await {
78-
Ok(result) => {
79-
let _ = tx.send(result.await);
80-
}
81-
Err(e) => {
82-
let _ = tx.send(Err(napi::Error::new(
83-
napi::Status::GenericFailure,
84-
format!("[impit] failed to retrieve cookies from the external cookie store: {e}"),
85-
)));
86-
}
87-
}
88-
});
89-
}
90-
Err(e) => {
91-
let _ = tx.send(Err(napi::Error::new(
92-
napi::Status::GenericFailure,
93-
format!("[impit] failed to retrieve cookies from the external cookie store: {e}"),
94-
)));
95-
}
96-
});
97-
98-
let mut result = rx.try_recv();
99-
100-
let max_retries = 5;
101-
let mut retries = 0;
102-
103-
while result.is_err() && retries < max_retries {
104-
sleep(Duration::from_millis(5));
105-
result = rx.try_recv();
106-
retries += 1;
107-
}
108-
109-
match result {
110-
Ok(Ok(result)) => Ok(result),
111-
Ok(Err(e)) => Err(e),
112-
Err(_) => Err(napi::Error::new(
113-
napi::Status::GenericFailure,
114-
"[impit] failed to retrieve cookies from the external cookie store".to_string(),
115-
)),
116-
}
117-
})
118-
}
119-
120-
pub struct NodeCookieJar {
121-
set_cookie_tsfn:
122-
ThreadsafeFunction<(String, String), Promise<()>, (String, String), Status, false>,
123-
get_cookies_tsfn: ThreadsafeFunction<String, Promise<String>, String, Status, false>,
124-
}
125-
126-
impl CookieStore for NodeCookieJar {
127-
fn set_cookies(
128-
&self,
129-
cookie_headers: &mut dyn Iterator<Item = &reqwest::header::HeaderValue>,
130-
url: &Url,
131-
) {
132-
for header in cookie_headers {
133-
let header = header.to_str().unwrap_or_default().to_string();
134-
let url = url.as_str().to_string();
135-
136-
let _ = await_promise(&self.set_cookie_tsfn, (header.clone(), url.clone()));
137-
}
138-
}
139-
140-
fn cookies(&self, url: &Url) -> Option<reqwest::header::HeaderValue> {
141-
let url = url.as_str().to_string();
142-
143-
await_promise(&self.get_cookies_tsfn, url.clone())
144-
.ok()
145-
.and_then(|header| {
146-
if header.is_empty() {
147-
return None;
148-
}
149-
150-
HeaderValue::from_str(&header).ok()
151-
})
152-
}
153-
}
154-
155-
impl NodeCookieJar {
156-
pub fn new(env: &Env, tough_cookie: Object) -> Result<Self, napi::Error> {
157-
let set_cookie_js_method = match tough_cookie
158-
.get_named_property::<Function<'_, (String, String), Promise<()>>>("setCookie")
159-
{
160-
Ok(method) => method,
161-
Err(e) => {
162-
return Err(napi::Error::new(
163-
napi::Status::GenericFailure,
164-
format!("[impit] Couldn't find `setCookie` method on the external cookie store: {e}"),
165-
));
166-
}
167-
};
168-
169-
let get_cookie_js_method = match tough_cookie
170-
.get_named_property::<Function<'_, String, Promise<String>>>("getCookieString")
171-
{
172-
Ok(method) => method,
173-
Err(e) => {
174-
return Err(napi::Error::new(
175-
napi::Status::GenericFailure,
176-
format!(
177-
"[impit] Couldn't find `getCookieString` method on the external cookie store: {e}"
178-
),
179-
));
180-
}
181-
};
182-
183-
let mut set_cookie = set_cookie_js_method
184-
.build_threadsafe_function::<(std::string::String, std::string::String)>()
185-
.build_callback(|ctx| Ok(ctx.value))?;
186-
187-
let mut get_cookies = get_cookie_js_method
188-
.build_threadsafe_function::<std::string::String>()
189-
.build_callback(|ctx| Ok(ctx.value))?;
190-
191-
// Unless the `ThreadsafeFunction` is unreferenced, the Node.JS application will hang on exit
192-
// https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs
193-
#[allow(deprecated)]
194-
let _ = set_cookie.unref(env);
195-
#[allow(deprecated)]
196-
let _ = get_cookies.unref(env);
197-
198-
Ok(Self {
199-
set_cookie_tsfn: set_cookie,
200-
get_cookies_tsfn: get_cookies,
201-
})
202-
}
203-
}

impit-node/src/utils.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use napi::threadsafe_function::ThreadsafeFunction;
2+
use tokio::sync::oneshot;
3+
4+
use std::thread;
5+
use std::time::Duration;
6+
7+
use std::thread::sleep;
8+
9+
use napi::Status;
10+
11+
use napi::bindgen_prelude::Promise;
12+
13+
use napi::bindgen_prelude::FromNapiValue;
14+
15+
use napi::bindgen_prelude::JsValuesTupleIntoVec;
16+
17+
pub(crate) fn await_promise<
18+
T: Send,
19+
CallbackArgs: JsValuesTupleIntoVec,
20+
RustReturn: FromNapiValue + std::fmt::Debug + Sync + Send,
21+
>(
22+
tsfn: &ThreadsafeFunction<T, Promise<RustReturn>, CallbackArgs, Status, false>,
23+
args: T,
24+
) -> Result<RustReturn, napi::Error> {
25+
thread::scope(|scope| {
26+
let (tx, mut rx) = oneshot::channel();
27+
28+
scope.spawn(move || match tokio::runtime::Runtime::new() {
29+
Ok(runtime) => {
30+
runtime.block_on(async {
31+
match tsfn.call_async(args).await {
32+
Ok(result) => {
33+
let _ = tx.send(result.await);
34+
}
35+
Err(e) => {
36+
let _ = tx.send(Err(napi::Error::new(
37+
napi::Status::GenericFailure,
38+
format!("[impit] failed to retrieve cookies from the external cookie store: {e}"),
39+
)));
40+
}
41+
}
42+
});
43+
}
44+
Err(e) => {
45+
let _ = tx.send(Err(napi::Error::new(
46+
napi::Status::GenericFailure,
47+
format!("[impit] failed to retrieve cookies from the external cookie store: {e}"),
48+
)));
49+
}
50+
});
51+
52+
let mut result = rx.try_recv();
53+
54+
let max_retries = 5;
55+
let mut retries = 0;
56+
57+
while result.is_err() && retries < max_retries {
58+
sleep(Duration::from_millis(5));
59+
result = rx.try_recv();
60+
retries += 1;
61+
}
62+
63+
match result {
64+
Ok(Ok(result)) => Ok(result),
65+
Ok(Err(e)) => Err(e),
66+
Err(_) => Err(napi::Error::new(
67+
napi::Status::GenericFailure,
68+
"[impit] failed to retrieve cookies from the external cookie store".to_string(),
69+
)),
70+
}
71+
})
72+
}

0 commit comments

Comments
 (0)