@nomicfoundation/edr 0.2.0-alpha.2
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/config.toml +8 -0
- package/.mocharc.json +4 -0
- package/Cargo.toml +50 -0
- package/LICENSE +1 -0
- package/artifacts/bindings-aarch64-apple-darwin/edr.darwin-arm64.node +0 -0
- package/artifacts/bindings-aarch64-pc-windows-msvc/edr.win32-arm64-msvc.node +0 -0
- package/artifacts/bindings-aarch64-unknown-linux-gnu/edr.linux-arm64-gnu.node +0 -0
- package/artifacts/bindings-aarch64-unknown-linux-musl/edr.linux-arm64-musl.node +0 -0
- package/artifacts/bindings-i686-pc-windows-msvc/edr.win32-ia32-msvc.node +0 -0
- package/artifacts/bindings-x86_64-apple-darwin/edr.darwin-x64.node +0 -0
- package/artifacts/bindings-x86_64-pc-windows-msvc/edr.win32-x64-msvc.node +0 -0
- package/artifacts/bindings-x86_64-unknown-linux-gnu/edr.linux-x64-gnu.node +0 -0
- package/artifacts/bindings-x86_64-unknown-linux-musl/edr.linux-x64-musl.node +0 -0
- package/build.rs +3 -0
- package/index.d.ts +383 -0
- package/index.js +264 -0
- package/npm/darwin-arm64/README.md +3 -0
- package/npm/darwin-arm64/edr.darwin-arm64.node +0 -0
- package/npm/darwin-arm64/package.json +22 -0
- package/npm/darwin-x64/README.md +3 -0
- package/npm/darwin-x64/edr.darwin-x64.node +0 -0
- package/npm/darwin-x64/package.json +22 -0
- package/npm/linux-arm64-gnu/README.md +3 -0
- package/npm/linux-arm64-gnu/edr.linux-arm64-gnu.node +0 -0
- package/npm/linux-arm64-gnu/package.json +25 -0
- package/npm/linux-arm64-musl/README.md +3 -0
- package/npm/linux-arm64-musl/edr.linux-arm64-musl.node +0 -0
- package/npm/linux-arm64-musl/package.json +25 -0
- package/npm/linux-x64-gnu/README.md +3 -0
- package/npm/linux-x64-gnu/edr.linux-x64-gnu.node +0 -0
- package/npm/linux-x64-gnu/package.json +25 -0
- package/npm/linux-x64-musl/README.md +3 -0
- package/npm/linux-x64-musl/edr.linux-x64-musl.node +0 -0
- package/npm/linux-x64-musl/package.json +25 -0
- package/npm/win32-arm64-msvc/README.md +3 -0
- package/npm/win32-arm64-msvc/edr.win32-arm64-msvc.node +0 -0
- package/npm/win32-arm64-msvc/package.json +22 -0
- package/npm/win32-ia32-msvc/README.md +3 -0
- package/npm/win32-ia32-msvc/edr.win32-ia32-msvc.node +0 -0
- package/npm/win32-ia32-msvc/package.json +22 -0
- package/npm/win32-x64-msvc/README.md +3 -0
- package/npm/win32-x64-msvc/edr.win32-x64-msvc.node +0 -0
- package/npm/win32-x64-msvc/package.json +22 -0
- package/package.json +61 -0
- package/src/account.rs +28 -0
- package/src/block.rs +110 -0
- package/src/cast.rs +119 -0
- package/src/config.rs +70 -0
- package/src/context.rs +74 -0
- package/src/debug_trace.rs +38 -0
- package/src/lib.rs +18 -0
- package/src/log.rs +41 -0
- package/src/logger.rs +1277 -0
- package/src/provider/config.rs +271 -0
- package/src/provider.rs +185 -0
- package/src/result.rs +254 -0
- package/src/subscribe.rs +60 -0
- package/src/sync.rs +85 -0
- package/src/threadsafe_function.rs +305 -0
- package/src/trace.rs +168 -0
- package/test/provider.ts +104 -0
- package/tsconfig.json +17 -0
package/src/result.rs
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
use std::mem;
|
|
2
|
+
|
|
3
|
+
use napi::{
|
|
4
|
+
bindgen_prelude::{BigInt, Buffer, Either3},
|
|
5
|
+
Either, Env, JsBuffer, JsBufferValue,
|
|
6
|
+
};
|
|
7
|
+
use napi_derive::napi;
|
|
8
|
+
|
|
9
|
+
use crate::log::ExecutionLog;
|
|
10
|
+
|
|
11
|
+
/// The possible reasons for successful termination of the EVM.
|
|
12
|
+
#[napi]
|
|
13
|
+
pub enum SuccessReason {
|
|
14
|
+
/// The opcode `STOP` was called
|
|
15
|
+
Stop,
|
|
16
|
+
/// The opcode `RETURN` was called
|
|
17
|
+
Return,
|
|
18
|
+
/// The opcode `SELFDESTRUCT` was called
|
|
19
|
+
SelfDestruct,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl From<edr_evm::Eval> for SuccessReason {
|
|
23
|
+
fn from(eval: edr_evm::Eval) -> Self {
|
|
24
|
+
match eval {
|
|
25
|
+
edr_evm::Eval::Stop => Self::Stop,
|
|
26
|
+
edr_evm::Eval::Return => Self::Return,
|
|
27
|
+
edr_evm::Eval::SelfDestruct => Self::SelfDestruct,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl From<SuccessReason> for edr_evm::Eval {
|
|
33
|
+
fn from(value: SuccessReason) -> Self {
|
|
34
|
+
match value {
|
|
35
|
+
SuccessReason::Stop => Self::Stop,
|
|
36
|
+
SuccessReason::Return => Self::Return,
|
|
37
|
+
SuccessReason::SelfDestruct => Self::SelfDestruct,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[napi(object)]
|
|
43
|
+
pub struct CallOutput {
|
|
44
|
+
/// Return value
|
|
45
|
+
pub return_value: JsBuffer,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
#[napi(object)]
|
|
49
|
+
pub struct CreateOutput {
|
|
50
|
+
/// Return value
|
|
51
|
+
pub return_value: JsBuffer,
|
|
52
|
+
/// Optionally, a 160-bit address
|
|
53
|
+
pub address: Option<Buffer>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// The result when the EVM terminates successfully.
|
|
57
|
+
#[napi(object)]
|
|
58
|
+
pub struct SuccessResult {
|
|
59
|
+
/// The reason for termination
|
|
60
|
+
pub reason: SuccessReason,
|
|
61
|
+
/// The amount of gas used
|
|
62
|
+
pub gas_used: BigInt,
|
|
63
|
+
/// The amount of gas refunded
|
|
64
|
+
pub gas_refunded: BigInt,
|
|
65
|
+
/// The logs
|
|
66
|
+
pub logs: Vec<ExecutionLog>,
|
|
67
|
+
/// The transaction output
|
|
68
|
+
pub output: Either<CallOutput, CreateOutput>,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// The result when the EVM terminates due to a revert.
|
|
72
|
+
#[napi(object)]
|
|
73
|
+
pub struct RevertResult {
|
|
74
|
+
/// The amount of gas used
|
|
75
|
+
pub gas_used: BigInt,
|
|
76
|
+
/// The transaction output
|
|
77
|
+
pub output: JsBuffer,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Indicates that the EVM has experienced an exceptional halt. This causes
|
|
81
|
+
/// execution to immediately end with all gas being consumed.
|
|
82
|
+
#[napi]
|
|
83
|
+
pub enum ExceptionalHalt {
|
|
84
|
+
OutOfGas,
|
|
85
|
+
OpcodeNotFound,
|
|
86
|
+
InvalidFEOpcode,
|
|
87
|
+
InvalidJump,
|
|
88
|
+
NotActivated,
|
|
89
|
+
StackUnderflow,
|
|
90
|
+
StackOverflow,
|
|
91
|
+
OutOfOffset,
|
|
92
|
+
CreateCollision,
|
|
93
|
+
PrecompileError,
|
|
94
|
+
NonceOverflow,
|
|
95
|
+
/// Create init code size exceeds limit (runtime).
|
|
96
|
+
CreateContractSizeLimit,
|
|
97
|
+
/// Error on created contract that begins with EF
|
|
98
|
+
CreateContractStartingWithEF,
|
|
99
|
+
/// EIP-3860: Limit and meter initcode. Initcode size limit exceeded.
|
|
100
|
+
CreateInitcodeSizeLimit,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl From<edr_evm::Halt> for ExceptionalHalt {
|
|
104
|
+
fn from(halt: edr_evm::Halt) -> Self {
|
|
105
|
+
match halt {
|
|
106
|
+
edr_evm::Halt::OutOfGas(..) => ExceptionalHalt::OutOfGas,
|
|
107
|
+
edr_evm::Halt::OpcodeNotFound => ExceptionalHalt::OpcodeNotFound,
|
|
108
|
+
edr_evm::Halt::InvalidFEOpcode => ExceptionalHalt::InvalidFEOpcode,
|
|
109
|
+
edr_evm::Halt::InvalidJump => ExceptionalHalt::InvalidJump,
|
|
110
|
+
edr_evm::Halt::NotActivated => ExceptionalHalt::NotActivated,
|
|
111
|
+
edr_evm::Halt::StackUnderflow => ExceptionalHalt::StackUnderflow,
|
|
112
|
+
edr_evm::Halt::StackOverflow => ExceptionalHalt::StackOverflow,
|
|
113
|
+
edr_evm::Halt::OutOfOffset => ExceptionalHalt::OutOfOffset,
|
|
114
|
+
edr_evm::Halt::CreateCollision => ExceptionalHalt::CreateCollision,
|
|
115
|
+
edr_evm::Halt::PrecompileError => ExceptionalHalt::PrecompileError,
|
|
116
|
+
edr_evm::Halt::NonceOverflow => ExceptionalHalt::NonceOverflow,
|
|
117
|
+
edr_evm::Halt::CreateContractSizeLimit => ExceptionalHalt::CreateContractSizeLimit,
|
|
118
|
+
edr_evm::Halt::CreateContractStartingWithEF => {
|
|
119
|
+
ExceptionalHalt::CreateContractStartingWithEF
|
|
120
|
+
}
|
|
121
|
+
edr_evm::Halt::CreateInitcodeSizeLimit => ExceptionalHalt::CreateInitcodeSizeLimit,
|
|
122
|
+
edr_evm::Halt::OverflowPayment
|
|
123
|
+
| edr_evm::Halt::StateChangeDuringStaticCall
|
|
124
|
+
| edr_evm::Halt::CallNotAllowedInsideStatic
|
|
125
|
+
| edr_evm::Halt::OutOfFund
|
|
126
|
+
| edr_evm::Halt::CallTooDeep => {
|
|
127
|
+
unreachable!("Internal halts that can be only found inside Inspector: {halt:?}")
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
impl From<ExceptionalHalt> for edr_evm::Halt {
|
|
134
|
+
fn from(value: ExceptionalHalt) -> Self {
|
|
135
|
+
match value {
|
|
136
|
+
ExceptionalHalt::OutOfGas => Self::OutOfGas(edr_evm::OutOfGasError::BasicOutOfGas),
|
|
137
|
+
ExceptionalHalt::OpcodeNotFound => Self::OpcodeNotFound,
|
|
138
|
+
ExceptionalHalt::InvalidFEOpcode => Self::InvalidFEOpcode,
|
|
139
|
+
ExceptionalHalt::InvalidJump => Self::InvalidJump,
|
|
140
|
+
ExceptionalHalt::NotActivated => Self::NotActivated,
|
|
141
|
+
ExceptionalHalt::StackUnderflow => Self::StackUnderflow,
|
|
142
|
+
ExceptionalHalt::StackOverflow => Self::StackOverflow,
|
|
143
|
+
ExceptionalHalt::OutOfOffset => Self::OutOfOffset,
|
|
144
|
+
ExceptionalHalt::CreateCollision => Self::CreateCollision,
|
|
145
|
+
ExceptionalHalt::PrecompileError => Self::PrecompileError,
|
|
146
|
+
ExceptionalHalt::NonceOverflow => Self::NonceOverflow,
|
|
147
|
+
ExceptionalHalt::CreateContractSizeLimit => Self::CreateContractSizeLimit,
|
|
148
|
+
ExceptionalHalt::CreateContractStartingWithEF => Self::CreateContractStartingWithEF,
|
|
149
|
+
ExceptionalHalt::CreateInitcodeSizeLimit => Self::CreateInitcodeSizeLimit,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// The result when the EVM terminates due to an exceptional halt.
|
|
155
|
+
#[napi(object)]
|
|
156
|
+
pub struct HaltResult {
|
|
157
|
+
/// The exceptional halt that occurred
|
|
158
|
+
pub reason: ExceptionalHalt,
|
|
159
|
+
/// Halting will spend all the gas and will thus be equal to the specified
|
|
160
|
+
/// gas limit
|
|
161
|
+
pub gas_used: BigInt,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// The result of executing a transaction.
|
|
165
|
+
#[napi(object)]
|
|
166
|
+
pub struct ExecutionResult {
|
|
167
|
+
/// The transaction result
|
|
168
|
+
pub result: Either3<SuccessResult, RevertResult, HaltResult>,
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
impl ExecutionResult {
|
|
172
|
+
pub fn new(env: &Env, result: &edr_evm::ExecutionResult) -> napi::Result<Self> {
|
|
173
|
+
let result = match result {
|
|
174
|
+
edr_evm::ExecutionResult::Success {
|
|
175
|
+
reason,
|
|
176
|
+
gas_used,
|
|
177
|
+
gas_refunded,
|
|
178
|
+
logs,
|
|
179
|
+
output,
|
|
180
|
+
} => {
|
|
181
|
+
let logs = logs
|
|
182
|
+
.iter()
|
|
183
|
+
.map(|log| ExecutionLog::new(env, log))
|
|
184
|
+
.collect::<napi::Result<_>>()?;
|
|
185
|
+
|
|
186
|
+
Either3::A(SuccessResult {
|
|
187
|
+
reason: SuccessReason::from(*reason),
|
|
188
|
+
gas_used: BigInt::from(*gas_used),
|
|
189
|
+
gas_refunded: BigInt::from(*gas_refunded),
|
|
190
|
+
logs,
|
|
191
|
+
output: match output {
|
|
192
|
+
edr_evm::Output::Call(return_value) => {
|
|
193
|
+
let return_value = return_value.clone();
|
|
194
|
+
Either::A(CallOutput {
|
|
195
|
+
return_value: unsafe {
|
|
196
|
+
env.create_buffer_with_borrowed_data(
|
|
197
|
+
return_value.as_ptr(),
|
|
198
|
+
return_value.len(),
|
|
199
|
+
return_value,
|
|
200
|
+
|return_value: edr_eth::Bytes, _env| {
|
|
201
|
+
mem::drop(return_value);
|
|
202
|
+
},
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
.map(JsBufferValue::into_raw)?,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
edr_evm::Output::Create(return_value, address) => {
|
|
209
|
+
let return_value = return_value.clone();
|
|
210
|
+
|
|
211
|
+
Either::B(CreateOutput {
|
|
212
|
+
return_value: unsafe {
|
|
213
|
+
env.create_buffer_with_borrowed_data(
|
|
214
|
+
return_value.as_ptr(),
|
|
215
|
+
return_value.len(),
|
|
216
|
+
return_value,
|
|
217
|
+
|return_value: edr_eth::Bytes, _env| {
|
|
218
|
+
mem::drop(return_value);
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
.map(JsBufferValue::into_raw)?,
|
|
223
|
+
address: address.map(|address| Buffer::from(address.as_slice())),
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
}
|
|
229
|
+
edr_evm::ExecutionResult::Revert { gas_used, output } => {
|
|
230
|
+
let output = output.clone();
|
|
231
|
+
Either3::B(RevertResult {
|
|
232
|
+
gas_used: BigInt::from(*gas_used),
|
|
233
|
+
output: unsafe {
|
|
234
|
+
env.create_buffer_with_borrowed_data(
|
|
235
|
+
output.as_ptr(),
|
|
236
|
+
output.len(),
|
|
237
|
+
output,
|
|
238
|
+
|output: edr_eth::Bytes, _env| {
|
|
239
|
+
mem::drop(output);
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
}
|
|
243
|
+
.map(JsBufferValue::into_raw)?,
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
edr_evm::ExecutionResult::Halt { reason, gas_used } => Either3::C(HaltResult {
|
|
247
|
+
reason: ExceptionalHalt::from(*reason),
|
|
248
|
+
gas_used: BigInt::from(*gas_used),
|
|
249
|
+
}),
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
Ok(Self { result })
|
|
253
|
+
}
|
|
254
|
+
}
|
package/src/subscribe.rs
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
use edr_eth::{remote::eth, B256};
|
|
2
|
+
use napi::{bindgen_prelude::BigInt, Env, JsFunction, NapiRaw};
|
|
3
|
+
use napi_derive::napi;
|
|
4
|
+
|
|
5
|
+
use crate::threadsafe_function::{
|
|
6
|
+
ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
#[derive(Clone)]
|
|
10
|
+
pub struct SubscriberCallback {
|
|
11
|
+
inner: ThreadsafeFunction<edr_provider::SubscriptionEvent>,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl SubscriberCallback {
|
|
15
|
+
pub fn new(env: &Env, subscription_event_callback: JsFunction) -> napi::Result<Self> {
|
|
16
|
+
let callback = ThreadsafeFunction::create(
|
|
17
|
+
env.raw(),
|
|
18
|
+
// SAFETY: The callback is guaranteed to be valid for the lifetime of the inspector.
|
|
19
|
+
unsafe { subscription_event_callback.raw() },
|
|
20
|
+
0,
|
|
21
|
+
|ctx: ThreadSafeCallContext<edr_provider::SubscriptionEvent>| {
|
|
22
|
+
// SubscriptionEvent
|
|
23
|
+
let mut event = ctx.env.create_object()?;
|
|
24
|
+
|
|
25
|
+
ctx.env
|
|
26
|
+
.create_bigint_from_words(false, ctx.value.filter_id.as_limbs().to_vec())
|
|
27
|
+
.and_then(|filter_id| event.set_named_property("filterId", filter_id))?;
|
|
28
|
+
|
|
29
|
+
let result = match ctx.value.result {
|
|
30
|
+
edr_provider::SubscriptionEventData::Logs(logs) => ctx.env.to_js_value(&logs),
|
|
31
|
+
edr_provider::SubscriptionEventData::NewHeads(block) => {
|
|
32
|
+
let block = eth::Block::<B256>::from(block);
|
|
33
|
+
ctx.env.to_js_value(&block)
|
|
34
|
+
}
|
|
35
|
+
edr_provider::SubscriptionEventData::NewPendingTransactions(tx_hash) => {
|
|
36
|
+
ctx.env.to_js_value(&tx_hash)
|
|
37
|
+
}
|
|
38
|
+
}?;
|
|
39
|
+
|
|
40
|
+
event.set_named_property("result", result)?;
|
|
41
|
+
|
|
42
|
+
ctx.callback.call(None, &[event])?;
|
|
43
|
+
Ok(())
|
|
44
|
+
},
|
|
45
|
+
)?;
|
|
46
|
+
Ok(Self { inner: callback })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pub fn call(&self, event: edr_provider::SubscriptionEvent) {
|
|
50
|
+
// This is blocking because it's important that the subscription events are
|
|
51
|
+
// in-order
|
|
52
|
+
self.inner.call(event, ThreadsafeFunctionCallMode::Blocking);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[napi(object)]
|
|
57
|
+
pub struct SubscriptionEvent {
|
|
58
|
+
pub filter_id: BigInt,
|
|
59
|
+
pub result: serde_json::Value,
|
|
60
|
+
}
|
package/src/sync.rs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
use std::{fmt::Debug, sync::mpsc::Sender};
|
|
2
|
+
|
|
3
|
+
use napi::{bindgen_prelude::FromNapiValue, Env, JsFunction, JsObject, JsUnknown, NapiRaw, Status};
|
|
4
|
+
|
|
5
|
+
use crate::cast::TryCast;
|
|
6
|
+
|
|
7
|
+
pub fn await_promise<I, O>(
|
|
8
|
+
env: Env,
|
|
9
|
+
result: JsUnknown,
|
|
10
|
+
tx: Sender<napi::Result<O>>,
|
|
11
|
+
) -> napi::Result<()>
|
|
12
|
+
where
|
|
13
|
+
I: FromNapiValue + TryCast<O, Error = napi::Error>,
|
|
14
|
+
O: 'static,
|
|
15
|
+
{
|
|
16
|
+
// If the result is a promise, wait for it to resolve, and send the result to
|
|
17
|
+
// the channel. Otherwise, send the result immediately.
|
|
18
|
+
if result.is_promise()? {
|
|
19
|
+
let result: JsObject = result.try_into()?;
|
|
20
|
+
let then: JsFunction = result.get_named_property("then")?;
|
|
21
|
+
let tx2 = tx.clone();
|
|
22
|
+
let cb = env.create_function_from_closure("callback", move |ctx| {
|
|
23
|
+
let result = ctx.get::<I>(0)?;
|
|
24
|
+
tx.send(Ok(result.try_cast()?)).unwrap();
|
|
25
|
+
ctx.env.get_undefined()
|
|
26
|
+
})?;
|
|
27
|
+
let eb = env.create_function_from_closure("error_callback", move |ctx| {
|
|
28
|
+
// TODO: need a way to convert a JsUnknown to an Error
|
|
29
|
+
tx2.send(Err(napi::Error::from_reason("Promise rejected")))
|
|
30
|
+
.unwrap();
|
|
31
|
+
ctx.env.get_undefined()
|
|
32
|
+
})?;
|
|
33
|
+
then.call(Some(&result), &[cb, eb])?;
|
|
34
|
+
} else {
|
|
35
|
+
let result = unsafe { I::from_napi_value(env.raw(), result.raw())? };
|
|
36
|
+
tx.send(Ok(result.try_cast()?)).unwrap();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
Ok(())
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[allow(dead_code)]
|
|
43
|
+
pub fn await_void_promise(
|
|
44
|
+
env: Env,
|
|
45
|
+
result: JsUnknown,
|
|
46
|
+
tx: Sender<napi::Result<()>>,
|
|
47
|
+
) -> napi::Result<()> {
|
|
48
|
+
// If the result is a promise, wait for it to resolve, and send the result to
|
|
49
|
+
// the channel. Otherwise, send the result immediately.
|
|
50
|
+
if result.is_promise()? {
|
|
51
|
+
let result: JsObject = result.try_into()?;
|
|
52
|
+
let then: JsFunction = result.get_named_property("then")?;
|
|
53
|
+
let tx2 = tx.clone();
|
|
54
|
+
let cb = env.create_function_from_closure("callback", move |ctx| {
|
|
55
|
+
tx.send(Ok(())).unwrap();
|
|
56
|
+
ctx.env.get_undefined()
|
|
57
|
+
})?;
|
|
58
|
+
let eb = env.create_function_from_closure("error_callback", move |ctx| {
|
|
59
|
+
// TODO: need a way to convert a JsUnknown to an Error
|
|
60
|
+
tx2.send(Err(napi::Error::from_reason("Promise rejected")))
|
|
61
|
+
.unwrap();
|
|
62
|
+
ctx.env.get_undefined()
|
|
63
|
+
})?;
|
|
64
|
+
then.call(Some(&result), &[cb, eb])?;
|
|
65
|
+
Ok(())
|
|
66
|
+
} else {
|
|
67
|
+
Err(napi::Error::new(
|
|
68
|
+
Status::ObjectExpected,
|
|
69
|
+
"Expected promise".to_owned(),
|
|
70
|
+
))
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub fn handle_error<T: Debug>(
|
|
75
|
+
tx: Sender<napi::Result<T>>,
|
|
76
|
+
res: napi::Result<()>,
|
|
77
|
+
) -> napi::Result<()> {
|
|
78
|
+
match res {
|
|
79
|
+
Ok(_) => Ok(()),
|
|
80
|
+
Err(e) => {
|
|
81
|
+
tx.send(Err(e)).expect("send error");
|
|
82
|
+
Ok(())
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// Fork of threadsafe_function from napi-rs that allows calling JS function
|
|
2
|
+
// manually rather than only returning args. This enables us to use the return
|
|
3
|
+
// value of the function.
|
|
4
|
+
|
|
5
|
+
#![allow(clippy::single_component_path_imports)]
|
|
6
|
+
|
|
7
|
+
use std::{
|
|
8
|
+
convert::Into,
|
|
9
|
+
ffi::CString,
|
|
10
|
+
marker::PhantomData,
|
|
11
|
+
os::raw::c_void,
|
|
12
|
+
ptr,
|
|
13
|
+
sync::{
|
|
14
|
+
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
15
|
+
Arc,
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
use napi::{check_status, sys, Env, JsError, JsFunction, NapiValue, Result, Status};
|
|
20
|
+
|
|
21
|
+
/// `ThreadSafeFunction` context object
|
|
22
|
+
/// the `value` is the value passed to `call` method
|
|
23
|
+
pub struct ThreadSafeCallContext<T: 'static> {
|
|
24
|
+
pub env: Env,
|
|
25
|
+
pub value: T,
|
|
26
|
+
pub callback: JsFunction,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[repr(u8)]
|
|
30
|
+
pub enum ThreadsafeFunctionCallMode {
|
|
31
|
+
NonBlocking,
|
|
32
|
+
Blocking,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl From<ThreadsafeFunctionCallMode> for sys::napi_threadsafe_function_call_mode {
|
|
36
|
+
fn from(value: ThreadsafeFunctionCallMode) -> Self {
|
|
37
|
+
match value {
|
|
38
|
+
ThreadsafeFunctionCallMode::Blocking => sys::ThreadsafeFunctionCallMode::blocking,
|
|
39
|
+
ThreadsafeFunctionCallMode::NonBlocking => sys::ThreadsafeFunctionCallMode::nonblocking,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Communicate with the addon's main thread by invoking a JavaScript function
|
|
45
|
+
/// from other threads.
|
|
46
|
+
///
|
|
47
|
+
/// ## Example
|
|
48
|
+
/// An example of using `ThreadsafeFunction`:
|
|
49
|
+
///
|
|
50
|
+
/// ```rust
|
|
51
|
+
/// #[macro_use]
|
|
52
|
+
/// extern crate napi_derive;
|
|
53
|
+
///
|
|
54
|
+
/// use std::thread;
|
|
55
|
+
///
|
|
56
|
+
/// use napi::{
|
|
57
|
+
/// threadsafe_function::{
|
|
58
|
+
/// ThreadSafeCallContext, ThreadsafeFunctionCallMode, ThreadsafeFunctionReleaseMode,
|
|
59
|
+
/// },
|
|
60
|
+
/// CallContext, Error, JsFunction, JsNumber, JsUndefined, Result, Status,
|
|
61
|
+
/// };
|
|
62
|
+
///
|
|
63
|
+
/// #[js_function(1)]
|
|
64
|
+
/// pub fn test_threadsafe_function(ctx: CallContext) -> Result<JsUndefined> {
|
|
65
|
+
/// let func = ctx.get::<JsFunction>(0)?;
|
|
66
|
+
///
|
|
67
|
+
/// let tsfn =
|
|
68
|
+
/// ctx
|
|
69
|
+
/// .env
|
|
70
|
+
/// .create_threadsafe_function(&func, 0, |ctx: ThreadSafeCallContext<Vec<u32>>| {
|
|
71
|
+
/// ctx.value
|
|
72
|
+
/// .iter()
|
|
73
|
+
/// .map(|v| ctx.env.create_uint32(*v))
|
|
74
|
+
/// .collect::<Result<Vec<JsNumber>>>()
|
|
75
|
+
/// })?;
|
|
76
|
+
///
|
|
77
|
+
/// let tsfn_cloned = tsfn.clone();
|
|
78
|
+
///
|
|
79
|
+
/// thread::spawn(move || {
|
|
80
|
+
/// let output: Vec<u32> = vec![0, 1, 2, 3];
|
|
81
|
+
/// // It's okay to call a threadsafe function multiple times.
|
|
82
|
+
/// tsfn.call(Ok(output.clone()), ThreadsafeFunctionCallMode::Blocking);
|
|
83
|
+
/// });
|
|
84
|
+
///
|
|
85
|
+
/// thread::spawn(move || {
|
|
86
|
+
/// let output: Vec<u32> = vec![3, 2, 1, 0];
|
|
87
|
+
/// // It's okay to call a threadsafe function multiple times.
|
|
88
|
+
/// tsfn_cloned.call(Ok(output.clone()), ThreadsafeFunctionCallMode::NonBlocking);
|
|
89
|
+
/// });
|
|
90
|
+
///
|
|
91
|
+
/// ctx.env.get_undefined()
|
|
92
|
+
/// }
|
|
93
|
+
/// ```
|
|
94
|
+
#[derive(Debug)]
|
|
95
|
+
pub struct ThreadsafeFunction<T: 'static> {
|
|
96
|
+
raw_tsfn: sys::napi_threadsafe_function,
|
|
97
|
+
aborted: Arc<AtomicBool>,
|
|
98
|
+
ref_count: Arc<AtomicUsize>,
|
|
99
|
+
_phantom: PhantomData<T>,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
impl<T: 'static> Clone for ThreadsafeFunction<T> {
|
|
103
|
+
fn clone(&self) -> Self {
|
|
104
|
+
if !self.aborted.load(Ordering::Acquire) {
|
|
105
|
+
let acquire_status = unsafe { sys::napi_acquire_threadsafe_function(self.raw_tsfn) };
|
|
106
|
+
debug_assert!(
|
|
107
|
+
acquire_status == sys::Status::napi_ok,
|
|
108
|
+
"Acquire threadsafe function failed in clone"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Self {
|
|
113
|
+
raw_tsfn: self.raw_tsfn,
|
|
114
|
+
aborted: Arc::clone(&self.aborted),
|
|
115
|
+
ref_count: Arc::clone(&self.ref_count),
|
|
116
|
+
_phantom: PhantomData,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
unsafe impl<T> Send for ThreadsafeFunction<T> {}
|
|
122
|
+
unsafe impl<T> Sync for ThreadsafeFunction<T> {}
|
|
123
|
+
|
|
124
|
+
impl<T: 'static> ThreadsafeFunction<T> {
|
|
125
|
+
/// See [napi_create_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function)
|
|
126
|
+
/// for more information.
|
|
127
|
+
pub(crate) fn create<R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>>(
|
|
128
|
+
env: sys::napi_env,
|
|
129
|
+
func: sys::napi_value,
|
|
130
|
+
max_queue_size: usize,
|
|
131
|
+
callback: R,
|
|
132
|
+
) -> Result<Self> {
|
|
133
|
+
let mut async_resource_name = ptr::null_mut();
|
|
134
|
+
let s = "napi_rs_threadsafe_function";
|
|
135
|
+
let len = s.len();
|
|
136
|
+
let s = CString::new(s)?;
|
|
137
|
+
check_status!(unsafe {
|
|
138
|
+
sys::napi_create_string_utf8(env, s.as_ptr(), len, &mut async_resource_name)
|
|
139
|
+
})?;
|
|
140
|
+
|
|
141
|
+
let initial_thread_count = 1usize;
|
|
142
|
+
let mut raw_tsfn = ptr::null_mut();
|
|
143
|
+
let ptr = Box::into_raw(Box::new(callback)).cast::<c_void>();
|
|
144
|
+
check_status!(unsafe {
|
|
145
|
+
sys::napi_create_threadsafe_function(
|
|
146
|
+
env,
|
|
147
|
+
func,
|
|
148
|
+
ptr::null_mut(),
|
|
149
|
+
async_resource_name,
|
|
150
|
+
max_queue_size,
|
|
151
|
+
initial_thread_count,
|
|
152
|
+
ptr,
|
|
153
|
+
Some(thread_finalize_cb::<T, R>),
|
|
154
|
+
ptr,
|
|
155
|
+
Some(call_js_cb::<T, R>),
|
|
156
|
+
&mut raw_tsfn,
|
|
157
|
+
)
|
|
158
|
+
})?;
|
|
159
|
+
|
|
160
|
+
let aborted = Arc::new(AtomicBool::new(false));
|
|
161
|
+
let aborted_ptr = Arc::into_raw(aborted.clone()) as *mut c_void;
|
|
162
|
+
check_status!(unsafe {
|
|
163
|
+
sys::napi_add_env_cleanup_hook(env, Some(cleanup_cb), aborted_ptr)
|
|
164
|
+
})?;
|
|
165
|
+
|
|
166
|
+
Ok(ThreadsafeFunction {
|
|
167
|
+
raw_tsfn,
|
|
168
|
+
aborted,
|
|
169
|
+
ref_count: Arc::new(AtomicUsize::new(initial_thread_count)),
|
|
170
|
+
_phantom: PhantomData,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
impl<T: 'static> ThreadsafeFunction<T> {
|
|
176
|
+
/// See [napi_call_threadsafe_function](https://nodejs.org/api/n-api.html#n_api_napi_call_threadsafe_function)
|
|
177
|
+
/// for more information.
|
|
178
|
+
pub fn call(&self, value: T, mode: ThreadsafeFunctionCallMode) -> Status {
|
|
179
|
+
if self.aborted.load(Ordering::Acquire) {
|
|
180
|
+
return Status::Closing;
|
|
181
|
+
}
|
|
182
|
+
unsafe {
|
|
183
|
+
sys::napi_call_threadsafe_function(
|
|
184
|
+
self.raw_tsfn,
|
|
185
|
+
Box::into_raw(Box::new(value)).cast(),
|
|
186
|
+
mode.into(),
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
.into()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
impl<T: 'static> Drop for ThreadsafeFunction<T> {
|
|
194
|
+
fn drop(&mut self) {
|
|
195
|
+
if !self.aborted.load(Ordering::Acquire) && self.ref_count.load(Ordering::Acquire) > 0usize
|
|
196
|
+
{
|
|
197
|
+
let release_status = unsafe {
|
|
198
|
+
sys::napi_release_threadsafe_function(
|
|
199
|
+
self.raw_tsfn,
|
|
200
|
+
sys::ThreadsafeFunctionReleaseMode::release,
|
|
201
|
+
)
|
|
202
|
+
};
|
|
203
|
+
assert!(
|
|
204
|
+
release_status == sys::Status::napi_ok,
|
|
205
|
+
"Threadsafe Function release failed"
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
unsafe extern "C" fn cleanup_cb(cleanup_data: *mut c_void) {
|
|
212
|
+
let aborted = Arc::<AtomicBool>::from_raw(cleanup_data.cast());
|
|
213
|
+
aborted.store(true, Ordering::SeqCst);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
unsafe extern "C" fn thread_finalize_cb<T: 'static, R>(
|
|
217
|
+
_raw_env: sys::napi_env,
|
|
218
|
+
finalize_data: *mut c_void,
|
|
219
|
+
_finalize_hint: *mut c_void,
|
|
220
|
+
) where
|
|
221
|
+
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
|
|
222
|
+
{
|
|
223
|
+
// cleanup
|
|
224
|
+
drop(Box::<R>::from_raw(finalize_data.cast()));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
unsafe extern "C" fn call_js_cb<T: 'static, R>(
|
|
228
|
+
raw_env: sys::napi_env,
|
|
229
|
+
js_callback: sys::napi_value,
|
|
230
|
+
context: *mut c_void,
|
|
231
|
+
data: *mut c_void,
|
|
232
|
+
) where
|
|
233
|
+
R: 'static + Send + FnMut(ThreadSafeCallContext<T>) -> Result<()>,
|
|
234
|
+
{
|
|
235
|
+
// env and/or callback can be null when shutting down
|
|
236
|
+
if raw_env.is_null() || js_callback.is_null() {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let ctx: &mut R = &mut *context.cast::<R>();
|
|
241
|
+
let val: Result<T> = Ok(*Box::<T>::from_raw(data.cast()));
|
|
242
|
+
|
|
243
|
+
let mut recv = ptr::null_mut();
|
|
244
|
+
sys::napi_get_undefined(raw_env, &mut recv);
|
|
245
|
+
|
|
246
|
+
let ret = val.and_then(|v| {
|
|
247
|
+
(ctx)(ThreadSafeCallContext {
|
|
248
|
+
env: Env::from_raw(raw_env),
|
|
249
|
+
value: v,
|
|
250
|
+
callback: JsFunction::from_raw(raw_env, js_callback).unwrap(), // TODO: unwrap
|
|
251
|
+
})
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
let status = match ret {
|
|
255
|
+
Ok(()) => sys::Status::napi_ok,
|
|
256
|
+
Err(e) => sys::napi_fatal_exception(raw_env, JsError::from(e).into_value(raw_env)),
|
|
257
|
+
};
|
|
258
|
+
if status == sys::Status::napi_ok {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if status == sys::Status::napi_pending_exception {
|
|
262
|
+
let mut error_result = ptr::null_mut();
|
|
263
|
+
assert_eq!(
|
|
264
|
+
sys::napi_get_and_clear_last_exception(raw_env, &mut error_result),
|
|
265
|
+
sys::Status::napi_ok
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// When shutting down, napi_fatal_exception sometimes returns another exception
|
|
269
|
+
let stat = sys::napi_fatal_exception(raw_env, error_result);
|
|
270
|
+
assert!(stat == sys::Status::napi_ok || stat == sys::Status::napi_pending_exception);
|
|
271
|
+
} else {
|
|
272
|
+
let error_code: Status = status.into();
|
|
273
|
+
let error_code_string = format!("{error_code:?}");
|
|
274
|
+
let mut error_code_value = ptr::null_mut();
|
|
275
|
+
assert_eq!(
|
|
276
|
+
sys::napi_create_string_utf8(
|
|
277
|
+
raw_env,
|
|
278
|
+
error_code_string.as_ptr().cast(),
|
|
279
|
+
error_code_string.len(),
|
|
280
|
+
&mut error_code_value,
|
|
281
|
+
),
|
|
282
|
+
sys::Status::napi_ok,
|
|
283
|
+
);
|
|
284
|
+
let error_msg = "Call JavaScript callback failed in thread safe function";
|
|
285
|
+
let mut error_msg_value = ptr::null_mut();
|
|
286
|
+
assert_eq!(
|
|
287
|
+
sys::napi_create_string_utf8(
|
|
288
|
+
raw_env,
|
|
289
|
+
error_msg.as_ptr().cast(),
|
|
290
|
+
error_msg.len(),
|
|
291
|
+
&mut error_msg_value,
|
|
292
|
+
),
|
|
293
|
+
sys::Status::napi_ok,
|
|
294
|
+
);
|
|
295
|
+
let mut error_value = ptr::null_mut();
|
|
296
|
+
assert_eq!(
|
|
297
|
+
sys::napi_create_error(raw_env, error_code_value, error_msg_value, &mut error_value),
|
|
298
|
+
sys::Status::napi_ok,
|
|
299
|
+
);
|
|
300
|
+
assert_eq!(
|
|
301
|
+
sys::napi_fatal_exception(raw_env, error_value),
|
|
302
|
+
sys::Status::napi_ok
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
}
|