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
2 changes: 2 additions & 0 deletions packages/host/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ add_library(node-api-host SHARED
../cpp/Logger.cpp
../cpp/CxxNodeApiHostModule.cpp
../cpp/WeakNodeApiInjector.cpp
../cpp/RuntimeNodeApi.cpp
../cpp/RuntimeNodeApi.hpp
)

target_include_directories(node-api-host PRIVATE
Expand Down
112 changes: 112 additions & 0 deletions packages/host/cpp/RuntimeNodeApi.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#include "RuntimeNodeApi.hpp"
#include <string>

auto ArrayType = napi_uint8_array;

napi_status NAPI_CDECL callstack::nodeapihost::napi_create_buffer(
napi_env env, size_t length, void** data, napi_value* result) {
napi_value buffer;
const auto status = napi_create_arraybuffer(env, length, data, &buffer);
if (status != napi_ok) {
return status;
}

// Warning: The returned data structure does not fully align with the
// characteristics of a Buffer.
// @see
// https://github.com/callstackincubator/react-native-node-api/issues/171
return napi_create_typedarray(env, ArrayType, length, buffer, 0, result);
}

napi_status NAPI_CDECL callstack::nodeapihost::napi_create_buffer_copy(
napi_env env,
size_t length,
const void* data,
void** result_data,
napi_value* result) {
if (!length || !data || !result) {
return napi_invalid_arg;
}

void* buffer = nullptr;
if (const auto status = ::napi_create_buffer(env, length, &buffer, result);
status != napi_ok) {
return status;
}

std::memcpy(buffer, data, length);
return napi_ok;
}

napi_status callstack::nodeapihost::napi_is_buffer(
napi_env env, napi_value value, bool* result) {
if (!result) {
return napi_invalid_arg;
}

if (!value) {
*result = false;
return napi_ok;
}

napi_valuetype type{};
if (const auto status = napi_typeof(env, value, &type); status != napi_ok) {
return status;
}

if (type != napi_object && type != napi_external) {
*result = false;
return napi_ok;
}

auto isArrayBuffer{false};
if (const auto status = napi_is_arraybuffer(env, value, &isArrayBuffer);
status != napi_ok) {
return status;
}
auto isTypedArray{false};
if (const auto status = napi_is_typedarray(env, value, &isTypedArray);
status != napi_ok) {
return status;
}

*result = isArrayBuffer || isTypedArray;
return napi_ok;
}

napi_status callstack::nodeapihost::napi_get_buffer_info(
napi_env env, napi_value value, void** data, size_t* length) {
if (!data || !length) {
return napi_invalid_arg;
}
*data = nullptr;
*length = 0;
if (!value) {
return napi_ok;
}

auto isArrayBuffer{false};
if (const auto status = napi_is_arraybuffer(env, value, &isArrayBuffer);
status == napi_ok && isArrayBuffer) {
return napi_get_arraybuffer_info(env, value, data, length);
}

auto isTypedArray{false};
if (const auto status = napi_is_typedarray(env, value, &isTypedArray);
status == napi_ok && isTypedArray) {
return napi_get_typedarray_info(
env, value, &ArrayType, length, data, nullptr, nullptr);
}

return napi_ok;
}

napi_status callstack::nodeapihost::napi_create_external_buffer(napi_env env,
size_t length,
void* data,
node_api_basic_finalize basic_finalize_cb,
void* finalize_hint,
napi_value* result) {
return napi_create_external_arraybuffer(
env, data, length, basic_finalize_cb, finalize_hint, result);
}
25 changes: 25 additions & 0 deletions packages/host/cpp/RuntimeNodeApi.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "node_api.h"

namespace callstack::nodeapihost {
napi_status napi_create_buffer(
napi_env env, size_t length, void** data, napi_value* result);

napi_status napi_create_buffer_copy(napi_env env,
size_t length,
const void* data,
void** result_data,
napi_value* result);

napi_status napi_is_buffer(napi_env env, napi_value value, bool* result);

napi_status napi_get_buffer_info(
napi_env env, napi_value value, void** data, size_t* length);

napi_status napi_create_external_buffer(napi_env env,
size_t length,
void* data,
node_api_basic_finalize basic_finalize_cb,
void* finalize_hint,
napi_value* result);

} // namespace callstack::nodeapihost
17 changes: 15 additions & 2 deletions packages/host/scripts/generate-weak-node-api-injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import { FunctionDecl, getNodeApiFunctions } from "./node-api-functions";

export const CPP_SOURCE_PATH = path.join(__dirname, "../cpp");

// TODO: Remove when all runtime Node API functions are implemented
const IMPLEMENTED_RUNTIME_FUNCTIONS = [
"napi_create_buffer",
"napi_create_buffer_copy",
"napi_is_buffer",
"napi_get_buffer_info",
"napi_create_external_buffer",
];

/**
* Generates source code which injects the Node API functions from the host.
*/
Expand All @@ -15,7 +24,8 @@ export function generateSource(functions: FunctionDecl[]) {
#include <Logger.hpp>
#include <dlfcn.h>
#include <weak_node_api.hpp>

#include <RuntimeNodeApi.hpp>

#if defined(__APPLE__)
#define WEAK_NODE_API_LIBRARY_NAME "@rpath/weak-node-api.framework/weak-node-api"
#elif defined(__ANDROID__)
Expand Down Expand Up @@ -43,7 +53,10 @@ export function generateSource(functions: FunctionDecl[]) {
log_debug("Injecting WeakNodeApiHost");
inject_weak_node_api_host(WeakNodeApiHost {
${functions
.filter(({ kind }) => kind === "engine")
.filter(
({ kind, name }) =>
kind === "engine" || IMPLEMENTED_RUNTIME_FUNCTIONS.includes(name)
)
.flatMap(({ name }) => `.${name} = ${name},`)
.join("\n")}
});
Expand Down
5 changes: 4 additions & 1 deletion packages/node-addon-examples/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ module.exports = {
"5-async-work": {
// TODO: This crashes (SIGABRT)
// "async_work_thread_safe_function": () => require("./examples/5-async-work/async_work_thread_safe_function/napi/index.js"),
}
},
"tests": {
"buffers": () => require("./tests/buffers/addon.js"),
},
};
2 changes: 1 addition & 1 deletion packages/node-addon-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"scripts": {
"copy-examples": "tsx scripts/copy-examples.mts",
"gyp-to-cmake": "gyp-to-cmake ./examples",
"gyp-to-cmake": "gyp-to-cmake .",
"build": "tsx scripts/build-examples.mts",
"copy-and-build": "npm run copy-examples && npm run gyp-to-cmake && npm run build",
"verify": "tsx scripts/verify-prebuilds.mts",
Expand Down
10 changes: 8 additions & 2 deletions packages/node-addon-examples/scripts/cmake-projects.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ import { readdirSync, statSync } from "node:fs";
import path from "node:path";

export const EXAMPLES_DIR = path.resolve(import.meta.dirname, "../examples");
export const TESTS_DIR = path.resolve(import.meta.dirname, "../tests");
export const DIRS = [EXAMPLES_DIR, TESTS_DIR];

export function findCMakeProjects(dir = EXAMPLES_DIR): string[] {
export function findCMakeProjectsRecursively(dir): string[] {
let results: string[] = [];
const files = readdirSync(dir);

for (const file of files) {
const fullPath = path.join(dir, file);
if (statSync(fullPath).isDirectory()) {
results = results.concat(findCMakeProjects(fullPath));
results = results.concat(findCMakeProjectsRecursively(fullPath));
} else if (file === "CMakeLists.txt") {
results.push(dir);
}
}

return results;
}

export function findCMakeProjects(): string[] {
return DIRS.flatMap(findCMakeProjectsRecursively);
}
1 change: 1 addition & 0 deletions packages/node-addon-examples/tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
15 changes: 15 additions & 0 deletions packages/node-addon-examples/tests/buffers/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.15)
project(tests-buffers)

add_compile_definitions(NAPI_VERSION=8)

add_library(addon SHARED addon.c ${CMAKE_JS_SRC})
set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node")
target_include_directories(addon PRIVATE ${CMAKE_JS_INC})
target_link_libraries(addon PRIVATE ${CMAKE_JS_LIB})
target_compile_features(addon PRIVATE cxx_std_17)

if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
# Generate node.lib
execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
endif()
Loading
Loading