diff --git a/README.md b/README.md index 9228d9b..23350bf 100644 --- a/README.md +++ b/README.md @@ -12,23 +12,24 @@ This is a part of [Node3D](https://github.com/node-3d) project. ## Synopsis -Helpers for Node.js addons and dependency packages: +Helpers for Node.js **NAPI** addons and dependency packages: -* C++ macros and shortcuts. -* `consoleLog()` C++ helper. -* `eventEmit()` C++ helper. -* `getData()` C++ helper. -* Crossplatform commands for GYP: `cp`, `rm`, `mkdir`. * Supported platforms (x64): Windows, Linux, OSX. +* C++ helpers: + * Macro shortcuts for NAPI. + * `consoleLog()` function. + * `eventEmit()` function. + * `getData()` function. +* Module helpers: + * Crossplatform commands for GYP: `cp`, `rm`, `mkdir`. + * Deps unzip installer. + * Url-to-buffer downloader. Useful links: [N-API Docs](https://nodejs.org/api/n-api.html), [Napi Docs](https://github.com/nodejs/node-addon-api/blob/master/doc/setup.md), [GYP Docs](https://gyp.gsrc.io/docs/UserDocumentation.md). ---- - - -## Contents +**Jump to**: [Snippets](#snippets) @@ -107,16 +108,41 @@ On both Windows and Unix those commands now have the same result: ], ``` -Those are the directory paths to C++ include files for Addon Tools and Napi -(which is preinstalled with Addon Tools) +Those are the directory paths to C++ include files for **Addon Tools** and +**Napi** (which is preinstalled with Addon Tools). ### Binary dependency package -If you design a module with binary dependencies for several platforms, **Addon Tools** -would encourage you to abide by the following rules: +If you design a module with binary dependencies for several platforms, +**Addon Tools** may help within the following guidelines: -* Your binary directories are: +* In **package.json** use a `"postinstall"` script to download the libraries. +For example the following structure might work. Note that **Addon Tools** will +append any given URL with ``/${platform}.zip`` + + ``` + "config" : { + "install" : "v1.0.0" + }, + "scripts": { + "postinstall": "install", + }, + ``` + + where `config.install` is **YOUR install.js** is: + + ``` + const install = require('addon-tools-raub/install'); + const prefix = 'https://github.com/user/addon/releases/download'; + const tag = process.env.npm_package_config_install; + install(`${prefix}/${tag}`); + ``` + + **Addon Tools** will unzip the downloaded file into the platform binary + directory. E.g. on Windows it will be **bin-windows**. + +* Per platform binary directories: * bin-windows * bin-linux @@ -129,69 +155,73 @@ is described [here](#indexjs). module.exports = require('addon-tools-raub').paths(__dirname); ``` -
+* Publishing is done by attaching a zipped platform folder to the Github +release. Zip file must NOT contain platform folder as a subfolder, but rather +contain the final binaries. The tag of the release should be the same as in +`npm_package_config_install` - that is the way installer will find it. - Show binding.gyp - - ``` - { - 'variables': { - 'rm' : ' +* NOTE: You can publish your binaries to anywhere, not necessarily Github. +Just tweak the **install.js** script as appropriate. The only limitation +from **Addon Tools** is that it should be a zipped set of files/folders. ### Compiled addon -It is easy to build a C++ addon with **Addon Tools**. To have a full picture, you -can view the -[official example](https://github.com/node-3d/addon-tools-raub/tree/master/examples/addon). +With the advent of N-API the focus of compiled addons shifted towards the +word "addons". Since ABI is now compatible across Node.js versions, now addons +are just plain DLLs. Therefore distribution of the binaries is covered in the +previous section. But for an addon you have to provide a GYP compilation step. +N-API changes it's role to out-of-install compilation, so we can't/shouldnt +put the file **binding.gyp** to the module root anymore. -The main file for an addon is **binding.gyp**. Here's a snippet with most of the features. +The workaround would be to have a separate directory within your project +(with simplified package.json) for the sake of addon compilation. -
- -binding.gyp +``` +{ + "name": "build", + "version": "0.0.0", + "private": true, + "dependencies": { + "addon-tools-raub": "5.0.0", + "deps-EXT_LIB": "1.0.0" + } +} +``` * Assume `EXT_LIB` is the name of a binary dependency. * Assume `deps-EXT_LIB` is the name of an Addon Tools compliant dependency module. * Assume `MY_ADDON` is the name of this addon's resulting binary. -* Assume C++ code goes to `cpp` directory. +* Assume C++ code goes to `cpp` subdirectory. + +That together with **binding.gyp**, this would be enough to get the addon compiled. +Then the binaries are published to the Github release. When the addon +is installed, its **index.js** is responsible for reexport of the binary. +Just require the built module like this: + +``` +const { bin } = require('addon-tools-raub'); +const core = require(`./${bin}/MY_ADDON`); +``` + +
+ +See a snipped for binding.gyp here + +* Assume `EXT_LIB` is the name of a binary dependency. +* Assume `deps-EXT_LIB` is the name of an Addon Tools compliant dependency module. +* Assume `MY_ADDON` is the name of this addon's resulting binary. +* Assume C++ code goes to `cpp` subdirectory. ``` { 'variables': { - 'rm' : ' --- @@ -332,99 +333,55 @@ so that you can replace: with ``` -#include // or event-emitter.hpp +#include ``` -In gyp, the include directory should be set for your addon to know where to get it. -As it was mentioned above, this can be done automatically. Also an actual path to the -directory is exported from the module and is accessible like this: +In gyp, the include directory should be set for your addon to know where +to get it. An actual path to the directory is exported from the module +and is accessible like this: ``` require('addon-tools-raub').include // a string ``` -Currently, there are following helpers in **addon-tools.hpp**: +### Helpers in **addon-tools.hpp**: - -
- -Handle scope - -* `NAN_HS` - creates a HandleScope. Also, you do not need them within `NAN_METHOD`, -`NAN_SETTER`, and `NAN_GETTER`, as it is stated in -[Nan doc](https://github.com/nodejs/nan/blob/master/doc/methods.md#api_nan_method). -So it is most likely to be used in parts of code called from C++ land. +Usually all the helpers work within the context of JS call. In this case we +have `CallbackInfo info` passed as an argument. ``` -void windowFocusCB(GLFWwindow *window, int focused) { NAN_HS; - ... -} -... -glfwSetWindowFocusCallback(window, windowFocusCB); +#define NAPI_ENV Napi::Env env = info.Env(); +#define NAPI_HS Napi::HandleScope scope(env); ``` -
- -
-Method return +Return value -* `RET_VALUE(VAL)` - set method return value, where `VAL` is `v8::Local`. -* `RET_UNDEFINED` - set method return value as `undefined`. -* `RET_STR(VAL)` - set method return value, where `VAL` is `const char *`. -* `RET_UTF8(VAL)` - set method return value, where `VAL` is `const char *`. -* `RET_INT(VAL)` - set method return value, where `VAL` is `int32`. -* `RET_INT32(VAL)` - set method return value, where `VAL` is `int32`. -* `RET_UINT32(VAL)` - set method return value, where `VAL` is `uint32`. -* `RET_NUM(VAL)` - set method return value, where `VAL` is `double`. -* `RET_OFFS(VAL)` - set method return value, where `VAL` is `size_t`. -* `RET_FLOAT(VAL)` - set method return value, where `VAL` is `float`. -* `RET_DOUBLE(VAL)` - set method return value, where `VAL` is `double`. -* `RET_EXT(VAL)` - set method return value, where `VAL` is `void *`. -* `RET_BOOL(VAL)` - set method return value, where `VAL` is `bool`. -* `RET_FUN(VAL)` - set method return value, where `VAL` is `Nan::Persistent`. -* `RET_OBJ(VAL)` - set method return value, where `VAL` is `Nan::Persistent`. +* `RET_VALUE(VAL)`- return a given Napi::Value. +* `RET_UNDEFINED`- return `undefined`. +* `RET_NULL` - return `null`. +* `RET_STR(VAL)` - return `Napi::String`, expected `VAL` is `const char *`. +* `RET_NUM(VAL)` - return `Napi::Number`, expected `VAL` is `double`. +* `RET_EXT(VAL)` - return `Napi::External`, expected `VAL` is `void *`. +* `RET_BOOL(VAL)` - return `Napi::Boolean`, expected `VAL` is `bool`. +* `RET_FUN(VAL)` - return `Napi::Function`, expected `VAL` is a `napi_value`. +* `RET_OBJ(VAL)` - return `Napi::Object`, expected `VAL` is a `napi_value`.
-
- -Shortcut types - -* `V8_VAR_VAL` = `v8::Local` -* `V8_VAR_OBJ` = `v8::Local` -* `V8_VAR_ARR` = `v8::Local` -* `V8_VAR_STR` = `v8::Local` -* `V8_VAR_FUNC` = `v8::Local` -* `V8_VAR_FT` = `v8::Local` -* `V8_VAR_OT` = `v8::Local` -* `V8_STORE_FT` = `Nan::Persistent` -* `V8_STORE_FUNC` = `Nan::Persistent` -* `V8_STORE_OBJ` = `Nan::Persistent` -* `V8_STORE_VAL` = `Nan::Persistent` - -
-
New JS value -* `JS_STR(...)` - create a string value -* `JS_UTF8(...)` - same as JS_STR -* `JS_INT(val)` - create an integer value -* `JS_INT32(val)` - same as `JS_INT` -* `JS_UINT32(val)` - same as `JS_INT` -* `JS_NUM(val)` - create a numeric value -* `JS_OFFS(val)` - same as `JS_NUM`, but has a cast designed to avoid `size_t -> double` warning -* `JS_FLOAT(val)` - same as `JS_NUM` -* `JS_DOUBLE(val)` - same as `JS_NUM` -* `JS_EXT(val)` - create an external (pointer) value -* `JS_BOOL(val)` - create a boolean value -* `JS_FUN(val)` - get a function from persistent `Nan::Persistent`. -* `JS_OBJ(val)` - get an object from persistent `Nan::Persistent`. +* `JS_STR(VAL)` - create a `Napi::String` value. +* `JS_NUM(VAL)` - create a `Napi::Number` value. +* `JS_EXT(VAL)` - create a `Napi::External` (from pointer) value. +* `JS_BOOL(VAL)` - create a `Napi::Boolean` value. +* `JS_FUN(VAL)` - create a `Napi::Function` value. +* `JS_OBJ(VAL)` - create a `Napi::Object` value.
@@ -434,8 +391,8 @@ glfwSetWindowFocusCallback(window, windowFocusCB); Method check These checks throw JS TypeError if not passed. Here `T` is always used as a typename -in error messages. `C` is -[v8::Value](https://v8docs.nodesource.com/node-0.8/dc/d0a/classv8_1_1_value.html) +in error messages. `C` is a +[Napi::Value](https://github.com/nodejs/node-addon-api/blob/master/doc/value.md#isboolean) check method, like `IsObject()`. `I` is the index of argument as in `info[I]`, starting from `0`. @@ -445,7 +402,8 @@ starting from `0`. * `CHECK_LET_ARG(I, C, T)` - check if argument `I` is approved by `C` check or empty. * `CTOR_CHECK(T)` - check if method is called as a constructor * `SETTER_CHECK(C, T)` - check if setter `value` is approved by `C` check. -* `DES_CHECK` - within dynamic method check if the instance wasn't destroyed by `destroy()`. +* `DES_CHECK` - within dynamic method check if the instance wasn't +destroyed by `destroy()`.
@@ -454,34 +412,64 @@ starting from `0`. Method arguments -Two types of argument retrieval are supported: `REQ_` and `LET_`. The difference -is that `LET_` allows the argument to be empty, using some zero-default in this case. -`I` is the index of argument as in `info[I]`, -starting from `0`. `VAR` is the name of the variable to be created. +The idea is to ease the transition from what inside the `CallbackInfo` to +what you work with in C++. +Three types of argument retrieval are supported: `REQ_`, `USE_` and `LET_`. +The difference: +* `REQ_` - 2 params, requires an argument to have a value +* `USE_` - 3 params, allows the argument to be empty and have a default +* `LET_` - 2 params, is `USE_` with a preset zero-default. + +What it does, basically: +``` +// REQ_DOUBLE_ARG(0, x) +double x = info[0].ToNumber().DoubleValue(); + +// USE_DOUBLE_ARG(0, x, 5.7) +double x = IS_ARG_EMPTY(0) ? 5.7 : info[0].ToNumber().DoubleValue(); + +// LET_DOUBLE_ARG(0, x) +double x = IS_ARG_EMPTY(0) ? 0.0 : info[0].ToNumber().DoubleValue(); +``` + +That extrapolates well to all the helpers below: +* `REQ_STR_ARG` - JS `string` => C++ `std::string`. +* `USE_STR_ARG` +* `LET_STR_ARG` - default: `""`. +* `REQ_INT32_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT32_ARG` +* `LET_INT32_ARG` - default: `0`. +* `REQ_INT_ARG` - JS `number` => C++ `int32_t`. +* `USE_INT_ARG` +* `LET_INT_ARG` - default: `0`. +* `REQ_UINT32_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT32_ARG` +* `LET_UINT32_ARG` - default: `0`. +* `REQ_UINT_ARG` - JS `number` => C++ `uint32_t`. +* `USE_UINT_ARG` +* `LET_UINT_ARG` - default: `0`. +* `REQ_BOOL_ARG` - JS `Boolean` => C++ `bool`. +* `USE_BOOL_ARG` +* `LET_BOOL_ARG` - default: `false`. +* `REQ_OFFS_ARG` - JS `number` => C++ `size_t`. +* `USE_OFFS_ARG` +* `LET_OFFS_ARG` - default: `0`. +* `REQ_DOUBLE_ARG` - JS `number` => C++ `double`. +* `USE_DOUBLE_ARG` +* `LET_DOUBLE_ARG` - default: `0.0`. +* `REQ_FLOAT_ARG` - JS `number` => C++ `float`. +* `USE_FLOAT_ARG` +* `LET_FLOAT_ARG` - default: `0.f`. +* `REQ_EXT_ARG` - JS `native` => C++ `void*`. +* `USE_EXT_ARG` +* `LET_EXT_ARG` - default: `nullptr`. +* `REQ_FUN_ARG` - JS `function` => C++ `Napi::Function`. +* `REQ_OBJ_ARG` - JS `object` => C++ `Napi::Object`. +* `USE_OBJ_ARG` +* `LET_OBJ_ARG` - default: `{}`. +* `REQ_ARRV_ARG` - JS `ArrayBuffer` => C++ `Napi::ArrayBuffer`. +* `REQ_BUF_ARG` - JS `Buffer` => C++ `Napi::Buffer`. -* `REQ_UTF8_ARG(I, VAR)` - require `I`'th argument to be a `string`. Stored at `Nan::Utf8String VAR`. -* `LET_UTF8_ARG(I, VAR)` - let optional `I`'th argument to be a `string`, the default is `""`. Stored at `Nan::Utf8String VAR`. -* `REQ_STR_ARG(I, VAR)` - require `I`'th argument to be a `string`. Stored at `Nan::Utf8String VAR`. -* `LET_STR_ARG(I, VAR)` - let optional `I`'th argument to be a `string`, the default is `""`. Stored at `Nan::Utf8String VAR`. -* `REQ_INT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `int VAR`. -* `LET_INT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `int VAR`. -* `REQ_INT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `int VAR`. -* `LET_INT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `int VAR`. -* `REQ_UINT32_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `unsigned VAR`. -* `LET_UINT32_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `unsigned VAR`. -* `REQ_BOOL_ARG(I, VAR)` - require `I`'th argument to be a `boolean`. Stored at `bool VAR`. -* `LET_BOOL_ARG(I, VAR)` - let optional `I`'th argument to be a `boolean`, the default is `false`. Stored at `Nan::Utf8String VAR`. -* `REQ_OFFS_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `size_t VAR`. -* `LET_OFFS_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0`. Stored at `Nan::Utf8String VAR`. -* `REQ_DOUBLE_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `double VAR`. -* `LET_DOUBLE_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0.0`. Stored at `Nan::Utf8String VAR`. -* `REQ_FLOAT_ARG(I, VAR)` - require `I`'th argument to be a `number`. Stored at `float VAR`. -* `LET_FLOAT_ARG(I, VAR)` - let optional `I`'th argument to be a `number`, the default is `0.0f`. Stored at `Nan::Utf8String VAR`. -* `REQ_EXT_ARG(I, VAR)` - require `I`'th argument to be an `external`. Stored at `Local VAR`. -* `LET_EXT_ARG(I, VAR)` - let optional `I`'th argument to be an `external`, the default is `nullptr`. Stored at `Nan::Utf8String VAR`. -* `REQ_FUN_ARG(I, VAR)` - require `I`'th argument to be a `function`. Stored at `Local VAR`. -* `REQ_OBJ_ARG(I, VAR)` - require `I`'th argument to be an `object`. Stored at `Local VAR`. -* `REQ_ARRV_ARG(I, VAR)` - require `I`'th argument to be a `TypedArray`. Stored at `Local VAR`. ``` NAN_METHOD(test) { @@ -493,22 +481,6 @@ NAN_METHOD(test) { ... ``` -> Note: The conversion from `Nan::Utf8String` to `std::string` (via `char *`) -is possible with unary `*` operator. - - - - -
- -Set properties - -Set-helpers for string and numeric keys. String keys are converted to JS strings -automatically. - -* `SET_PROP(OBJ, KEY, VAL)` -* `SET_I(ARR, I, VAL)` -
@@ -519,18 +491,25 @@ automatically. Simplified accessor assignment, adds accessors of NAME for OBJ. Read accessor is assumed to have the name `NAME+'Getter'` and write accessor is `NAME+'Setter'`. -* `ACCESSOR_RW(OBJ, NAME)` - add read and write accessors of NAME for OBJ. -* `ACCESSOR_R(OBJ, NAME)` - read-only property. +* `ACCESSOR_RW(CLASS, NAME)` - add read and write accessors NAME of CLASS. +* `ACCESSOR_R(CLASS, NAME)` - add read accessor NAME of CLASS. +* `ACCESSOR_M(CLASS, NAME)` - add method NAME of CLASS. + ``` -void MyClass::init(Handle target) { +void MyClass::init(Napi::Env env, Napi::Object exports) { ... - Local proto = ctor->PrototypeTemplate(); - ACCESSOR_RW(proto, message); + Napi::Function ctor = DefineClass(env, "MyClass", { + ACCESSOR_R(MyClass, isDestroyed), + ACCESSOR_RW(MyClass, x), + ACCESSOR_M(MyClass, reset), + }); ... } -NAN_GETTER(MyClass::messageGetter) { ... -NAN_SETTER(MyClass::messageSetter) { ... +JS_GETTER(MyClass::isDestroyedGetter) { ... +JS_GETTER(MyClass::xGetter) { ... +JS_SETTER(MyClass::xSetter) { ... +JS_METHOD(MyClass::save) { ... ``` @@ -540,26 +519,26 @@ NAN_SETTER(MyClass::messageSetter) { ... Setter argument -Useful addition to NAN_SETTER macro. Works similar to method arguments. But there -is always only one required argument stored in `v`. +Works similar to method arguments. But there is always `value` +argument, from which a C++ value is extracted. -* `SETTER_UTF8_ARG` - require the value to be a `string`. Stored at `Nan::Utf8String v`. -* `SETTER_STR_ARG` - require the value to be a `string`. Stored at `Nan::Utf8String v`. -* `SETTER_INT32_ARG` - require the value to be a `number`. Stored at `int v`. -* `SETTER_INT_ARG` - require the value to be a `number`. Stored at `int v`. -* `SETTER_UINT32_ARG` - require the value to be a `number`. Stored at `unsigned v`. -* `SETTER_BOOL_ARG` - require the value to be a `boolean`. Stored at `bool v`. -* `SETTER_OFFS_ARG` - require the value to be a `number`. Stored at `size_t v`. -* `SETTER_DOUBLE_ARG` - require the value to be a `number`. Stored at `double v`. -* `SETTER_FLOAT_ARG` - require the value to be a `number`. Stored at `float v`. -* `SETTER_EXT_ARG` - require the value to be an `external`. Stored at `Local v`. -* `SETTER_FUN_ARG` - require the value to be a `function`. Stored at `Local v`. -* `SETTER_OBJ_ARG` - require the value to be an `object`. Stored at `Local v`. -* `SETTER_ARRV_ARG` - require the value to be a `TypedArray`. Stored at `Local v`. +* `SETTER_STR_ARG` +* `SETTER_INT32_ARG` +* `SETTER_INT_ARG` +* `SETTER_BOOL_ARG` +* `SETTER_UINT32_ARG` +* `SETTER_UINT_ARG` +* `SETTER_OFFS_ARG` +* `SETTER_DOUBLE_ARG` +* `SETTER_FLOAT_ARG` +* `SETTER_EXT_ARG` +* `SETTER_FUN_ARG` +* `SETTER_OBJ_ARG` +* `SETTER_ARRV_ARG` ``` -NAN_SETTER(MyClass::messageSetter) { SETTER_UTF8_ARG; - // Variable created: Nan::Utf8String v; +JS_SETTER(MyClass::x) { SETTER_STR_ARG; + // Variable created: std::string v; ... ``` @@ -571,9 +550,8 @@ NAN_SETTER(MyClass::messageSetter) { SETTER_UTF8_ARG; Data retrieval * `T *getArrayData(value, num = NULL)` - extracts TypedArray data of any type from -the given JS value. Does not accept Array, checked with `IsArrayBufferView()`. -Returns `NULL` for empty JS values. For unacceptable values throws TypeError. - +the given JS value. Does not accept `Array`. Checks with `IsArrayBuffer()`. +Returns `nullptr` for empty JS values. For unacceptable values throws TypeError. * `void *getData(value)` - if value is a TypedArray, then the result of `getArrayData(value)` is returned. Otherwise if value has `'data'` property, it's @@ -589,41 +567,18 @@ content is then returned as `node::Buffer`. Returns `nullptr` in other cases. Exports: * `paths(dir)` - function. Returns a set of platform dependent paths depending on input `dir`. - * `bin()` - prints platform binary directory absolute path. - * `rem()` - prints a space-separated list of binary paths to be cleaned on this platform. - * `include()` - prints include directory for this `dir`. - * `binPath` - platform binary directory absolute path. - * `remPath` - a space-separated list of binary paths to be cleaned on this platform. - * `includePath` - include directory for this `dir`. -* `root()` - prints where `'addon-tools-raub'` module is situated. -* `include()` - prints both `'addon-tools-raub'` and `'nan'` include paths. Use with -`node -e` through list context command expansion ` Note: This implementation has some minor deviations from the above standard. -Specifically there is no static `EventEmitter.defaultMaxListeners` property. -However the dynamic one persists and is infinite (`0`) by default. - -Also -[EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) -is implemented. Not in full detail, but should be fine for callers. - -An example can be found in **examples/node-addon** directory. -There is `Example` class, implemented in **cpp/example.cpp**, that inherits -EventEmitter behavior and is exported to JS. - -For the C++ side `EventEmitter` has following public methods: - -* `void emit(const std::string &name, int argc = 0, v8::Local *argv = NULL)` - -emits an event with the given `name` and, optionally, some additional arguments where -`argc` is the number of arguments and `argv` is a pointer to the arguments array. - -* `void on(const std::string &name, V8_VAR_FUNC cb)` - -subscribes `cb` to receive `name` events from this emitter, basically -`emitter.on(name, cb)`. - -* `void destroy()` - destroys the object, i.e. deactivates it and frees -resources. This is what also called inside -`~EventEmitter()`, but only the first call is effective anyway. - - -Be sure to add the include directory in **binding.gyp**: - -``` - 'include_dirs': [ - ' - -class Example : public EventEmitter { - ... -} -``` - -> Note: Do not forget to call `EventEmitter::init()` once, in the module `init()`. - - -
- -V8 Inheritance - -Now that everything is in place, consider providing **V8** with JS inheritance info: - -``` -void Example::init(Handle target) { - - Local proto = Nan::New(newCtor); - - // -------------------------- HERE! - // class Example extends EventEmitter - Local parent = Nan::New(EventEmitter::_prototype); - proto->Inherit(parent); - // -------------------------- - - proto->InstanceTemplate()->SetInternalFieldCount(1); - proto->SetClassName(JS_STR("Example")); - - Local ctor = Nan::GetFunction(proto).ToLocalChecked(); - - _constructor.Reset(ctor); - - Nan::Set(target, JS_STR("Example"), ctor); - -} -``` - - - ---- - ## Function consoleLog @@ -766,7 +631,7 @@ At first it may look as if `cout << "msg" << endl;` works nice, but it doesn't. After a while, it just ceases on a midword, and you end up thinking something has broken really hard in your addon. -To overcome this, we can use some V8 `eval` magic to make a real `console.log` +To overcome this, we can use some `eval` magic to make a real `console.log` call from C++ land. And this is where `consoleLog` comes into play. * `inline void consoleLog(int argc, V8_VAR_VAL *argv)` - a generic logger, @@ -775,4 +640,39 @@ receives any set of arguments. * `inline void consoleLog(const std::string &message)` - an alias to log a single string. -> Note: Don't do it in GC-accessible code: sometimes it works, sometimes it crashes. +> Note: only use this within JS function stack + + +## Function eventEmit + +In N-API there is no inheritance. And no `eval('require(...)')` possible at +the time of module init. But `require('events')` is very tempting... +The solution is: + +``` +// JS +const EventEmitter = require('events'); +const { bin } = require('addon-tools-raub'); +const MyClass = require(`./${bin}/addon`); +MyClass.prototype.__proto__ = EventEmitter.prototype; +// Since now it is possible to call `emit` from instancess of MyClass +``` + +``` +// C++ +void MyClass::emit(const Napi::CallbackInfo& info, const char* name) { + NAPI_ENV; + eventEmit(env, info.This().As(), name); +} +``` + +Signature: +``` +inline void eventEmit( + Napi::Env env, + Napi::Object that, + const std::string &name, + int argc = 0, + Napi::Value *argv = nullptr +) +``` diff --git a/include/addon-tools.hpp b/include/addon-tools.hpp index 9cdb45e..4551566 100644 --- a/include/addon-tools.hpp +++ b/include/addon-tools.hpp @@ -9,27 +9,27 @@ #define NAPI_HS Napi::HandleScope scope(env); -#define JS_STR(val) Napi::String::New(env, val) -#define JS_NUM(val) Napi::Number::New(env, static_cast(val)) -#define JS_EXT(val) Napi::External::New(env, reinterpret_cast(val)) -#define JS_BOOL(val) Napi::Boolean::New(env, val) -#define JS_FUN(val) Napi::Function::Function(env, val) -#define JS_OBJ(val) Napi::Object::Object(env, val) +#define JS_STR(VAL) Napi::String::New(env, VAL) +#define JS_NUM(VAL) Napi::Number::New(env, static_cast(VAL)) +#define JS_EXT(VAL) Napi::External::New(env, reinterpret_cast(VAL)) +#define JS_BOOL(VAL) Napi::Boolean::New(env, VAL) +#define JS_FUN(VAL) Napi::Function::Function(env, VAL) +#define JS_OBJ(VAL) Napi::Object::Object(env, VAL) #define RET_VALUE(VAL) return VAL; #define RET_UNDEFINED RET_VALUE(env.Undefined()) #define RET_NULL RET_VALUE(env.Null()) -#define RET_STR(val) RET_VALUE(JS_STR(val)) -#define RET_NUM(val) RET_VALUE(JS_NUM(val)) -#define RET_EXT(val) RET_VALUE(JS_EXT(val)) -#define RET_BOOL(val) RET_VALUE(JS_BOOL(val)) -#define RET_FUN(val) RET_VALUE(JS_FUN(val)) -#define RET_OBJ(val) RET_VALUE(JS_OBJ(val)) +#define RET_STR(VAL) RET_VALUE(JS_STR(VAL)) +#define RET_NUM(VAL) RET_VALUE(JS_NUM(VAL)) +#define RET_EXT(VAL) RET_VALUE(JS_EXT(VAL)) +#define RET_BOOL(VAL) RET_VALUE(JS_BOOL(VAL)) +#define RET_FUN(VAL) RET_VALUE(JS_FUN(VAL)) +#define RET_OBJ(VAL) RET_VALUE(JS_OBJ(VAL)) -#define JS_THROW(val) \ - Napi::Error::New(env, val).ThrowAsJavaScriptException(); +#define JS_THROW(VAL) \ + Napi::Error::New(env, VAL).ThrowAsJavaScriptException(); #define REQ_ARGS(N) \ @@ -38,7 +38,7 @@ } -#define IS_EMPTY(val) (val.IsNull() || val.IsUndefined()) +#define IS_EMPTY(VAL) (VAL.IsNull() || VAL.IsUndefined()) #define IS_ARG_EMPTY(I) IS_EMPTY(info[I]) @@ -173,17 +173,6 @@ #define LET_OBJ_ARG(I, VAR) USE_OBJ_ARG(I, VAR, info[I].As()) -#define REQ_OBJ_ARG(I, VAR) \ - CHECK_REQ_ARG(I, IsObject(), "Object"); \ - Napi::Object VAR = info[I].As(); - -#define USE_OBJ_ARG(I, VAR, DEF) \ - CHECK_LET_ARG(I, IsObject(), "Object"); \ - Napi::Object VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As(); - -#define LET_OBJ_ARG(I, VAR) USE_OBJ_ARG(I, VAR, info[I].As()) - - #define REQ_ARRV_ARG(I, VAR) \ CHECK_REQ_ARG(I, IsArrayBuffer(), "Object"); \ Napi::ArrayBuffer VAR = info[I].As(); @@ -224,6 +213,8 @@ #define ACCESSOR_M(CLASS, NAME) \ InstanceMethod(#NAME, &CLASS::NAME) +#define THIS_OBJ(VAR) \ + Napi::Object VAR = info.This().As(); #define SETTER_STR_ARG \ SETTER_CHECK(IsNumber(), "String"); \ diff --git a/install.js b/install.js index 201029c..4b129b7 100644 --- a/install.js +++ b/install.js @@ -2,35 +2,56 @@ const https = require('https'); -const unzipper = require('unzipper'); +const http = require('http'); const unzipper = require('unzipper'); -const REPO = process.argv[1]; +const { bin, platform } = require('.'); -const download = (url, count = 1) => { - const sendReq = https.get(url, response => { - if ([301, 302, 303, 307].includes(response.statusCode)) { - if (count < 5) { - return download(response.headers.location, count + 1); - } - console.error('Error: Too many redirects.'); - process.exit(-1); - return; - } - if (response.statusCode !== 200) { - console.log('Response status was ' + response.statusCode); - process.exit(-1); - return; - } - response.pipe(unzipper.Extract({ path: 'bin' })); - }); - - sendReq.on('error', err => { - console.log(err.message); - process.exit(-1); - }); +const protocols = { http, https }; + +const onError = msg => { + console.error(msg); + process.exit(-1); }; -download('https://github.com/raub/test-download/releases/download/v1.0.0/win.zip'); + +const install = (url, count = 1) => { + + url = url.toLowerCase(); + const proto = protocols[url.match(/^https?/)[0]]; + + const request = https.get(url, response => { + + // Handle redirects + if ([301, 302, 303, 307].includes(response.statusCode)) { + if (count < 5) { + return install(response.headers.location, count + 1); + } + return onError('Error: Too many redirects.'); + } + + // Handle bad status + if (response.statusCode !== 200) { + return onError(`Response status was ${response.statusCode}`); + } + + response.on('error', err => onError(err.message)); + + const extractor = unzipper.Extract({ path: bin }); + extractor.on('error', err => onError(err.message)); + + response.pipe(extractor); + + }); + + request.on('error', err => onError(err.message)); + +}; + + +module.exports = folder => { + const url = `${folder}/${platform}`; + install(url); +};