Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ts_callback_utils.cpp
Go to the documentation of this file.
2
3#include <sstream>
4#include <stdexcept>
5
8
9namespace bb::nodejs {
10
11std::string extract_error_from_napi_value(const Napi::CallbackInfo& cb_info)
12{
13 if (cb_info.Length() > 0) {
14 if (cb_info[0].IsString()) {
15 return cb_info[0].As<Napi::String>().Utf8Value();
16 }
17 if (cb_info[0].IsObject()) {
18 auto err_obj = cb_info[0].As<Napi::Object>();
19 auto msg = err_obj.Get("message");
20 if (msg.IsString()) {
21 return msg.As<Napi::String>().Utf8Value();
22 }
23 }
24 }
25 return "Unknown error from TypeScript";
26}
27
28Napi::Function create_buffer_resolve_handler(Napi::Env env, std::shared_ptr<CallbackResults> cb_results)
29{
30 // Capture shared_ptr by value to ensure CallbackResults outlives the Promise handler.
31 // This prevents use-after-free when timeouts occur before the Promise resolves.
32 return Napi::Function::New(
33 env,
34 [cb_results](const Napi::CallbackInfo& cb_info) -> Napi::Value {
35 Napi::Env env = cb_info.Env();
36 try {
37 // Check if first arg is undefined or null
38 if (cb_info.Length() > 0 && !cb_info[0].IsUndefined() && !cb_info[0].IsNull()) {
39 // Check if the first argument is a buffer
40 if (cb_info[0].IsBuffer()) {
41 auto buffer = cb_info[0].As<Napi::Buffer<uint8_t>>();
42 std::vector<uint8_t> vec(buffer.Data(), buffer.Data() + buffer.Length());
43 cb_results->result_promise.set_value(std::move(vec));
44 } else {
45 cb_results->error_message = "Callback returned non-Buffer value";
46 cb_results->result_promise.set_value(std::nullopt);
47 }
48 } else {
49 // Got undefined/null - not found
50 cb_results->result_promise.set_value(std::nullopt);
51 }
52 } catch (const std::exception& e) {
53 cb_results->error_message = std::string("Exception in resolve handler: ") + e.what();
54 cb_results->result_promise.set_value(std::nullopt);
55 }
56 return env.Undefined();
57 },
58 "resolveHandler");
59}
60
61Napi::Function create_string_resolve_handler(Napi::Env env, std::shared_ptr<CallbackResults> cb_results)
62{
63 // Capture shared_ptr by value to ensure CallbackResults outlives the Promise handler.
64 return Napi::Function::New(
65 env,
66 [cb_results](const Napi::CallbackInfo& cb_info) -> Napi::Value {
67 Napi::Env env = cb_info.Env();
68 try {
69 // Check if first arg is undefined or null
70 if (cb_info.Length() > 0 && !cb_info[0].IsUndefined() && !cb_info[0].IsNull()) {
71 // Check if the first argument is a string
72 if (cb_info[0].IsString()) {
73 std::string name = cb_info[0].As<Napi::String>().Utf8Value();
74 std::vector<uint8_t> vec(name.begin(), name.end());
75 cb_results->result_promise.set_value(std::move(vec));
76 } else {
77 cb_results->error_message = "Callback returned non-string value";
78 cb_results->result_promise.set_value(std::nullopt);
79 }
80 } else {
81 // Got undefined/null - not found
82 cb_results->result_promise.set_value(std::nullopt);
83 }
84 } catch (const std::exception& e) {
85 cb_results->error_message = std::string("Exception in resolve handler: ") + e.what();
86 cb_results->result_promise.set_value(std::nullopt);
87 }
88 return env.Undefined();
89 },
90 "resolveHandler");
91}
92
93Napi::Function create_void_resolve_handler(Napi::Env env, std::shared_ptr<CallbackResults> cb_results)
94{
95 // Capture shared_ptr by value to ensure CallbackResults outlives the Promise handler.
96 return Napi::Function::New(
97 env,
98 [cb_results](const Napi::CallbackInfo& cb_info) -> Napi::Value {
99 cb_results->result_promise.set_value(std::nullopt);
100 return cb_info.Env().Undefined();
101 },
102 "resolveHandler");
103}
104
105Napi::Function create_reject_handler(Napi::Env env, std::shared_ptr<CallbackResults> cb_results)
106{
107 // Capture shared_ptr by value to ensure CallbackResults outlives the Promise handler.
108 return Napi::Function::New(
109 env,
110 [cb_results](const Napi::CallbackInfo& cb_info) -> Napi::Value {
111 cb_results->error_message = extract_error_from_napi_value(cb_info);
112 cb_results->result_promise.set_value(std::nullopt);
113 return cb_info.Env().Undefined();
114 },
115 "rejectHandler");
116}
117
118void attach_promise_handlers(Napi::Promise promise, Napi::Function resolve_handler, Napi::Function reject_handler)
119{
120 auto then_prop = promise.Get("then");
121 if (!then_prop.IsFunction()) {
122 throw std::runtime_error("Promise does not have .then() method");
123 }
124
125 auto then_fn = then_prop.As<Napi::Function>();
126 then_fn.Call(promise, { resolve_handler, reject_handler });
127}
128
129template <typename T> std::vector<uint8_t> serialize_to_msgpack(const T& data)
130{
131 msgpack::sbuffer buffer;
132 msgpack::pack(buffer, data);
133 return std::vector<uint8_t>(buffer.data(), buffer.data() + buffer.size());
134}
135
136template <typename T> T deserialize_from_msgpack(const std::vector<uint8_t>& data, const std::string& type_name)
137{
138 try {
139 T result;
140 msgpack::object_handle obj_handle = msgpack::unpack(reinterpret_cast<const char*>(data.data()), data.size());
141 msgpack::object obj = obj_handle.get();
142 obj.convert(result);
143 return result;
144 } catch (const std::exception& e) {
145 throw std::runtime_error(std::string("Failed to deserialize ") + type_name + ": " + e.what());
146 }
147}
148
150 const Napi::ThreadSafeFunction& callback,
151 const std::string& operation_name,
152 std::function<void(Napi::Env, Napi::Function, std::shared_ptr<CallbackResults>)> call_js_function,
153 std::chrono::seconds timeout)
154{
155 // Create promise/future pair for synchronization.
156 // The shared_ptr is passed to call_js_function which MUST capture it in Promise handlers.
157 // This ensures CallbackResults outlives the Promise, even if we timeout and return early.
158 auto callback_data = std::make_shared<CallbackResults>();
159 auto future = callback_data->result_promise.get_future();
160
161 // Call TypeScript callback on the JS main thread.
162 // We pass the shared_ptr to the call_js_function so it can be captured by Promise handlers.
163 auto status = callback.BlockingCall(
164 callback_data.get(),
165 [call_js_function, callback_data](Napi::Env env, Napi::Function js_callback, CallbackResults* /*cb_results*/) {
166 try {
167 // Call the TypeScript function with the shared_ptr (not raw pointer).
168 // The call_js_function MUST capture this shared_ptr in Promise handlers.
169 call_js_function(env, js_callback, callback_data);
170
171 } catch (const std::exception& e) {
172 callback_data->error_message = std::string("Exception calling TypeScript: ") + e.what();
173 callback_data->result_promise.set_value(std::nullopt);
174 }
175 });
176
177 if (status != napi_ok) {
178 throw std::runtime_error("Failed to invoke TypeScript callback for " + operation_name);
179 }
180
181 // Wait for the promise to be fulfilled (with timeout).
182 // If timeout occurs, we throw but callback_data stays alive via shared_ptr in Promise handlers.
183 auto wait_status = future.wait_for(timeout);
184 if (wait_status == std::future_status::timeout) {
185 throw std::runtime_error("Timeout waiting for TypeScript callback for " + operation_name);
186 }
187
188 // Get the result
189 auto result_data = future.get();
190
191 // Check for errors
192 if (!callback_data->error_message.empty()) {
193 throw std::runtime_error("Error from TypeScript callback: " + callback_data->error_message);
194 }
195
196 return result_data;
197}
198
200 const std::string& input_str,
201 const std::string& operation_name)
202{
204 callback,
205 operation_name,
206 [input_str](Napi::Env env, Napi::Function js_callback, std::shared_ptr<CallbackResults> cb_results) {
207 auto js_input = Napi::String::New(env, input_str);
208 auto js_result = js_callback.Call({ js_input });
209
210 if (!js_result.IsPromise()) {
211 cb_results->error_message = "TypeScript callback did not return a Promise";
212 cb_results->result_promise.set_value(std::nullopt);
213 return;
214 }
215
216 auto promise = js_result.As<Napi::Promise>();
217 // Pass shared_ptr to handlers so CallbackResults outlives the Promise
218 auto resolve_handler = create_buffer_resolve_handler(env, cb_results);
219 auto reject_handler = create_reject_handler(env, cb_results);
220 attach_promise_handlers(promise, resolve_handler, reject_handler);
221 });
222}
223
225 const std::string& input_str1,
226 const std::string& input_str2,
227 const std::string& operation_name)
228{
230 callback,
231 operation_name,
232 [input_str1,
233 input_str2](Napi::Env env, Napi::Function js_callback, std::shared_ptr<CallbackResults> cb_results) {
234 auto js_input1 = Napi::String::New(env, input_str1);
235 auto js_input2 = Napi::String::New(env, input_str2);
236 auto js_result = js_callback.Call({ js_input1, js_input2 });
237
238 if (!js_result.IsPromise()) {
239 cb_results->error_message = "TypeScript callback did not return a Promise";
240 cb_results->result_promise.set_value(std::nullopt);
241 return;
242 }
243
244 auto promise = js_result.As<Napi::Promise>();
245 // Pass shared_ptr to handlers so CallbackResults outlives the Promise
246 auto resolve_handler = create_string_resolve_handler(env, cb_results);
247 auto reject_handler = create_reject_handler(env, cb_results);
248 attach_promise_handlers(promise, resolve_handler, reject_handler);
249 });
250}
251
252void invoke_buffer_void_callback(const Napi::ThreadSafeFunction& callback,
253 std::vector<uint8_t> buffer_data,
254 const std::string& operation_name)
255{
257 callback,
258 operation_name,
259 [buffer_data = std::move(buffer_data)](
260 Napi::Env env, Napi::Function js_callback, std::shared_ptr<CallbackResults> cb_results) {
261 auto js_buffer = Napi::Buffer<uint8_t>::Copy(env, buffer_data.data(), buffer_data.size());
262 auto js_result = js_callback.Call({ js_buffer });
263
264 if (!js_result.IsPromise()) {
265 cb_results->error_message = "TypeScript callback did not return a Promise";
266 cb_results->result_promise.set_value(std::nullopt);
267 return;
268 }
269
270 auto promise = js_result.As<Napi::Promise>();
271 // Pass shared_ptr to handlers so CallbackResults outlives the Promise
272 auto resolve_handler = create_void_resolve_handler(env, cb_results);
273 auto reject_handler = create_reject_handler(env, cb_results);
274 attach_promise_handlers(promise, resolve_handler, reject_handler);
275 });
276
277 // For void callbacks, we just need to ensure no errors occurred
278 // The result itself is ignored (will be nullopt for void)
279}
280
281// Explicit template instantiations for types used in this codebase
282template std::vector<uint8_t> serialize_to_msgpack(const bb::avm2::ContractDeploymentData& data);
283template bb::avm2::ContractInstance deserialize_from_msgpack(const std::vector<uint8_t>& data,
284 const std::string& type_name);
285template bb::avm2::ContractClass deserialize_from_msgpack(const std::vector<uint8_t>& data,
286 const std::string& type_name);
287template bb::avm2::FF deserialize_from_msgpack(const std::vector<uint8_t>& data, const std::string& type_name);
288
289} // namespace bb::nodejs
const std::vector< MemoryValue > data
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
AvmFlavorSettings::FF FF
Definition field.hpp:10
std::vector< uint8_t > serialize_to_msgpack(const T &data)
Serializes data to msgpack format.
void attach_promise_handlers(Napi::Promise promise, Napi::Function resolve_handler, Napi::Function reject_handler)
Attaches resolve and reject handlers to a promise.
T deserialize_from_msgpack(const std::vector< uint8_t > &data, const std::string &type_name)
Deserializes msgpack data to a specific type.
Napi::Function create_reject_handler(Napi::Env env, std::shared_ptr< CallbackResults > cb_results)
Creates a reject handler for promises.
Napi::Function create_void_resolve_handler(Napi::Env env, std::shared_ptr< CallbackResults > cb_results)
Creates a resolve handler for promises that return void.
std::optional< std::vector< uint8_t > > invoke_single_string_callback(const Napi::ThreadSafeFunction &callback, const std::string &input_str, const std::string &operation_name)
Helper for callbacks that take a single string argument and return Buffer | undefined.
std::optional< std::vector< uint8_t > > invoke_ts_callback_with_promise(const Napi::ThreadSafeFunction &callback, const std::string &operation_name, std::function< void(Napi::Env, Napi::Function, std::shared_ptr< CallbackResults >)> call_js_function, std::chrono::seconds timeout)
Generic callback invoker that handles the full BlockingCall pattern.
Napi::Function create_buffer_resolve_handler(Napi::Env env, std::shared_ptr< CallbackResults > cb_results)
Creates a resolve handler for promises that return Buffer | undefined.
std::optional< std::vector< uint8_t > > invoke_double_string_callback(const Napi::ThreadSafeFunction &callback, const std::string &input_str1, const std::string &input_str2, const std::string &operation_name)
Helper for callbacks that take two string arguments and return string | undefined.
std::string extract_error_from_napi_value(const Napi::CallbackInfo &cb_info)
Extracts error message from a Napi value (string or Error object)
Napi::Function create_string_resolve_handler(Napi::Env env, std::shared_ptr< CallbackResults > cb_results)
Creates a resolve handler for promises that return string | undefined.
void invoke_buffer_void_callback(const Napi::ThreadSafeFunction &callback, std::vector< uint8_t > buffer_data, const std::string &operation_name)
Helper for callbacks that take a buffer and return void.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Helper struct to pass data between C++ worker thread and JS main thread.