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.orgRun
3va run app.js \
--allow-ffi \
--allow-read=. \
--allow-read=/usr/bin/ldd \
--allow-read=/etc/alpine-release \
--allow-env=ELECTRON_RUN_AS_NODEhash : $2b$10$T47i6obDeJnYP4aHhSMzGOCIzowPN9Z72yhZ3RPwME3QRUG0YbL/e
right pass : true
wrong pass : false
async check : trueThe 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-ffiis required for any.nodefile. 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.
bcryptfinds its prebuilt binary withnode-gyp-build, which reads/usr/bin/lddand/etc/alpine-releaseto tell glibc from musl, and checksELECTRON_RUN_AS_NODE. Deny-by-default shows you exactly which reads a package makes. - Synchronous and asynchronous calls.
hashSyncblocks;hashqueues 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 anundefined symbolerror when it loads.napi_wrapfinalizers 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 withnapi_wrapis freed.napi_create_functionno longer leaks a bridge per function either, so an addon that creates closures per request stays bounded.