Skip to Content
Examples14 · Native addon

14 · Native addon

A native addon is compiled code, loaded into the process like a shared library. This example loads bcrypt, a widely used one built on node-addon-api, through 3va’s Node-API (napi_*) layer.

Source: examples/14-native-addon

Needs 2.12.1 or newer. On 2.12.0 the asynchronous call at the end never prints its result when it is the only thing left to do, and addons that define classes with more than one member can crash on load. Both are fixed in 2.12.1.

Setup

cd examples/14-native-addon 3va install --allow-net=registry.npmjs.org

Run

3va run app.js \ --allow-ffi \ --allow-read=. \ --allow-read=/usr/bin/ldd \ --allow-read=/etc/alpine-release \ --allow-env=ELECTRON_RUN_AS_NODE
hash : $2b$10$T47i6obDeJnYP4aHhSMzGOCIzowPN9Z72yhZ3RPwME3QRUG0YbL/e right pass : true wrong pass : false async check : true

The hash differs on every run: it contains a random salt.

The code

app.js
const bcrypt = require('bcrypt'); const hash = bcrypt.hashSync('correct horse', 10); console.log('hash :', hash); console.log('right pass :', bcrypt.compareSync('correct horse', hash)); console.log('wrong pass :', bcrypt.compareSync('battery staple', hash)); // The async API runs on a native worker thread and completes on the event loop. bcrypt .hash('async secret', 10) .then((h) => bcrypt.compare('async secret', h)) .then((ok) => console.log('async check :', ok));

How it works

  • --allow-ffi is required for any .node file. A native addon runs outside the sandbox: it can do anything the process can, whatever the other flags say. Grant it only for addons you trust.
  • Why the other flags. bcrypt finds its prebuilt binary with node-gyp-build, which reads /usr/bin/ldd and /etc/alpine-release to tell glibc from musl, and checks ELECTRON_RUN_AS_NODE. Deny-by-default shows you exactly which reads a package makes.
  • Synchronous and asynchronous calls. hashSync blocks; hash queues native work (napi_create_async_work) and the event loop stays alive until it completes.
  • Handles are released. Values an addon creates while a native callback runs are freed when it returns, so a loop calling into an addon does not grow without bound.

Things to keep in mind

  • Linux and macOS prebuilds ship inside the package, so no compiler is needed.
  • bcrypt (node-addon-api) and @node-rs/argon2 (napi-rs) both load and give the same results as Node, synchronous and asynchronous. Another addon may still need a Node-API function 3va does not export, which shows up as an undefined symbol error when it loads.
  • napi_wrap finalizers run when the wrapped object is garbage collected and when the environment is torn down, so native memory an addon attaches to a JS object with napi_wrap is freed. napi_create_function no longer leaks a bridge per function either, so an addon that creates closures per request stays bounded.