commit 908ff85e75c46c295f57f9a0be8aca3c07fc6bcb Author: Ivan Folgueira Bande Date: Sat Jan 13 13:14:57 2024 +0100 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09cab87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +dist/ +src/examples/simple/package-lock.json +src/examples/simple/node_modules/ +src/build/ +package-lock.json +src/node_modules/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..133af6a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/vendor/nwaku"] + path = src/vendor/nwaku + url = git@github.com:waku-org/nwaku.git diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..5779e29 --- /dev/null +++ b/.npmignore @@ -0,0 +1,6 @@ +src/vendor/ +src/build/Release/ +*.map +*.spec.* +SetupPackage.* +version.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab9417e --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ + +## Intro + +This is a NodeJs wrapper around Waku + +## Requirements + +node-gyp is required to be installed in the system. + +The following commands should be run to install npm dependencies: + +cd src && npm install bindings + +## Create a package + +1. Run `npm run prepare-package` +2. Run `npm run build-libwaku` (step required to build the libwaku.so library) +3. Run `npm run build-gyp` +4. Run `npm run build-package` + +## Publish a package + +Run `npm run publish-package` + +--- + +:information_source: See the package.json file for details about npm commands definitions. + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..43818ac --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "nodejs-waku", + "version": "0.1.11", + "description": "NodeJs wrapper of a Waku node", + "main": "waku.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "clean": "rm -rf dist", + "compile": "tsc src/SetupPackage.ts --outDir dist/", + "prepare-package": "mkdir dist && npm run clean && npm run compile && node dist/SetupPackage.js && cp src/distributed_readme.md dist/", + "build-libwaku": "cd src/vendor/nwaku && yes Y | make libwaku && cp build/libwaku.so ../../../dist/", + "build-gyp": "node-gyp configure --directory=src/ && node-gyp build --directory=src/ && cp -r src/build/ dist/", + "build-package": "cd dist && npm pack", + "publish-package": "cd dist && npm publish --access public" + }, + "author": "", + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "bindings": "^1.5.0", + "libxmljs": "^1.0.11", + "link": "^1.5.1", + "node-addon-api": "^6.1.0", + "node-fetch": "^2.6.11" + }, + "devDependencies": { + "node-gyp": "^9.4.1" + } +} diff --git a/src/SetupPackage.ts b/src/SetupPackage.ts new file mode 100644 index 0000000..cea1c82 --- /dev/null +++ b/src/SetupPackage.ts @@ -0,0 +1,23 @@ +import * as fs from 'fs' + +// DO NOT DELETE THIS FILE +// This file is used by build system to build a clean npm package with the compiled js +// files in the root of the package. +// It will not be included in the npm package. + +function main() { + const source = fs.readFileSync(__dirname + "/../package.json").toString('utf-8'); + const sourceObj = JSON.parse(source); + sourceObj.scripts = {}; + sourceObj.devDependencies = {}; + if (sourceObj.main.startsWith("dist/")) { + sourceObj.main = sourceObj.main.slice(5); + } + fs.writeFileSync(__dirname + "/package.json", Buffer.from(JSON.stringify(sourceObj, null, 2), "utf-8") ); + fs.writeFileSync(__dirname + "/version.txt", Buffer.from(sourceObj.version, "utf-8") ); + fs.copyFileSync(__dirname + "/../.npmignore", __dirname + "/.npmignore"); + fs.copyFileSync(__dirname + "/../src/waku.js", __dirname + "/waku.js"); + fs.cpSync(__dirname + "/../src/node_modules/bindings", __dirname + "/node_modules/bindings", {recursive: true}); +} + +main(); diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..0f9acdf --- /dev/null +++ b/src/base64.c @@ -0,0 +1,58 @@ + +#include "base64.h" + +// Base64 encoding +// source: https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/ +size_t b64_encoded_size(size_t inlen) +{ + size_t ret; + + ret = inlen; + if (inlen % 3 != 0) + ret += 3 - (inlen % 3); + ret /= 3; + ret *= 4; + + return ret; +} + +const char b64chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +char *b64_encode(const unsigned char *in, size_t len) +{ + char *out; + size_t elen; + size_t i; + size_t j; + size_t v; + + if (in == NULL || len == 0) + return NULL; + + elen = b64_encoded_size(len); + out = malloc(elen+1); + out[elen] = '\0'; + + for (i=0, j=0; i> 18) & 0x3F]; + out[j+1] = b64chars[(v >> 12) & 0x3F]; + if (i+1 < len) { + out[j+2] = b64chars[(v >> 6) & 0x3F]; + } else { + out[j+2] = '='; + } + if (i+2 < len) { + out[j+3] = b64chars[v & 0x3F]; + } else { + out[j+3] = '='; + } + } + + return out; +} + +// End of Base64 encoding diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..37ca50f --- /dev/null +++ b/src/base64.h @@ -0,0 +1,11 @@ + +#ifndef _BASE64_H_ +#define _BASE64_H_ + +#include + +size_t b64_encoded_size(size_t inlen); + +char *b64_encode(const unsigned char *in, size_t len); + +#endif diff --git a/src/binding.gyp b/src/binding.gyp new file mode 100644 index 0000000..bdbe495 --- /dev/null +++ b/src/binding.gyp @@ -0,0 +1,9 @@ +{ + "targets": [ + { + "target_name": "waku", + "sources": [ "waku_addon.c", "base64.c" ], + "libraries": [ "-lwaku", "-L../vendor/nwaku/build/" ] + } + ] +} diff --git a/src/distributed_readme.md b/src/distributed_readme.md new file mode 100644 index 0000000..91f6324 --- /dev/null +++ b/src/distributed_readme.md @@ -0,0 +1,31 @@ + +## Intro + +NodeJs integration of Waku node. + +## Exported functions: + +`wakuNew = function(cfg)` +`wakuVersion = function()` +`wakuDefaultPubsubTopic = function()` +`wakuSetEventCallback = function(eventHandler)` +`wakuStart = function()` +`wakuConnect = function(peerAddr, timeoutMs, onErr)` +`wakuRelaySubscribe = function(pubsubTopic, onErr)` + +## Usage example + +``` +var wakuMod = require('nodejs-waku'); + +var cfg = `{ + "host": "0.0.0.0", + "port": 60001, + "key": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", + "relay": true +}`; + +wakuMod.wakuNew(cfg); + +wakuMod.wakuVersion(); +``` \ No newline at end of file diff --git a/src/examples/simple/package.json b/src/examples/simple/package.json new file mode 100644 index 0000000..56152d5 --- /dev/null +++ b/src/examples/simple/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "nodejs-waku": "^0.1.11" + } +} diff --git a/src/examples/simple/waku_example.js b/src/examples/simple/waku_example.js new file mode 100644 index 0000000..9f2325f --- /dev/null +++ b/src/examples/simple/waku_example.js @@ -0,0 +1,51 @@ + +var wakuMod = require('nodejs-waku'); + +var cfg = `{ + "host": "0.0.0.0", + "port": 60001, + "key": "364d111d729a6eb6d3e6113e163f017b5ef03a6f94c9b5b7bb1bb36fa5cb07a9", + "relay": true +}`; + +wakuMod.wakuNew(cfg); + +wakuMod.wakuVersion(); + +/* + +exports.wakuNew = function(cfg) { + wakuMod.wakuNew(cfg) +} + +exports.wakuVersion = function() { + wakuMod.wakuVersion(function(msg){ console.log("Waku Version: " + msg) }) +} + +exports.wakuShowDefaultPubsubTopic = function() { + // Example on how to retrieve a value from the waku library + var defaultPubsubTopic = "" + wakuMod.wakuDefaultPubsubTopic(function(msg){ defaultPubsubTopic = msg }) + console.log("Default pubsub topic: " + defaultPubsubTopic) +} + +exports.wakuSetEventCallback = function(eventHandler) { + console.log("Setting callback event callback function") + wakuMod.wakuSetEventCallback(eventHandler) +} + +exports.wakuStart = function() { + wakuMod.wakuStart() +} + +exports.wakuConnect = function(peerAddr, timeoutMs, onErr) { + wakuMod.wakuConnect(peerAddr, timeoutMs, onErr) +} + +exports.wakuRelaySubscribe = function(pubsubTopic, onErr) { + wakuMod.wakuRelaySubscribe(pubsubTopic, onErr) +} + +*/ + + diff --git a/src/vendor/nwaku b/src/vendor/nwaku new file mode 160000 index 0000000..e9d083b --- /dev/null +++ b/src/vendor/nwaku @@ -0,0 +1 @@ +Subproject commit e9d083b2786c95a0fc1def1b4f21f4d73ae56402 diff --git a/src/waku.js b/src/waku.js new file mode 100644 index 0000000..62f53ff --- /dev/null +++ b/src/waku.js @@ -0,0 +1,35 @@ + +var wakuMod = require('bindings')('waku'); + +exports.wakuNew = function(cfg) { + wakuMod.wakuNew(cfg) +} + +exports.wakuVersion = function() { + wakuMod.wakuVersion(function(msg){ console.log("Waku Version: " + msg) }) +} + +exports.wakuDefaultPubsubTopic = function() { + // Example on how to retrieve a value from the waku library + var defaultPubsubTopic = "" + wakuMod.wakuDefaultPubsubTopic(function(msg){ defaultPubsubTopic = msg }) + console.log("Default pubsub topic: " + defaultPubsubTopic) +} + +exports.wakuSetEventCallback = function(eventHandler) { + console.log("Setting callback event callback function") + wakuMod.wakuSetEventCallback(eventHandler) +} + +exports.wakuStart = function() { + wakuMod.wakuStart() +} + +exports.wakuConnect = function(peerAddr, timeoutMs, onErr) { + wakuMod.wakuConnect(peerAddr, timeoutMs, onErr) +} + +exports.wakuRelaySubscribe = function(pubsubTopic, onErr) { + wakuMod.wakuRelaySubscribe(pubsubTopic, onErr) +} + diff --git a/src/waku_addon.c b/src/waku_addon.c new file mode 100644 index 0000000..33d9988 --- /dev/null +++ b/src/waku_addon.c @@ -0,0 +1,586 @@ +#include +#include +#include +#include +#include + +#include "base64.h" +#include "vendor/nwaku/library/libwaku.h" + +// Reference to the NodeJs function to be called when a waku event occurs. +// static napi_ref ref_event_callback = NULL; +static napi_ref ref_version_callback = NULL; +static napi_ref ref_def_pubsub_topic_callback = NULL; +static napi_ref ref_on_error_callback = NULL; +static napi_threadsafe_function thsafe_fn = NULL; + +// As a convenience, wrap N-API calls such that they cause Node.js to abort +// when they are unsuccessful. +#define NAPI_CALL(call) \ +do { \ + napi_status status = call; \ + if (status != napi_ok) { \ + napi_fatal_error(#call, NAPI_AUTO_LENGTH, "failed", NAPI_AUTO_LENGTH); \ + } \ +} while (0) + +#define WAKU_CALL(call) \ +do { \ + int ret = call; \ + if (ret != 0) { \ + char msg[128]; \ + snprintf(msg, 128, "WAKU_CALL failed with code %d", ret); \ + napi_fatal_error(#call, NAPI_AUTO_LENGTH, "failed", NAPI_AUTO_LENGTH); \ + } \ +} while (0) + +// libwaku Context +void* ctx; + +// For the case of C language we don't need to store a particular userData +void* userData = NULL; + +static napi_env my_env; + +// This function is responsible for converting data coming in from the worker +// thread to napi_value items that can be passed into JavaScript, and for +// calling the JavaScript function. +static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) { + + // This parameter is not used. + (void) context; + // napi_status status; + + // Retrieve the message item created by the worker thread. + char* msg = (char*) data; + + // env and js_cb may both be NULL if Node.js is in its cleanup phase, and + // items are left over from earlier thread-safe calls from the worker thread. + // When env is NULL, we simply skip over the call into Javascript and free the + // items. + if (env != NULL) { + napi_value undefined; + + // Convert the integer to a napi_value. + napi_value napi_msg; + NAPI_CALL(napi_create_string_utf8(my_env, + msg, + NAPI_AUTO_LENGTH, + &napi_msg)); + + // Retrieve the JavaScript `undefined` value so we can use it as the `this` + // value of the JavaScript function call. + NAPI_CALL(napi_get_undefined(env, &undefined)); + + // Call the JavaScript function and pass it the message generated in the + // working thread. + NAPI_CALL(napi_call_function(env, + undefined, + js_cb, + 1, + &napi_msg, + NULL)); + } + + // Free the item created by the worker thread. + free(data); +} + +void handle_waku_version(int callerRet, const char* msg, size_t len) { + if (ref_version_callback == NULL) { + napi_throw_type_error(my_env, NULL, "ERROR in event_handler. ref_version_callback == NULL"); + } + + napi_value callback; + NAPI_CALL(napi_get_reference_value(my_env, ref_version_callback, &callback)); + + size_t argc = 2; + napi_value napi_msg; + NAPI_CALL(napi_create_string_utf8(my_env, + msg, + NAPI_AUTO_LENGTH, + &napi_msg)); + napi_value napi_len; + NAPI_CALL(napi_create_int32(my_env, + len, + &napi_len)); + + napi_value global; + NAPI_CALL(napi_get_global(my_env, &global)); + NAPI_CALL(napi_call_function(my_env, global, callback, argc, &napi_msg, NULL)); +} + +// This function is directly passed as a callback to the libwaku and it +// calls a NodeJs function if it has been set. +void event_handler(int callerRet, const char* msg, size_t len) { + if (thsafe_fn == NULL) { + // if (ref_event_callback == NULL) { + napi_throw_type_error(my_env, NULL, "ERROR in event_handler. ref_event_callback == NULL"); + } + + char* allocated_msg = malloc(len + 1); + strcpy(allocated_msg, msg); + + NAPI_CALL(napi_call_threadsafe_function(thsafe_fn, allocated_msg, napi_tsfn_nonblocking)); +} + +void handle_error(int callerRet, const char* msg, size_t len) { + if (ref_on_error_callback == NULL) { + napi_throw_type_error(my_env, NULL, "ERROR in event_handler. ref_on_error_callback == NULL"); + } + + napi_value callback; + NAPI_CALL(napi_get_reference_value(my_env, + ref_on_error_callback, + &callback)); + size_t argc = 2; + napi_value napi_msg; + NAPI_CALL(napi_create_string_utf8(my_env, + msg, + NAPI_AUTO_LENGTH, + &napi_msg)); + napi_value global; + NAPI_CALL(napi_get_global(my_env, &global)); + NAPI_CALL(napi_call_function(my_env, global, callback, argc, &napi_msg, NULL)); +} + +char* contentTopic = NULL; +void handle_content_topic(int callerRet, const char* msg, size_t len) { + if (contentTopic != NULL) { + free(contentTopic); + } + + contentTopic = malloc(len * sizeof(char) + 1); + strcpy(contentTopic, msg); +} + +void handle_default_pubsub_topic(int callerRet, const char* msg, size_t len) { + if (ref_def_pubsub_topic_callback == NULL) { + napi_throw_type_error(my_env, NULL, + "ERROR in event_handler. ref_def_pubsub_topic_callback == NULL"); + } + + napi_value callback; + NAPI_CALL(napi_get_reference_value(my_env, + ref_def_pubsub_topic_callback, + &callback)); + size_t argc = 2; + napi_value napi_msg; + NAPI_CALL(napi_create_string_utf8(my_env, + msg, + NAPI_AUTO_LENGTH, + &napi_msg)); + napi_value napi_len; + NAPI_CALL(napi_create_int32(my_env, + len, + &napi_len)); + + napi_value global; + NAPI_CALL(napi_get_global(my_env, &global)); + NAPI_CALL(napi_call_function(my_env, global, callback, argc, &napi_msg, NULL)); +} + +// The next should be called always, at the beginning +static napi_value WakuNew(napi_env env, napi_callback_info info) { + + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + size_t str_size; + size_t str_size_read; + napi_get_value_string_utf8(env, args[0], NULL, 0, &str_size); + char* jsonConfig; + jsonConfig = malloc(str_size + 1); + str_size = str_size + 1; + napi_get_value_string_utf8(env, args[0], jsonConfig, str_size, &str_size_read); + + ctx = waku_new(jsonConfig, event_handler, userData); + + free(jsonConfig); + + return NULL; +} + +static napi_value WakuVersion(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + napi_value cb = args[0]; + + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + my_env = env; + if(ref_version_callback != NULL) { + NAPI_CALL(napi_delete_reference(env, ref_version_callback)); + } + + NAPI_CALL(napi_create_reference(env, cb, 1, &ref_version_callback)); + + WAKU_CALL( waku_version(&ctx, handle_waku_version, userData) ); + + return NULL; +} + +static napi_value WakuSetEventCallback(napi_env env, napi_callback_info info) { + + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + napi_value cb = args[0]; + + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + my_env = env; + + napi_value work_name; + NAPI_CALL(napi_create_string_utf8(env, + "worker_name", + NAPI_AUTO_LENGTH, + &work_name)); + + NAPI_CALL( + napi_create_threadsafe_function(env, + cb, + NULL, + work_name, + 0, + 1, + NULL, + NULL, + NULL, + CallJs, // the C/C++ callback function + // out: the asynchronous thread-safe JavaScript function + &thsafe_fn)); + + // Inside 'event_handler', the event will be dispatched to the NodeJs + // if there is a proper napi_function (ref_event_callback) being set. + waku_set_event_callback(event_handler, userData); + + return NULL; +} + +static napi_value WakuStart(napi_env env, napi_callback_info info) { + waku_start(&ctx, event_handler, userData); + return NULL; +} + +static napi_value WakuConnect(napi_env env, napi_callback_info info) { + size_t argc = 3; + napi_value args[3]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 3) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + // Getting the peers param + napi_value napiPeers = args[0]; + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, napiPeers, &valueType)); + + if (valueType != napi_string) { + napi_throw_type_error(env, NULL, "The peers attribute should be a string"); + return NULL; + } + + size_t str_size; + size_t str_size_read; + napi_get_value_string_utf8(env, napiPeers, NULL, 0, &str_size); + char* peers; + peers = malloc(str_size + 1); + str_size = str_size + 1; + napi_get_value_string_utf8(env, napiPeers, peers, str_size, &str_size_read); + + // Getting the timeout param + napi_value napiTimeout = args[1]; + NAPI_CALL(napi_typeof(env, napiTimeout, &valueType)); + + if (valueType != napi_number) { + napi_throw_type_error(env, NULL, "The timeout attribute should be a number"); + return NULL; + } + + int32_t timeoutMs; + NAPI_CALL(napi_get_value_int32(env, napiTimeout, &timeoutMs)); + + // Getting the 'onError' callback + napi_value cb = args[2]; + + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + my_env = env; + NAPI_CALL(napi_create_reference(env, cb, 1, &ref_on_error_callback)); + + WAKU_CALL(waku_connect(&ctx, peers, timeoutMs, handle_error, userData)); + + // Free allocated memory + free(peers); + + return NULL; +} + +static napi_value WakuRelayPublish(napi_env env, napi_callback_info info) { + size_t argc = 5; + napi_value args[5]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 5) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + // pubsubtopic + napi_value napiPubsubTopic = args[0]; + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, napiPubsubTopic, &valueType)); + + if (valueType != napi_string) { + napi_throw_type_error(env, NULL, "The napiPubsubTopic attribute should be a string"); + return NULL; + } + + size_t str_size; + size_t str_size_read; + napi_get_value_string_utf8(env, napiPubsubTopic, NULL, 0, &str_size); + char* pubsub_topic; + pubsub_topic = malloc(str_size + 1); + str_size = str_size + 1; + napi_get_value_string_utf8(env, napiPubsubTopic, pubsub_topic, str_size, &str_size_read); + + // content topic + napi_value napiContentTopic = args[1]; + NAPI_CALL(napi_typeof(env, napiContentTopic, &valueType)); + + if (valueType != napi_string) { + napi_throw_type_error(env, NULL, "The content topic attribute should be a string"); + return NULL; + } + + napi_get_value_string_utf8(env, napiContentTopic, NULL, 0, &str_size); + char* content_topic_name = malloc(str_size + 1); + str_size = str_size + 1; + napi_get_value_string_utf8(env, napiContentTopic, content_topic_name, str_size, &str_size_read); + + // message + napi_value napiMessage = args[2]; + NAPI_CALL(napi_typeof(env, napiMessage, &valueType)); + + if (valueType != napi_string) { + napi_throw_type_error(env, NULL, "The message attribute should be a string"); + return NULL; + } + + char msg[2048]; + // TODO: check the correct message size limit + size_t lengthMsg; + NAPI_CALL(napi_get_value_string_utf8(env, + napiMessage, + msg, + 2048, + &lengthMsg)); + char jsonWakuMsg[1024]; + char *msgPayload = b64_encode((unsigned char*) msg, strlen(msg)); + + // TODO: move all the 'waku_content_topic' logic inside the libwaku + WAKU_CALL( waku_content_topic(&ctx, + "appName", + 1, + content_topic_name, + "encoding", + handle_content_topic, + userData) ); + snprintf(jsonWakuMsg, + 1024, + "{\"payload\":\"%s\",\"content_topic\":\"%s\"}", + msgPayload, contentTopic); + free(msgPayload); + + // Getting the timeout parameter + unsigned int timeoutMs; + napi_value timeout = args[3]; + + NAPI_CALL(napi_typeof(env, timeout, &valueType)); + + if (valueType != napi_number) { + napi_throw_type_error(env, NULL, "The argument should be a napi_number"); + return NULL; + } + + NAPI_CALL(napi_get_value_int64(env, timeout, (int64_t *) &timeoutMs)); + + // Getting the 'onError' callback + napi_value cb = args[4]; + + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + NAPI_CALL(napi_create_reference(env, cb, 1, &ref_on_error_callback)); + + // Perform the actual 'publish' + WAKU_CALL( waku_relay_publish(&ctx, + pubsub_topic, + jsonWakuMsg, + timeoutMs, + handle_error, + userData) ); + free(pubsub_topic); + free(content_topic_name); + + return NULL; +} + +static napi_value WakuDefaultPubsubTopic(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 1) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + napi_value cb = args[0]; + + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + my_env = env; + if(ref_def_pubsub_topic_callback != NULL) { + NAPI_CALL(napi_delete_reference(env, ref_def_pubsub_topic_callback)); + } + + NAPI_CALL(napi_create_reference(env, cb, 1, &ref_def_pubsub_topic_callback)); + + WAKU_CALL( waku_default_pubsub_topic(&ctx, handle_default_pubsub_topic, userData) ); + + return NULL; +} + +static napi_value WakuRelaySubscribe(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + if (argc < 2) { + napi_throw_type_error(env, NULL, "Wrong number of arguments"); + return NULL; + } + + // Getting the pubsub topic param + napi_value topic = args[0]; + napi_valuetype valueType; + NAPI_CALL(napi_typeof(env, topic, &valueType)); + + if (valueType != napi_string) { + napi_throw_type_error(env, NULL, "The topic attribute should be a string"); + return NULL; + } + + size_t str_size; + size_t str_size_read; + napi_get_value_string_utf8(env, topic, NULL, 0, &str_size); + char* pubsub_topic; + pubsub_topic = malloc(str_size + 1); + str_size = str_size + 1; + napi_get_value_string_utf8(env, topic, pubsub_topic, str_size, &str_size_read); + + // Getting the 'onError' callback + napi_value cb = args[1]; + + NAPI_CALL(napi_typeof(env, cb, &valueType)); + + if (valueType != napi_function) { + napi_throw_type_error(env, NULL, "The argument should be a napi_function"); + return NULL; + } + + my_env = env; + NAPI_CALL(napi_create_reference(env, cb, 1, &ref_on_error_callback)); + + // Calling the actual 'subscribe' waku function + WAKU_CALL( waku_relay_subscribe(&ctx, pubsub_topic, handle_error, userData) ); + + free(pubsub_topic); + + return NULL; +} + +#define DECLARE_NAPI_METHOD(name, func) \ + { name, 0, func, 0, 0, 0, napi_default, 0 } + +static napi_value Init(napi_env env, napi_value exports) { + // DECLARE_NAPI_METHOD("", ); + + napi_property_descriptor wakuVersion = DECLARE_NAPI_METHOD("wakuVersion", WakuVersion); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuVersion)); + + napi_property_descriptor wakuNew = DECLARE_NAPI_METHOD("wakuNew", WakuNew); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuNew)); + + napi_property_descriptor wakuStart = DECLARE_NAPI_METHOD("wakuStart", WakuStart); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuStart)); + + napi_property_descriptor wakuSetEventCallback = DECLARE_NAPI_METHOD("wakuSetEventCallback", WakuSetEventCallback); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuSetEventCallback)); + + napi_property_descriptor wakuDefaultPubsubTopic = DECLARE_NAPI_METHOD("wakuDefaultPubsubTopic", WakuDefaultPubsubTopic); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuDefaultPubsubTopic)); + + napi_property_descriptor wakuRelaySubscribe = DECLARE_NAPI_METHOD("wakuRelaySubscribe", WakuRelaySubscribe); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuRelaySubscribe)); + + napi_property_descriptor wakuConnect = DECLARE_NAPI_METHOD("wakuConnect", WakuConnect); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuConnect)); + + napi_property_descriptor wakuRelayPublish = DECLARE_NAPI_METHOD("wakuRelayPublish", WakuRelayPublish); + NAPI_CALL(napi_define_properties(env, exports, 1, &wakuRelayPublish)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)