• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <cstring>
17 #include <vector>
18 #include <string>
19 
20 #include "interop-logging.h"
21 #include "convertors-napi.h"
22 
23 // NOLINTBEGIN
24 
25 // Adapter for NAPI_MODULE
26 #define NODE_API_MODULE_ADAPTER(modname, regfunc)                        \
27     static napi_value __napi_##regfunc(napi_env env, napi_value exports) \
28     {                                                                    \
29         /* CC-OFFNXT(G.PRE.05) code generation */                        \
30         return Napi::RegisterModule(env, exports, regfunc);              \
31     }                                                                    \
32     NAPI_MODULE(modname, __napi_##regfunc)
33 
getValueTypeChecked(napi_env env,napi_value value)34 napi_valuetype getValueTypeChecked(napi_env env, napi_value value)
35 {
36     napi_valuetype type;
37     napi_status status = napi_typeof(env, value, &type);
38     TS_NAPI_THROW_IF_FAILED(env, status, napi_undefined);
39     return type;
40 }
41 
isTypedArray(napi_env env,napi_value value)42 bool isTypedArray(napi_env env, napi_value value)
43 {
44     bool result = false;
45     napi_status status = napi_is_typedarray(env, value, &result);
46     TS_NAPI_THROW_IF_FAILED(env, status, false);
47     return result;
48 }
49 
getBoolean(napi_env env,napi_value value)50 KBoolean getBoolean(napi_env env, napi_value value)
51 {
52     if (getValueTypeChecked(env, value) == napi_valuetype::napi_boolean) {
53         bool result = false;
54         napi_get_value_bool(env, value, &result);
55         return static_cast<KBoolean>(result);
56     }
57     return static_cast<KBoolean>(getInt32(env, value) != 0);
58 }
59 
getInt32(napi_env env,napi_value value)60 KInt getInt32(napi_env env, napi_value value)
61 {
62     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
63         napi_throw_error(env, nullptr, "Expected Number");
64         return 0;
65     }
66     int32_t result = 0;
67     napi_get_value_int32(env, value, &result);
68     return static_cast<KInt>(result);
69 }
70 
getUInt32(napi_env env,napi_value value)71 KUInt getUInt32(napi_env env, napi_value value)
72 {
73     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
74         napi_throw_error(env, nullptr, "Expected Number");
75         return 0;
76     }
77     uint32_t result = 0U;
78     napi_get_value_uint32(env, value, &result);
79     return static_cast<KUInt>(result);
80 }
81 
getFloat32(napi_env env,napi_value value)82 KFloat getFloat32(napi_env env, napi_value value)
83 {
84     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
85         napi_throw_error(env, nullptr, "Expected Number");
86         return 0.0F;
87     }
88     double result = 0.0;
89     napi_get_value_double(env, value, &result);
90     return static_cast<KFloat>(static_cast<float>(result));
91 }
92 
getFloat64(napi_env env,napi_value value)93 KDouble getFloat64(napi_env env, napi_value value)
94 {
95     if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
96         napi_throw_error(env, nullptr, "Expected Number");
97         return 0.0;
98     }
99     double result = 0.0;
100     napi_get_value_double(env, value, &result);
101     return static_cast<KDouble>(result);
102 }
103 
getString(napi_env env,napi_value value)104 KStringPtr getString(napi_env env, napi_value value)
105 {
106     KStringPtr result {};
107     napi_valuetype valueType = getValueTypeChecked(env, value);
108     if (valueType == napi_valuetype::napi_null || valueType == napi_valuetype::napi_undefined) {
109         return result;
110     }
111 
112     if (valueType != napi_valuetype::napi_string) {
113         napi_throw_error(env, nullptr, "Expected String");
114         return result;
115     }
116 
117     size_t length = 0;
118     napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &length);
119     if (status != 0) {
120         return result;
121     }
122     result.resize(length);
123     status = napi_get_value_string_utf8(env, value, result.data(), length + 1, nullptr);
124     TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
125     return result;
126 }
127 
getPointer(napi_env env,napi_value value)128 KNativePointer getPointer(napi_env env, napi_value value)
129 {
130     napi_valuetype valueType = getValueTypeChecked(env, value);
131     if (valueType == napi_valuetype::napi_external) {
132         KNativePointer result = nullptr;
133         napi_status status = napi_get_value_external(env, value, &result);
134         TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
135         return result;
136     }
137 
138     if (valueType != napi_valuetype::napi_bigint) {
139         napi_throw_error(env, nullptr, "cannot be coerced to pointer");
140         return nullptr;
141     }
142 
143     bool isWithinRange = true;
144     uint64_t ptrU64 = 0;
145     napi_status status = napi_get_value_bigint_uint64(env, value, &ptrU64, &isWithinRange);
146     TS_NAPI_THROW_IF_FAILED(env, status, nullptr);
147     if (!isWithinRange) {
148         napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
149         return nullptr;
150     }
151     return reinterpret_cast<KNativePointer>(ptrU64);
152 }
153 
getInt64(napi_env env,napi_value value)154 KLong getInt64(napi_env env, napi_value value)
155 {
156     if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
157         napi_throw_error(env, nullptr, "cannot be coerced to int64");
158         return -1;
159     }
160 
161     bool isWithinRange = true;
162     int64_t ptr64 = 0;
163     napi_get_value_bigint_int64(env, value, &ptr64, &isWithinRange);
164     if (!isWithinRange) {
165         napi_throw_error(env, nullptr, "cannot be coerced to int64, value is too large");
166         return -1;
167     }
168     return static_cast<KLong>(ptr64);
169 }
170 
makeString(napi_env env,const KStringPtr & value)171 napi_value makeString(napi_env env, const KStringPtr &value)
172 {
173     napi_value result;
174     napi_status status = napi_create_string_utf8(env, value.isNull() ? "" : value.data(), value.length(), &result);
175     TS_NAPI_THROW_IF_FAILED(env, status, result);
176     return result;
177 }
178 
makeString(napi_env env,const std::string & value)179 napi_value makeString(napi_env env, const std::string &value)
180 {
181     napi_value result;
182     napi_status status = napi_create_string_utf8(env, value.c_str(), value.length(), &result);
183     TS_NAPI_THROW_IF_FAILED(env, status, result);
184     return result;
185 }
186 
makeBoolean(napi_env env,int8_t value)187 napi_value makeBoolean(napi_env env, int8_t value)
188 {
189     napi_value result;
190     napi_status status = napi_get_boolean(env, value != 0, &result);
191     TS_NAPI_THROW_IF_FAILED(env, status, result);
192     return result;
193 }
194 
makeInt32(napi_env env,int32_t value)195 napi_value makeInt32(napi_env env, int32_t value)
196 {
197     napi_value result;
198     napi_status status = napi_create_int32(env, value, &result);
199     TS_NAPI_THROW_IF_FAILED(env, status, result);
200     return result;
201 }
202 
makeUInt32(napi_env env,uint32_t value)203 napi_value makeUInt32(napi_env env, uint32_t value)
204 {
205     napi_value result;
206     napi_status status = napi_create_uint32(env, value, &result);
207     TS_NAPI_THROW_IF_FAILED(env, status, result);
208     return result;
209 }
210 
makeFloat32(napi_env env,float value)211 napi_value makeFloat32(napi_env env, float value)
212 {
213     napi_value result;
214     napi_status status = napi_create_double(env, value, &result);
215     TS_NAPI_THROW_IF_FAILED(env, status, result);
216     return result;
217 }
218 
makePointer(napi_env env,void * value)219 napi_value makePointer(napi_env env, void *value)
220 {
221     napi_value result;
222     napi_status status =
223         napi_create_bigint_uint64(env, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value)), &result);
224     TS_NAPI_THROW_IF_FAILED(env, status, result);
225     return result;
226 }
227 
makeVoid(napi_env env)228 napi_value makeVoid(napi_env env)
229 {
230     napi_value result;
231     napi_status status = napi_get_undefined(env, &result);
232     TS_NAPI_THROW_IF_FAILED(env, status, result);
233     return result;
234 }
235 
makeObject(napi_env env,napi_value object)236 napi_value makeObject(napi_env env, [[maybe_unused]] napi_value object)
237 {
238     napi_value result;
239     napi_status status = napi_create_object(env, &result);
240     TS_NAPI_THROW_IF_FAILED(env, status, result);
241     return result;
242 }
243 
244 #if _MSC_VER >= 1932  // Visual Studio 2022 version 17.2+
245 #pragma comment(linker, "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
246 #pragma comment(linker, "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
247 #endif
248 
getInstance()249 Exports *Exports::getInstance()
250 {
251     static Exports *instance = nullptr;
252     if (instance == nullptr) {
253         instance = new Exports();
254     }
255     return instance;
256 }
257 
getModules()258 std::vector<std::string> Exports::getModules()
259 {
260     std::vector<std::string> result;
261     for (auto it = implementations.begin(); it != implementations.end(); ++it) {
262         result.push_back(it->first);
263     }
264     return result;
265 }
266 
addMethod(const char * module,const char * name,napi_type_t impl)267 void Exports::addMethod(const char *module, const char *name, napi_type_t impl)
268 {
269     auto it = implementations.find(module);
270     if (it == implementations.end()) {
271         it = implementations.insert(std::make_pair(module, std::vector<std::pair<std::string, napi_type_t>>())).first;
272     }
273     it->second.push_back(std::make_pair(name, impl));
274 }
275 
getMethods(const std::string & module)276 const std::vector<std::pair<std::string, napi_type_t>> &Exports::getMethods(const std::string &module)
277 {
278     auto it = implementations.find(module);
279     if (it == implementations.end()) {
280         LOGE("Module %s is not registered", module.c_str());
281         throw "Fatal error";
282     }
283     return it->second;
284 }
285 
286 //
287 // Callback dispatcher
288 //
289 // NOTE(khil): Should we get rid of explicit Node_* declrations and hide the naming convention behind the macro
290 // definitions?
291 
292 static napi_ref g_koalaNapiCallbackDispatcher = nullptr;
293 
294 // NOTE(khil): shall we pass name in globalThis instead of object reference?
Node_SetCallbackDispatcher(napi_env env,napi_callback_info cbinfo)295 napi_value Node_SetCallbackDispatcher(napi_env env, napi_callback_info cbinfo)
296 {
297     CallbackInfo info(env, cbinfo);
298     napi_value dispatcher = info[0];
299     napi_value result = makeVoid(env);
300     napi_status status = napi_create_reference(env, dispatcher, 1, &g_koalaNapiCallbackDispatcher);
301     TS_NAPI_THROW_IF_FAILED(env, status, result);
302 
303     return result;
304 }
MAKE_NODE_EXPORT(TS_INTEROP_MODULE,SetCallbackDispatcher)305 MAKE_NODE_EXPORT(TS_INTEROP_MODULE, SetCallbackDispatcher)
306 
307 napi_value Node_CleanCallbackDispatcher(napi_env env, [[maybe_unused]] napi_callback_info cbinfo)
308 {
309     napi_value result = makeVoid(env);
310     if (g_koalaNapiCallbackDispatcher) {
311         napi_status status = napi_delete_reference(env, g_koalaNapiCallbackDispatcher);
312         g_koalaNapiCallbackDispatcher = nullptr;
313         TS_NAPI_THROW_IF_FAILED(env, status, result);
314     }
315     return result;
316 }
MAKE_NODE_EXPORT(TS_INTEROP_MODULE,CleanCallbackDispatcher)317 MAKE_NODE_EXPORT(TS_INTEROP_MODULE, CleanCallbackDispatcher)
318 
319 napi_value getKoalaNapiCallbackDispatcher(napi_env env)
320 {
321     if (!g_koalaNapiCallbackDispatcher) {
322         abort();
323     }
324     napi_value value;
325     napi_status status = napi_get_reference_value(env, g_koalaNapiCallbackDispatcher, &value);
326     TS_NAPI_THROW_IF_FAILED(env, status, makeVoid(env));
327     return value;
328 }
329 
330 // Module initialization
331 using ModuleRegisterCallback = napi_value (*)(napi_env env, napi_value exports);
332 
333 /**
334  * Sets a new callback and returns its previous value.
335  */
ProvideModuleRegisterCallback(ModuleRegisterCallback value=nullptr)336 ModuleRegisterCallback ProvideModuleRegisterCallback(ModuleRegisterCallback value = nullptr)
337 {
338     static const ModuleRegisterCallback DEFAULT_CB = []([[maybe_unused]] napi_env env, napi_value exports) {
339         return exports;
340     };
341     static ModuleRegisterCallback curCallback = DEFAULT_CB;
342 
343     ModuleRegisterCallback prevCallback = curCallback;
344     curCallback = value != nullptr ? value : DEFAULT_CB;
345     return prevCallback;
346 }
347 
348 static constexpr bool SPLIT_MODULES = true;
349 
InitModule(napi_env env,napi_value exports)350 static napi_value InitModule(napi_env env, napi_value exports)
351 {
352     Exports *inst = Exports::getInstance();
353     napi_status status;
354     napi_value target = exports;
355     for (const auto &module : inst->getModules()) {
356         if (SPLIT_MODULES) {
357             status = napi_create_object(env, &target);
358             TS_NAPI_THROW_IF_FAILED(env, status, exports);
359             status = napi_set_named_property(env, exports, module.c_str(), target);
360             TS_NAPI_THROW_IF_FAILED(env, status, exports);
361         }
362 
363         for (const auto &impl : inst->getMethods(module)) {
364             napi_value implFunc;
365             status = napi_create_function(env, impl.first.c_str(), 0, impl.second, nullptr, &implFunc);
366             TS_NAPI_THROW_IF_FAILED(env, status, exports);
367             status = napi_set_named_property(env, target, impl.first.c_str(), implFunc);
368             TS_NAPI_THROW_IF_FAILED(env, status, exports);
369         }
370     }
371     return ProvideModuleRegisterCallback()(env, exports);
372 }
373 
374 NAPI_MODULE(INTEROP_LIBRARY_NAME, InitModule)
375 
376 // NOLINTEND
377