
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@tybys/emnapi
Advanced tools
Node-API implementation for Emscripten, wasi-sdk and clang wasm32-unknown-unknown target, napi-rs support is comming soon.
Emscripten is the first class support target, currently thread related APIs are unavailable on wasm32-unknown-unknown and wasm32-wasi target.
This project aims to
See documentation for more details:
中文文档:
How to build Node-API official examples
You will need to install:
>= v16.15.0>= v8>= v3.1.9 / wasi-sdk / LLVM clang with wasm support>= v3.13There are several choices to get make for Windows user
mingw32-make%Path% then rename it to mingw32-makenmake in Visual Studio Developer Command Promptnmake in Visual Studio Developer Command PromptVerify your environment:
node -v
npm -v
emcc -v
# clang -v
# clang -print-targets # ensure wasm32 target exists
cmake --version
# if you use ninja
ninja --version
# if you use make
make -v
# if you use nmake in Visual Studio Developer Command Prompt
nmake /?
You need to set EMSDK and WASI_SDK_PATH environment variables.
git clone https://github.com/toyobayashi/emnapi.git
cd ./emnapi
npm install -g node-gyp
npm install
npm run build # output ./packages/*/dist
node ./script/release.js # output ./out
# test
npm run rebuild:test
npm test
npm install -D @tybys/emnapi
npm install @tybys/emnapi-runtime
# for non-emscripten
npm install @tybys/emnapi-core
Each package should match the same version.
Create hello.c.
#include <node_api.h>
#define NAPI_CALL(env, the_call) \
do { \
if ((the_call) != napi_ok) { \
const napi_extended_error_info *error_info; \
napi_get_last_error_info((env), &error_info); \
bool is_pending; \
const char* err_message = error_info->error_message; \
napi_is_exception_pending((env), &is_pending); \
if (!is_pending) { \
const char* error_message = err_message != NULL ? \
err_message : \
"empty error message"; \
napi_throw_error((env), NULL, error_message); \
} \
return NULL; \
} \
} while (0)
static napi_value js_hello(napi_env env, napi_callback_info info) {
napi_value world;
const char* str = "world";
NAPI_CALL(env, napi_create_string_utf8(env, str, NAPI_AUTO_LENGTH, &world));
return world;
}
NAPI_MODULE_INIT() {
napi_value hello;
NAPI_CALL(env, napi_create_function(env, "hello", NAPI_AUTO_LENGTH,
js_hello, NULL, &hello));
NAPI_CALL(env, napi_set_named_property(env, exports, "hello", hello));
return exports;
}
The C code is equivalant to the following JavaScript:
module.exports = (function (exports) {
const hello = function hello () {
// native code in js_hello
const world = 'world'
return world
}
exports.hello = hello
return exports
})(module.exports)
emcc -O3 \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32-emscripten \
--js-library=./node_modules/@tybys/emnapi/dist/library_napi.js \
-sEXPORTED_FUNCTIONS="['_malloc','_free']" \
-o hello.js \
hello.c \
-lemnapi
clang -O3 \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32-wasi \
--target=wasm32-wasi \
--sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
-mexec-model=reactor \
-Wl,--initial-memory=16777216 \
-Wl,--export-dynamic \
-Wl,--export=malloc \
-Wl,--export=free \
-Wl,--export=napi_register_wasm_v1 \
-Wl,--import-undefined \
-Wl,--export-table \
-o hello.wasm \
hello.c \
-lemnapi
Choose libdlmalloc.a or libemmalloc.a for malloc and free.
clang -O3 \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32 \
--target=wasm32 \
-nostdlib \
-Wl,--no-entry \
-Wl,--initial-memory=16777216 \
-Wl,--export-dynamic \
-Wl,--export=malloc \
-Wl,--export=free \
-Wl,--export=napi_register_wasm_v1 \
-Wl,--import-undefined \
-Wl,--export-table \
-o hello.wasm \
hello.c \
-lemnapi \
-ldlmalloc # -lemmalloc
To initialize emnapi, you need to import the emnapi runtime to create a Context by createContext or getDefaultContext first.
Each context owns isolated Node-API object such as napi_env, napi_value, napi_ref. If you have multiple emnapi modules, you should reuse the same Context across them.
declare namespace emnapi {
// module '@tybys/emnapi-runtime'
export class Context { /* ... */ }
/** Create a new context */
export function createContext (): Context
/** Create or get */
export function getDefaultContext (): Context
// ...
}
then call Module.emnapiInit after emscripten runtime initialized.
Module.emnapiInit only do initialization once, it will always return the same binding exports after successfully initialized.
declare namespace Module {
interface EmnapiInitOptions {
context: emnapi.Context
/** node_api_get_module_file_name */
filename?: string
/**
* Support following async_hooks related things
* on Node.js runtime only
*
* napi_async_init,
* napi_async_destroy,
* napi_make_callback,
* async resource parameter of
* napi_create_async_work and napi_create_threadsafe_function
*/
nodeBinding?: typeof import('@tybys/emnapi-node-binding')
}
export function emnapiInit (options: EmnapiInitOptions): any
}
<script src="node_modules/@tybys/emnapi-runtime/dist/emnapi.min.js"></script>
<script src="hello.js"></script>
<script>
Module.onRuntimeInitialized = function () {
var binding;
try {
binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
} catch (err) {
console.error(err);
return;
}
var msg = 'hello ' + binding.hello();
window.alert(msg);
};
// if -sMODULARIZE=1
Module({ /* Emscripten module init options */ }).then(function (Module) {
var binding = Module.emnapiInit({ context: emnapi.getDefaultContext() });
});
</script>
If you are using Visual Studio Code and have Live Server extension installed, you can right click the HTML file in Visual Studio Code source tree and click Open With Live Server, then you can see the hello world alert!
Running on Node.js:
const emnapi = require('@tybys/emnapi-runtime')
const Module = require('./hello.js')
Module.onRuntimeInitialized = function () {
let binding
try {
binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
} catch (err) {
console.error(err)
return
}
const msg = `hello ${binding.hello()}`
console.log(msg)
}
// if -sMODULARIZE=1
Module({ /* Emscripten module init options */ }).then((Module) => {
const binding = Module.emnapiInit({ context: emnapi.getDefaultContext() })
})
For non-emscripten, you need to use @tybys/emnapi-core. The initialization is similar to emscripten.
<script src="node_modules/@tybys/emnapi-runtime/dist/emnapi.min.js"></script>
<script src="node_modules/@tybys/emnapi-core/dist/emnapi-core.min.js"></script>
<script>
const napiModule = emnapiCore.createNapiModule({
context: emnapi.getDefaultContext()
})
fetch('./hello.wasm').then(res => res.arrayBuffer()).then(wasmBuffer => {
return WebAssembly.instantiate(wasmBuffer, {
env: {
...napiModule.imports.env,
// Currently napi-rs imports all symbols from env module
...napiModule.imports.napi,
...napiModule.imports.emnapi
},
// clang
napi: napiModule.imports.napi,
emnapi: napiModule.imports.emnapi
})
}).then(({ instance }) => {
const binding = napiModule.init(
instance, // WebAssembly.Instance
instance.exports.memory, // WebAssembly.Memory
instance.exports.__indirect_function_table // WebAssembly.Table
)
// binding === napiModule.exports
})
</script>
Using WASI on Node.js
const { createNapiModule } = require('@tybys/emnapi-core')
const { getDefaultContext } = require('@tybys/emnapi-runtime')
const { WASI } = require('wasi')
const napiModule = createNapiModule({
context: getDefaultContext()
})
const wasi = new WASI({ /* ... */ })
WebAssembly.instantiate(require('fs').readFileSync('./hello.wasm'), {
wasi_snapshot_preview1: wasi.wasiImport,
env: {
...napiModule.imports.env,
// Currently napi-rs imports all symbols from env module
...napiModule.imports.napi,
...napiModule.imports.emnapi
},
// clang
napi: napiModule.imports.napi,
emnapi: napiModule.imports.emnapi
}).then(({ instance }) => {
wasi.initialize(instance)
const binding = napiModule.init(
instance,
instance.exports.memory,
instance.exports.__indirect_function_table
)
// binding === napiModule.exports
})
Using WASI on browser, you can use WASI polyfill in wasm-util, and memfs-browser
import { createNapiModule } from '@tybys/emnapi-core'
import { getDefaultContext } from '@tybys/emnapi-runtime'
import { WASI } from '@tybys/wasm-util'
import { Volumn, createFsFromVolume } from 'memfs-browser'
const napiModule = createNapiModule({
context: getDefaultContext()
})
const fs = createFsFromVolume(Volume.from({ /* ... */ }))
const wasi = WASI.createSync({ fs, /* ... */ })
WebAssembly.instantiate(wasmBuffer, {
wasi_snapshot_preview1: wasi.wasiImport,
env: {
...napiModule.imports.env,
// Currently napi-rs imports all symbols from env module
...napiModule.imports.napi,
...napiModule.imports.emnapi
},
// clang
napi: napiModule.imports.napi,
emnapi: napiModule.imports.emnapi
}).then(({ instance }) => {
wasi.initialize(instance)
const binding = napiModule.init(
instance,
instance.exports.memory,
instance.exports.__indirect_function_table
)
// binding === napiModule.exports
})
Alternatively, you can also use node-addon-api which is official Node-API C++ wrapper, already shipped (v5.1.0) in this package but without Node.js specific API such as CallbackScope.
Note: C++ wrapper can only be used to target Node.js v14.6.0+ and modern browsers those support FinalizationRegistry and WeakRef (v8 engine v8.4+)!
Create hello.cpp.
#include <napi.h>
Napi::String Method(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, "world");
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "hello"),
Napi::Function::New(env, Method)).Check();
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
Compile hello.cpp using em++. C++ exception is disabled by Emscripten default, and not supported by wasi-sdk, so predefine -DNAPI_DISABLE_CPP_EXCEPTIONS and -DNODE_ADDON_API_ENABLE_MAYBE here. If you would like to enable C++ exception, use -sDISABLE_EXCEPTION_CATCHING=0 instead and remove .Check() call. See official documentation here.
em++ -O3 \
-DNAPI_DISABLE_CPP_EXCEPTIONS \
-DNODE_ADDON_API_ENABLE_MAYBE \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32-emscripten \
--js-library=./node_modules/@tybys/emnapi/dist/library_napi.js \
-sEXPORTED_FUNCTIONS="['_malloc','_free']" \
-o hello.js \
hello.cpp \
-lemnapi
clang++ -O3 \
-DNAPI_DISABLE_CPP_EXCEPTIONS \
-DNODE_ADDON_API_ENABLE_MAYBE \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32-wasi \
--target=wasm32-wasi \
--sysroot=$WASI_SDK_PATH/share/wasi-sysroot \
-mexec-model=reactor \
-Wl,--initial-memory=16777216 \
-Wl,--export-dynamic \
-Wl,--export=malloc \
-Wl,--export=free \
-Wl,--export=napi_register_wasm_v1 \
-Wl,--import-undefined \
-Wl,--export-table \
-o hello.wasm \
hello.cpp \
-lemnapi
node-addon-api is using the C++ standard libraries, so you must use WASI if you are using node-addon-api.
You can still use wasm32-unknown-unknown target if you use Node-API C API only in C++.
clang++ -O3 \
-I./node_modules/@tybys/emnapi/include \
-L./node_modules/@tybys/emnapi/lib/wasm32 \
--target=wasm32 \
-nostdlib \
-Wl,--no-entry \
-Wl,--initial-memory=16777216 \
-Wl,--export-dynamic \
-Wl,--export=malloc \
-Wl,--export=free \
-Wl,--export=napi_register_wasm_v1 \
-Wl,--import-undefined \
-Wl,--export-table \
-o node_api_c_api_only.wasm \
node_api_c_api_only.cpp \
-lemnapi \
-ldlmalloc # -lemmalloc
operator new and operator delete.
#include <stddef.h>
extern "C" void* malloc(size_t size);
extern "C" void free(void* p);
void* operator new(size_t size) {
return malloc(size);
}
void operator delete(void* p) noexcept {
free(p);
}
Create CMakeLists.txt.
cmake_minimum_required(VERSION 3.13)
project(emnapiexample)
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@tybys/emnapi")
add_executable(hello hello.c)
target_link_libraries(hello emnapi)
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
target_link_options(hello PRIVATE
"-sEXPORTED_FUNCTIONS=\"['_malloc','_free']\""
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "WASI")
target_link_options(hello PRIVATE
"-mexec-model=reactor"
"-Wl,--export=napi_register_wasm_v1"
"-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
)
elseif((CMAKE_C_COMPILER_TARGET STREQUAL "wasm32") OR (CMAKE_C_COMPILER_TARGET STREQUAL "wasm32-unknown-unknown"))
target_link_options(hello PRIVATE
"-nostdlib"
"-Wl,--export=napi_register_wasm_v1"
"-Wl,--no-entry"
"-Wl,--initial-memory=16777216,--export-dynamic,--export=malloc,--export=free,--import-undefined,--export-table"
)
target_link_libraries(hello dlmalloc)
# target_link_libraries(hello emmalloc)
endif()
mkdir build
# emscripten
emcmake cmake -DCMAKE_BUILD_TYPE=Release -G Ninja -H. -Bbuild
# wasi-sdk
cmake -DCMAKE_TOOLCHAIN_FILE=$WASI_SDK_PATH/share/cmake/wasi-sdk.cmake \
-DWASI_SDK_PREFIX=$WASI_SDK_PATH \
-DCMAKE_BUILD_TYPE=Release \
-G Ninja -H. -Bbuild
# wasm32
cmake -DCMAKE_TOOLCHAIN_FILE=node_modules/@tybys/emnapi/cmake/wasm32.cmake \
-DLLVM_PREFIX=$WASI_SDK_PATH \
-DCMAKE_BUILD_TYPE=Release \
-G Ninja -H. -Bbuild
cmake --build build
Output code can run in recent version modern browsers and Node.js latest LTS. IE is not supported.
Currently you can use napi-rs like this, more work is working in progress.
Note: WASI target require rust nightly toolchain.
[package]
edition = "2021"
name = "binding"
version = "0.0.0"
# We should build binary for WASI reactor
# https://github.com/rust-lang/rust/pull/79997
# https://github.com/WebAssembly/WASI/issues/24
# for wasm
[[bin]]
name = "binding"
path = "src/main.rs"
# for native
# [lib]
# name = "binding"
# path = "src/lib.rs"
# crate-type = ["cdylib"]
[dependencies]
napi = { version = "2.10.13", default-features = false, features = ["napi8", "compat-mode"] }
napi-sys = { version = "2.2.3", features = ["napi8"] }
napi-derive = "2.10.0"
[build-dependencies]
napi-build = "2.0.1"
[profile.release]
strip = "symbols"
[build]
target = [
"wasm32-unknown-unknown",
"wasm32-wasi"
]
[target.wasm32-unknown-unknown]
rustflags = [
"-L./node_modules/@tybys/emnapi/lib/wasm32",
"-lemnapi",
"-ldlmalloc",
# "-lemmalloc",
"-C", "link-arg=--no-entry",
"-C", "link-arg=--initial-memory=16777216",
"-C", "link-arg=--export-dynamic",
"-C", "link-arg=--export=malloc",
"-C", "link-arg=--export=free",
"-C", "link-arg=--export=napi_register_wasm_v1",
"-C", "link-arg=--export-table",
"-C", "link-arg=--import-undefined",
]
[target.wasm32-wasi]
rustflags = [
"-L./node_modules/@tybys/emnapi/lib/wasm32-wasi",
"-lemnapi",
"-C", "link-arg=--initial-memory=16777216",
"-C", "link-arg=--export-dynamic",
"-C", "link-arg=--export=malloc",
"-C", "link-arg=--export=free",
"-C", "link-arg=--export=napi_register_wasm_v1",
"-C", "link-arg=--export-table",
"-C", "link-arg=--import-undefined",
"-Z", "wasi-exec-model=reactor", # +nightly
]
#![no_main]
use napi::*;
#[cfg(target_arch = "wasm32")]
use napi::bindgen_prelude::*;
#[cfg(target_arch = "wasm32")]
use napi_sys::*;
#[macro_use]
extern crate napi_derive;
fn sum(a: i32, b: i32) -> i32 {
a + b
}
#[js_function(2)]
fn sum_js(ctx: CallContext) -> napi::Result<napi::JsNumber> {
let arg0 = ctx.get::<napi::JsNumber>(0)?.get_int32()?;
let arg1 = ctx.get::<napi::JsNumber>(1)?.get_int32()?;
let ret = sum(arg0, arg1);
ctx.env.create_int32(ret)
}
fn module_register(_env: napi::Env, mut exports: napi::JsObject) -> napi::Result<()> {
exports.create_named_method("sum", sum_js)?;
Ok(())
}
#[cfg(not(target_arch = "wasm32"))]
#[module_exports]
fn init(exports: napi::JsObject, env: napi::Env) -> napi::Result<()> {
module_register(env, exports)
}
#[cfg(target_arch = "wasm32")]
#[no_mangle]
pub unsafe extern "C" fn napi_register_wasm_v1(env: napi_env, exports: napi_value) -> () {
let env_object = napi::Env::from_raw(env);
let exports_object = napi::JsObject::from_napi_value(env, exports).unwrap();
module_register(env_object, exports_object).unwrap();
}
If you want to use async work or thread safe functions, there are additional C source file need to be compiled and linking. Recommend use CMake directly.
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/node_modules/@tybys/emnapi")
add_executable(hello hello.c)
target_link_libraries(hello emnapi-mt)
target_compile_options(hello PRIVATE "-sUSE_PTHREADS=1")
target_link_options(hello PRIVATE
"-sALLOW_MEMORY_GROWTH=1"
"-sEXPORTED_FUNCTIONS=\"['_malloc','_free']\""
"-sUSE_PTHREADS=1"
"-sPTHREAD_POOL_SIZE=4"
# try to specify stack size if you experience pthread errors
"-sSTACK_SIZE=2MB"
"-sDEFAULT_PTHREAD_STACK_SIZE=2MB"
)
emcmake cmake -DCMAKE_BUILD_TYPE=Release -DEMNAPI_WORKER_POOL_SIZE=4 -G Ninja -H. -Bbuild
cmake --build build
-DEMNAPI_WORKER_POOL_SIZE=4This is UV_THREADPOOL_SIZE equivalent at compile time, if not predefined, emnapi will read UV_THREADPOOL_SIZE from Emscripten environment variable at runtime, you can set UV_THREADPOOL_SIZE like this:
Module.preRun = Module.preRun || [];
Module.preRun.push(function () {
if (typeof ENV !== 'undefined') {
ENV.UV_THREADPOOL_SIZE = '2';
}
});
It represent max of EMNAPI_WORKER_POOL_SIZE async work (napi_queue_async_work) can be executed in parallel. Default is not defined, read UV_THREADPOOL_SIZE at runtime.
You can set both PTHREAD_POOL_SIZE and EMNAPI_WORKER_POOL_SIZE to number of CPU cores in general.
If you use another library function which may create N child threads in async work,
then you need to set PTHREAD_POOL_SIZE to EMNAPI_WORKER_POOL_SIZE * (N + 1).
This option only has effect if you use -sUSE_PTHREADS.
Emnapi will create EMNAPI_WORKER_POOL_SIZE threads when initializing,
it will throw error if PTHREAD_POOL_SIZE < EMNAPI_WORKER_POOL_SIZE && PTHREAD_POOL_SIZE_STRICT == 2.
See Issue #8 for more detail.
-DEMNAPI_NEXTTICK_TYPE=0This option only has effect if you use -sUSE_PTHREADS, Default is 0.
Tell emnapi how to delay async work in uv_async_send / uv__async_close.
0: Use setImmediate() (Node.js native setImmediate or browser MessageChannel and port.postMessage)1: Use Promise.resolve().then()-DEMNAPI_USE_PROXYING=1This option only has effect if you use -sUSE_PTHREADS. Default is 1 if emscripten version >= 3.1.9, else 0.
0
Use JavaScript implementation to send async work from worker threads, runtime code will access the Emscripten internal PThread object to add custom worker message listener.
1:
Use Emscripten proxying API to send async work from worker threads in C. If you experience something wrong, you can switch set this to 0 and feel free to create an issue.
See source code here
binding: function () {}
embind #emptyFunction x 37,148,158 ops/sec ±0.39% (67 runs sampled)
emnapi #emptyFunction x 40,207,668 ops/sec ±0.79% (67 runs sampled)
node-addon-api + emnapi #emptyFunction x 13,871,523 ops/sec ±0.35% (67 runs sampled)
Fastest is emnapi #emptyFunction
binding: function (obj) { return obj }
embind #returnParam x 19,230,099 ops/sec ±0.59% (68 runs sampled)
emnapi #returnParam x 14,930,264 ops/sec ±0.68% (64 runs sampled)
node-addon-api + emnapi #returnParam x 11,402,133 ops/sec ±0.47% (67 runs sampled)
Fastest is embind #returnParam
binding: function (int) { return copy(int) }
embind #convertInteger x 9,178,287 ops/sec ±0.67% (67 runs sampled)
emnapi #convertInteger x 9,898,681 ops/sec ±0.56% (66 runs sampled)
node-addon-api + emnapi #convertInteger x 7,888,491 ops/sec ±0.79% (67 runs sampled)
Fastest is emnapi #convertInteger
binding: function (str) { return copy(str) }
embind #convertString x 2,574,960 ops/sec ±0.46% (66 runs sampled)
emnapi #convertString x 3,412,941 ops/sec ±0.66% (68 runs sampled)
node-addon-api + emnapi #convertString x 2,913,797 ops/sec ±0.62% (67 runs sampled)
Fastest is emnapi #convertString
binding: function (param) { return param.length }
embind #ObjectGet x 6,192,531 ops/sec ±0.73% (67 runs sampled)
emnapi #ObjectGet x 5,268,653 ops/sec ±0.59% (66 runs sampled)
node-addon-api + emnapi #ObjectGet x 4,828,204 ops/sec ±0.57% (67 runs sampled)
Fastest is embind #ObjectGet
binding: function (obj, key, value) { obj[key] = value }
embind #ObjectSet x 11,467,433 ops/sec ±0.55% (67 runs sampled)
emnapi #ObjectSet x 9,476,512 ops/sec ±0.91% (66 runs sampled)
node-addon-api + emnapi #ObjectSet x 7,647,341 ops/sec ±0.36% (66 runs sampled)
Fastest is embind #ObjectSet
FAQs
Node-API implementation for Emscripten
The npm package @tybys/emnapi receives a total of 6 weekly downloads. As such, @tybys/emnapi popularity was classified as not popular.
We found that @tybys/emnapi demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.