Search code examples
c++node.jsnode-addon-api

Is it possible to create an ObjectWrap in node-addon-api with a private js constructor and that can be instantiated in c++ only?


I'm currently writing a node.js addon in c++ using the node-addon-api (Napi). From what I saw in the different docs and tutorials, every objet has to extend Napi::ObjectWrap to be considered as js object. Then the constructor can be called in js. But what if I want this specific object to be only provided by an other Object and turn the js constructor private ? considering this case

class Provided: public Napi::ObjectWrap<Provided> {
public:
   // Init function for exports
   static Init(Napi::Env, Napi::Object exports);
   // what if I want my constructor to have a value instead of a CallbackInfo and only allow instantiation from c++ ?
   Provided(std::string value);
private:
   std::string m_value;
};

class MyProvider: public Napi::ObjectWrap<MyProvider> {
public:
   // Init function for exports
   static Init(Napi::Env, Napi::Object exports) {
      Napi::Function func = DefineClass(env, "provider", {InstanceMethod("getProvided", &MyProvider::GetProvided)});

      constructor = Napi::Persistent(func);
      constructor.SupressDestruct();

      exports.Set("Provider", func);
      return exports;
   }
   MyProvider(Napi::CallbackInfo const&): Napi::ObjectWrap<MyProvider>(info) {
       this->m_provided = Provided("my value");

   }
private:
   Provided m_provided;
   static Napi::FunctionReference constructor;
}

How to be able not to have Napi::CallbackInfo in the Provided constructor


Solution

  • My solution was using the data argument of DefineClass to pass through my argument (in my case only one, but I guess you can pass an array/vector/whatever container with as much as you'd like). Then I created an instance of the "Provided" class and returned it. But an example is worth more than my explanation:

    class Provided: public Napi::ObjectWrap<Provided> {
    public:
       // Factory for a JavaScript class instance
       static Create(Napi::Env env, std::string &myString) {
          // Pass the argument(s) as last DefineClass argument
          Napi::Function func = DefineClass(env, "provided", {InstanceMethod("getString", &MyProvider::GetString)}, &myString);
       };
       Provided(const Napi::CallbackInfo &info) {
          // And get the c++ object back in the constructor.
          // It requires a cast as it goes through being a void pointer.
          myValue = *(std::string *)info.Data();
       };
    private:
       std::string myValue;
       Napi::Value GetString(const Napi::CallbackInfo &info) {
          return Napi::String::New(info.Env(), myValue);
       };
    };
    
    class MyProvider: public Napi::ObjectWrap<MyProvider> {
    public:
       // Init function for exports
       static Init(Napi::Env env, Napi::Object exports) {
          Napi::Function func = DefineClass(env, "provider", {InstanceMethod("getProvided", &MyProvider::GetProvided)});
    
          constructor = Napi::Persistent(func);
          constructor.SupressDestruct();
    
          exports.Set("Provider", func);
          return exports;
       }
       MyProvider(Napi::CallbackInfo const&): Napi::ObjectWrap<MyProvider>(info) {
          // Use the "Factory" to create an object instance.
          m_provided = Provided::Create(info.Env(), "my value");
    
       }
    private:
       Provided m_provided;
       static Napi::FunctionReference constructor;
    }