├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── appveyor.yml ├── binding.gyp ├── images ├── demo.gif └── post-pwa-paradite.png ├── index.js ├── package.json ├── src ├── main.cpp ├── napi-thread-safe-callback-impl.hpp └── napi-thread-safe-callback.hpp └── test ├── mocha.opts └── network-interface.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | **/.* 2 | node_modules 3 | test 4 | src 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true, 8 | "mocha": true 9 | }, 10 | "plugins": [ 11 | "mocha" 12 | ], 13 | // https://github.com/feross/eslint-config-standard 14 | "rules": { 15 | "accessor-pairs": 2, 16 | "block-scoped-var": 0, 17 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 18 | "camelcase": 0, 19 | "comma-dangle": [2, "never"], 20 | "comma-spacing": [2, { "before": false, "after": true }], 21 | "comma-style": [2, "last"], 22 | "complexity": 0, 23 | "consistent-return": 0, 24 | "consistent-this": 0, 25 | "curly": [2, "multi-line"], 26 | "default-case": 0, 27 | "dot-location": [2, "property"], 28 | "dot-notation": 0, 29 | "eol-last": 2, 30 | "eqeqeq": [2, "allow-null"], 31 | "func-names": 0, 32 | "func-style": 0, 33 | "generator-star-spacing": [2, "both"], 34 | "guard-for-in": 0, 35 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], 36 | "indent": [2, 2], 37 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 38 | "linebreak-style": 0, 39 | "max-depth": 0, 40 | "max-len": 0, 41 | "max-nested-callbacks": 0, 42 | "max-params": 0, 43 | "max-statements": 0, 44 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 45 | "new-parens": 2, 46 | "no-alert": 0, 47 | "no-array-constructor": 2, 48 | "no-bitwise": 0, 49 | "no-caller": 2, 50 | "no-catch-shadow": 0, 51 | "no-cond-assign": 2, 52 | "no-console": 0, 53 | "no-constant-condition": 0, 54 | "no-continue": 0, 55 | "no-control-regex": 2, 56 | "no-debugger": 2, 57 | "no-delete-var": 2, 58 | "no-div-regex": 0, 59 | "no-dupe-args": 2, 60 | "no-dupe-keys": 2, 61 | "no-duplicate-case": 2, 62 | "no-else-return": 0, 63 | "no-empty": 0, 64 | "no-empty-character-class": 2, 65 | "no-eq-null": 0, 66 | "no-eval": 2, 67 | "no-ex-assign": 2, 68 | "no-extend-native": 2, 69 | "no-extra-bind": 2, 70 | "no-extra-boolean-cast": 2, 71 | "no-extra-semi": 0, 72 | "no-extra-strict": 0, 73 | "no-fallthrough": 2, 74 | "no-floating-decimal": 2, 75 | "no-func-assign": 2, 76 | "no-implied-eval": 2, 77 | "no-inline-comments": 0, 78 | "no-inner-declarations": [2, "functions"], 79 | "no-invalid-regexp": 2, 80 | "no-irregular-whitespace": 2, 81 | "no-iterator": 2, 82 | "no-label-var": 2, 83 | "no-labels": 2, 84 | "no-lone-blocks": 2, 85 | "no-lonely-if": 0, 86 | "no-loop-func": 0, 87 | "no-mixed-requires": 0, 88 | "no-mixed-spaces-and-tabs": [2, false], 89 | "no-multi-spaces": 2, 90 | "no-multi-str": 2, 91 | "no-multiple-empty-lines": [2, { "max": 1 }], 92 | "no-native-reassign": 2, 93 | "no-negated-in-lhs": 2, 94 | "no-nested-ternary": 0, 95 | "no-new": 0, 96 | "no-new-func": 2, 97 | "no-new-object": 2, 98 | "no-new-require": 2, 99 | "no-new-wrappers": 2, 100 | "no-obj-calls": 2, 101 | "no-octal": 2, 102 | "no-octal-escape": 2, 103 | "no-path-concat": 0, 104 | "no-plusplus": 0, 105 | "no-process-env": 0, 106 | "no-process-exit": 0, 107 | "no-proto": 2, 108 | "no-redeclare": 2, 109 | "no-regex-spaces": 2, 110 | "no-reserved-keys": 0, 111 | "no-restricted-modules": 0, 112 | "no-return-assign": 2, 113 | "no-script-url": 0, 114 | "no-self-compare": 2, 115 | "no-sequences": 2, 116 | "no-shadow": 0, 117 | "no-shadow-restricted-names": 2, 118 | "no-spaced-func": 2, 119 | "no-sparse-arrays": 2, 120 | "no-sync": 0, 121 | "no-ternary": 0, 122 | "no-throw-literal": 2, 123 | "no-trailing-spaces": 2, 124 | "no-undef": 2, 125 | "no-undef-init": 2, 126 | "no-undefined": 0, 127 | "no-underscore-dangle": 0, 128 | "no-unneeded-ternary": 2, 129 | "no-unreachable": 2, 130 | "no-unused-expressions": 0, 131 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 132 | "no-use-before-define": 0, 133 | "no-var": 0, 134 | "no-void": 0, 135 | "no-warning-comments": 0, 136 | "no-with": 2, 137 | "no-extra-parens": 0, 138 | "object-curly-spacing": 0, 139 | "one-var": [2, { "initialized": "never" }], 140 | "operator-assignment": 0, 141 | "operator-linebreak": [2, "after"], 142 | "padded-blocks": 0, 143 | "quote-props": 0, 144 | "quotes": [1, "single", "avoid-escape"], 145 | "radix": 2, 146 | "semi": [2, "always"], 147 | "semi-spacing": 0, 148 | "sort-vars": 0, 149 | "keyword-spacing": [2], 150 | "space-before-blocks": [2, "always"], 151 | "space-before-function-paren": 0, 152 | "space-in-parens": [2, "never"], 153 | "space-infix-ops": 2, 154 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 155 | "spaced-comment": [2, "always"], 156 | "strict": 0, 157 | "use-isnan": 2, 158 | "valid-jsdoc": 0, 159 | "valid-typeof": 2, 160 | "vars-on-top": 0, 161 | "wrap-iife": [2, "any"], 162 | "wrap-regex": 0, 163 | "yoda": [2, "never"] 164 | } 165 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | build 5 | .vscode/c_cpp_properties.json 6 | *.gz 7 | *.sw* 8 | *.un~ 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "JS Debug Build", 8 | "console": "integratedTerminal", 9 | "program": "${workspaceFolder}/index.js", 10 | "preLaunchTask": "npm: build:debug" 11 | }, 12 | { 13 | "name": "Windows Attach", 14 | "type": "cppvsdbg", 15 | "request": "attach", 16 | "processId": "${command:pickProcess}" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "memory": "cpp" 4 | } 5 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/electron-modules/network-interface/337e0c429f33ddd42102f449dc7f2e446719f2b6/.vscode/tasks.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # network-interface 2 | 3 | --- 4 | 5 | [![electron modules][electron-modules-image]][electron-modules-url] 6 | [![NPM version][npm-image]][npm-url] 7 | [![build status][build-image]][build-url] 8 | [![node version][node-image]][node-url] 9 | [![npm download][download-image]][download-url] 10 | 11 | [electron-modules-image]: https://img.shields.io/badge/electron-modules-blue.svg 12 | [electron-modules-url]: https://github.com/electron-modules/electron-modules 13 | [npm-image]: https://img.shields.io/npm/v/network-interface.svg 14 | [npm-url]: https://npmjs.org/package/network-interface 15 | [build-image]: https://img.shields.io/appveyor/build/electron-modules/network-interface.svg?logo=appveyor 16 | [build-url]: https://ci.appveyor.com/project/electron-modules/network-interface 17 | [node-image]: https://img.shields.io/badge/node.js-%3E=_8-green.svg 18 | [node-url]: http://nodejs.org/download/ 19 | [download-image]: https://img.shields.io/npm/dm/network-interface.svg 20 | [download-url]: https://npmjs.org/package/network-interface 21 | 22 | > Operating system network-related library for Node.js is used to obtain hardware status and network environment changes, etc. 23 | 24 | ## Introduction 25 | 26 | The goal of this project is to provide a library that can be used by Node.js so that we can accurately obtain the current computer network status and support the integrated use of frameworks like Electron. At this stage, only some APIs of [wlanapi](https://docs.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanregisternotification) and [netlistmgr](https://docs.microsoft.com/en-us/windows/win32/api/netlistmgr/nn-netlistmgr-inetworkconnectionevents) are wrapped. 27 | 28 | ### More cases 29 | 30 |

31 | 32 |

33 | 34 | 0. https://paradite.com/2016/09/28/my-experience-building-progressive-web-app-pwa 35 | 1. https://github.com/electron/electron/issues/11290 36 | 37 | ## Installment 38 | 39 | ```bash 40 | $ npm i network-interface --save 41 | ``` 42 | 43 | ## Usage 44 | 45 |

46 | 47 |

48 | 49 | ```javascript 50 | const networkInterface = require('network-interface'); 51 | 52 | networkInterface.addEventListener('wlan-status-changed', (error, data) => { 53 | if (error) { 54 | throw error; 55 | return; 56 | } 57 | console.log('event fired: wlan-status-changed'); 58 | console.log(data); 59 | }); 60 | ``` 61 | 62 | ## Future plan 63 | 64 | Only supports windows platform? Because I don’t need to solve the problems of other platforms, others may be supported in the future, and hope you can give some contributions. 65 | 66 | 67 | 68 | ## Contributors 69 | 70 | |[
xudafeng](https://github.com/xudafeng)
| 71 | | :---: | 72 | 73 | 74 | This project follows the git-contributor [spec](https://github.com/xudafeng/git-contributor), auto updated at `Wed Dec 08 2021 11:38:44 GMT+0800`. 75 | 76 | 77 | 78 | ## License 79 | 80 | The MIT License (MIT) 81 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - GYP_MSVS_VERSION: 2015 4 | nodejs_version: "12" 5 | 6 | platform: 7 | - x86 8 | - x64 9 | 10 | install: 11 | - ps: Install-Product node $env:nodejs_version 12 | - npm i --msvs_version=%GYP_MSVS_VERSION% 13 | 14 | test_script: 15 | - npm run build:release 16 | - npm run test 17 | 18 | build: off 19 | 20 | version: "{build}" -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "network-interface", 5 | "sources": [ 6 | "./src/main.cpp" 7 | ], 8 | "include_dirs": [ 9 | " 4 | #include 5 | #include 6 | #include 7 | #include "napi-thread-safe-callback.hpp" 8 | 9 | #pragma comment(lib, "wlanapi.lib") 10 | 11 | static std::shared_ptr notifyCallbackForJsFn = nullptr; 12 | 13 | void OnNotificationCallback(PWLAN_NOTIFICATION_DATA data, PVOID context) { 14 | if (data != NULL 15 | && data -> NotificationSource == WLAN_NOTIFICATION_SOURCE_ACM) { 16 | switch (data -> NotificationCode) { 17 | case wlan_notification_acm_connection_complete: { 18 | notifyCallbackForJsFn -> call( 19 | [](Napi::Env env, 20 | std::vector& args) { 21 | // will run in main thread 22 | Napi::Object obj = Napi::Object::New(env); 23 | obj.Set("type", "wlan"); 24 | obj.Set("code", "wlan_notification_acm_connection_complete"); 25 | args = { env.Null(), obj }; 26 | }); 27 | } break; 28 | case wlan_notification_acm_disconnected: { 29 | notifyCallbackForJsFn -> call( 30 | [](Napi::Env env, 31 | std::vector& args) { 32 | Napi::Object obj = Napi::Object::New(env); 33 | obj.Set("type", "wlan"); 34 | obj.Set("code", "wlan_notification_acm_disconnected"); 35 | args = { env.Null(), obj }; 36 | }); 37 | } break; 38 | case wlan_notification_acm_scan_complete: { 39 | notifyCallbackForJsFn -> call([]( 40 | Napi::Env env, 41 | std::vector& args) { 42 | Napi::Object obj = Napi::Object::New(env); 43 | obj.Set("type", "wlan"); 44 | obj.Set("code", "wlan_notification_acm_scan_complete"); 45 | args = { env.Null(), obj }; 46 | }); 47 | } break; 48 | } 49 | } 50 | } 51 | 52 | void RunCallback(const Napi::CallbackInfo& info) { 53 | // TODO(xudafeng) info[0] is the event name 54 | notifyCallbackForJsFn = std::make_shared( 55 | info[1].As()); 56 | 57 | HANDLE hClient = NULL; 58 | DWORD dwMaxClient = 2; 59 | DWORD dwCurVersion = 0; 60 | DWORD dwResult = 0; 61 | dwResult = WlanOpenHandle(dwMaxClient, NULL, &dwCurVersion, &hClient); 62 | 63 | if (dwResult != ERROR_SUCCESS) { 64 | notifyCallbackForJsFn -> call( 65 | [](Napi::Env env, 66 | std::vector& args) { 67 | auto err = Napi::Object::New(env); 68 | err.Set("message", Napi::String::New(env, "wlan open handle error")); 69 | args = { err }; 70 | }); 71 | return; 72 | } 73 | dwResult = WlanRegisterNotification( 74 | hClient, 75 | WLAN_NOTIFICATION_SOURCE_ACM, 76 | TRUE, 77 | WLAN_NOTIFICATION_CALLBACK(OnNotificationCallback), 78 | NULL, 79 | NULL, 80 | NULL); 81 | 82 | if (dwResult != ERROR_SUCCESS) { 83 | notifyCallbackForJsFn -> call( 84 | [](Napi::Env env, 85 | std::vector& args) { 86 | auto err = Napi::Object::New(env); 87 | err.Set( 88 | "message", 89 | Napi::String::New(env, "wlan rigister notification error")); 90 | args = { err }; 91 | }); 92 | } 93 | } 94 | 95 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 96 | exports.Set( 97 | Napi::String::New(env, "addEventListener"), 98 | Napi::Function::New(env, RunCallback)); 99 | return exports; 100 | } 101 | 102 | NODE_API_MODULE(main, Init); 103 | -------------------------------------------------------------------------------- /src/napi-thread-safe-callback-impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Needs to go first because of winsock issues 4 | #include 5 | #include 6 | #include 7 | 8 | class ThreadSafeCallback::Impl 9 | { 10 | public: 11 | Impl(Napi::Reference &&receiver, Napi::FunctionReference &&callback) 12 | : receiver_(std::move(receiver)), callback_(std::move(callback)), close_(false) 13 | { 14 | if (receiver_.IsEmpty()) 15 | receiver_ = Napi::Persistent(static_cast(Napi::Object::New(callback_.Env()))); 16 | uv_async_init(uv_default_loop(), &handle_, &static_async_callback); 17 | handle_.data = this; 18 | } 19 | 20 | void unref() 21 | { 22 | uv_unref(reinterpret_cast(&handle_)); 23 | } 24 | 25 | void call(arg_func_t arg_function, completion_func_t completion_function) 26 | { 27 | std::lock_guard lock(mutex_); 28 | function_pairs_.push_back({arg_function, completion_function}); 29 | uv_async_send(&handle_); 30 | } 31 | 32 | void close() 33 | { 34 | std::lock_guard lock(mutex_); 35 | close_ = true; 36 | uv_async_send(&handle_); 37 | } 38 | 39 | protected: 40 | using func_pair_t = std::pair; 41 | 42 | static void static_async_callback(uv_async_t *handle) 43 | { 44 | try 45 | { 46 | static_cast(handle->data)->async_callback(); 47 | } 48 | catch (std::exception& e) 49 | { 50 | Napi::Error::Fatal("", e.what()); 51 | } 52 | catch (...) 53 | { 54 | Napi::Error::Fatal("", "ERROR: Unknown exception during async callback"); 55 | } 56 | } 57 | 58 | void async_callback() 59 | { 60 | auto env = callback_.Env(); 61 | while (true) 62 | { 63 | std::vector func_pairs; 64 | { 65 | std::lock_guard lock(mutex_); 66 | if (function_pairs_.empty()) 67 | break; 68 | else 69 | func_pairs.swap(function_pairs_); 70 | } 71 | 72 | for (const auto &function_pair : func_pairs) 73 | { 74 | Napi::HandleScope scope(env); 75 | std::vector args; 76 | if (function_pair.first) 77 | function_pair.first(env, args); 78 | Napi::Value result(env, nullptr); 79 | Napi::Error error(env, nullptr); 80 | try 81 | { 82 | result = callback_.MakeCallback(receiver_.Value(), args); 83 | } 84 | catch (Napi::Error& err) 85 | { 86 | error = std::move(err); 87 | } 88 | if (function_pair.second) 89 | function_pair.second(result, error); 90 | else if (!error.IsEmpty()) 91 | throw std::runtime_error(error.Message()); 92 | } 93 | } 94 | 95 | if (close_) 96 | uv_close(reinterpret_cast(&handle_), [](uv_handle_t *handle) { 97 | delete static_cast(handle->data); 98 | }); 99 | } 100 | 101 | Napi::Reference receiver_; 102 | Napi::FunctionReference callback_; 103 | 104 | uv_async_t handle_; 105 | 106 | std::mutex mutex_; 107 | std::vector function_pairs_; 108 | bool close_; 109 | }; 110 | 111 | // public API 112 | 113 | inline ThreadSafeCallback::ThreadSafeCallback(const Napi::Function &callback) 114 | : ThreadSafeCallback(Napi::Value(), callback) 115 | {} 116 | 117 | inline ThreadSafeCallback::ThreadSafeCallback(const Napi::Value& receiver, const Napi::Function& callback) 118 | : impl(nullptr) 119 | { 120 | if (!receiver.IsEmpty() && !(receiver.IsObject() || receiver.IsFunction())) 121 | throw Napi::Error::New(callback.Env(), "Callback receiver must be an object or function"); 122 | if (!callback.IsFunction()) 123 | throw Napi::Error::New(callback.Env(), "Callback must be a function"); 124 | impl = new Impl(Napi::Persistent(receiver), Napi::Persistent(callback)); 125 | } 126 | 127 | inline void ThreadSafeCallback::unref() 128 | { 129 | impl->unref(); 130 | } 131 | 132 | inline ThreadSafeCallback::ThreadSafeCallback(ThreadSafeCallback&& other) 133 | : impl(other.impl) 134 | { 135 | other.impl = nullptr; 136 | } 137 | 138 | inline ThreadSafeCallback::~ThreadSafeCallback() 139 | { 140 | // Destruction of the impl is defered because: 141 | // 1) uv_async_close may only be called on nodejs main thread 142 | // 2) uv_async_t memory may only be freed in close callback 143 | if (impl) 144 | impl->close(); 145 | } 146 | 147 | inline std::future ThreadSafeCallback::operator()() 148 | { 149 | return operator()(arg_func_t(nullptr)); 150 | } 151 | 152 | inline std::future ThreadSafeCallback::operator()(arg_func_t arg_function) 153 | { 154 | auto promise = std::make_shared>(); 155 | operator()(arg_function, [promise](const Napi::Value &value, const Napi::Error &error) 156 | { 157 | try 158 | { 159 | if (error.IsEmpty()) 160 | promise->set_value(); 161 | else 162 | throw std::runtime_error(error.Message()); 163 | } 164 | catch (...) 165 | { 166 | try 167 | { 168 | promise->set_exception(std::current_exception()); 169 | } 170 | catch (...) 171 | { 172 | Napi::Error::Fatal("", "Unable to set exception on promise"); 173 | } 174 | } 175 | }); 176 | return promise->get_future(); 177 | } 178 | 179 | inline std::future ThreadSafeCallback::error(const std::string& message) 180 | { 181 | return operator()([message](napi_env env, std::vector& args) { 182 | args.push_back(Napi::Error::New(env, message).Value()); 183 | }); 184 | } 185 | 186 | inline void ThreadSafeCallback::operator()(completion_func_t completion_function) 187 | { 188 | operator()(nullptr, completion_function); 189 | } 190 | 191 | inline void ThreadSafeCallback::operator()(arg_func_t arg_function, completion_func_t completion_function) 192 | { 193 | if (impl) 194 | impl->call(arg_function, completion_function); 195 | else 196 | throw std::runtime_error("Callback called after move"); 197 | } 198 | 199 | inline void ThreadSafeCallback::error(const std::string& message, completion_func_t completion_function) 200 | { 201 | operator()([message](napi_env env, std::vector& args) { 202 | args.push_back(Napi::Error::New(env, message).Value()); 203 | }, completion_function); 204 | } 205 | 206 | inline std::future ThreadSafeCallback::callStringify() 207 | { 208 | return callStringify(nullptr); 209 | } 210 | 211 | inline std::future ThreadSafeCallback::callStringify(arg_func_t arg_function) 212 | { 213 | return call(arg_function, [](const Napi::Value& value) 214 | { 215 | auto JSON = value.Env().Global().Get("JSON").As(); 216 | auto stringify = JSON.Get("stringify").As(); 217 | return stringify.Call(JSON, {value}).As().Utf8Value(); 218 | }); 219 | } 220 | 221 | inline std::future ThreadSafeCallback::errorStringify(const std::string& message) 222 | { 223 | return callStringify([message](napi_env env, std::vector& args) 224 | { 225 | args.push_back(Napi::Error::New(env, message).Value()); 226 | }); 227 | } 228 | 229 | template 230 | inline std::future ThreadSafeCallback::call(std::function completion_function) 231 | { 232 | return call(nullptr, completion_function); 233 | } 234 | 235 | template 236 | inline std::future ThreadSafeCallback::call(arg_func_t arg_function, std::function completion_function) 237 | { 238 | auto promise = std::make_shared>(); 239 | operator()(arg_function, [promise, completion_function](const Napi::Value &value, const Napi::Error& error) 240 | { 241 | try 242 | { 243 | if (error.IsEmpty()) 244 | promise->set_value(completion_function(value)); 245 | else 246 | throw std::runtime_error(error.Message()); 247 | } 248 | catch (...) 249 | { 250 | try 251 | { 252 | promise->set_exception(std::current_exception()); 253 | } 254 | catch (...) 255 | { 256 | Napi::Error::Fatal("", "Unable to set exception on promise"); 257 | } 258 | } 259 | }); 260 | return promise->get_future(); 261 | } 262 | 263 | inline void ThreadSafeCallback::call() 264 | { 265 | operator()(nullptr, nullptr); 266 | } 267 | 268 | inline void ThreadSafeCallback::call(arg_func_t arg_function) 269 | { 270 | operator()(arg_function, nullptr); 271 | } 272 | 273 | inline void ThreadSafeCallback::callError(const std::string& message) 274 | { 275 | error(message, nullptr); 276 | } -------------------------------------------------------------------------------- /src/napi-thread-safe-callback.hpp: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifndef NAPI_CPP_EXCEPTIONS 11 | #error ThreadSafeCallback needs napi exception support 12 | #endif 13 | 14 | #if defined(_MSC_VER) && !_HAS_EXCEPTIONS 15 | #error Please define _HAS_EXCEPTIONS=1, otherwise exception handling will not work properly 16 | #endif 17 | 18 | class ThreadSafeCallback 19 | { 20 | public: 21 | // The argument function is responsible for providing napi_values which will 22 | // be used for invoking the callback. Since this touches JS state it must run 23 | // in the NodeJS main loop. 24 | using arg_func_t = std::function&)>; 25 | 26 | // The completion function is reponsible for handling the return value and/or 27 | // raised exception from calling the callback. Since this touches JS state it 28 | // must run in the NodeJS main loop. Either the Value or Error will be empty. 29 | using completion_func_t = std::function; 30 | 31 | // Both functions will be called within the same HandleScope 32 | 33 | // Must be called from Node event loop because it calls napi_create_reference and uv_async_init 34 | ThreadSafeCallback(const Napi::Function& callback); 35 | ThreadSafeCallback(const Napi::Value& receiver, const Napi::Function& callback); 36 | 37 | // Must be called from Node event loop because it calls uv_unref 38 | void unref(); 39 | 40 | // All other member functions can be called from any thread, including move constructor and destructor 41 | ThreadSafeCallback(ThreadSafeCallback&& other); 42 | ~ThreadSafeCallback(); 43 | 44 | // Invoke JS callback from any thread. These functions are additionally thread-safe, i.e. 45 | // they can be invoked concurrently 46 | 47 | // - return future, JS error is transformed to std::runtime_error 48 | std::future operator()(); 49 | std::future operator()(arg_func_t arg_function); 50 | std::future error(const std::string& message); 51 | 52 | // - handle result/error in NodeJS main thread 53 | void operator()(completion_func_t completion_function); 54 | void operator()(arg_func_t arg_function, completion_func_t completion_function); 55 | void error(const std::string& message, completion_func_t completion_function); 56 | 57 | // - return JSON result in future 58 | std::future callStringify(); 59 | std::future callStringify(arg_func_t arg_function); 60 | std::future errorStringify(const std::string& message); 61 | 62 | // - return JS value in future 63 | template 64 | std::future call(std::function completion_function); 65 | template 66 | std::future call(arg_func_t arg_function, std::function completion_function); 67 | 68 | // - ignore result, terminate on error 69 | void call(); 70 | void call(arg_func_t arg_function); 71 | void callError(const std::string& message); 72 | 73 | protected: 74 | // Cannot be copied or assigned 75 | ThreadSafeCallback(const ThreadSafeCallback&) = delete; 76 | ThreadSafeCallback& operator=(const ThreadSafeCallback&) = delete; 77 | ThreadSafeCallback& operator=(ThreadSafeCallback&&) = delete; 78 | 79 | class Impl; 80 | Impl* impl; 81 | }; 82 | 83 | #include "napi-thread-safe-callback-impl.hpp" -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --timeout 60000 -------------------------------------------------------------------------------- /test/network-interface.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const assert = require('assert'); 6 | 7 | const pkg = require('../package'); 8 | 9 | describe('./test/network-interface.test.js', function() { 10 | this.timeout(60 * 1000); 11 | let binaryFilePath; 12 | 13 | describe('build', () => { 14 | it('should build success', () => { 15 | binaryFilePath = path.join(__dirname, '..', 'build', 'Release', `${pkg.name}.node`); 16 | assert(fs.existsSync(binaryFilePath)); 17 | }); 18 | }); 19 | 20 | describe('addEventListener()', () => { 21 | // it('should be ok', (done) => { 22 | // const networkInterface = require(binaryFilePath); 23 | // networkInterface.addEventListener('wlan-status-changed', (error, data) => { 24 | // if (error) { 25 | // throw error; 26 | // return; 27 | // } 28 | // console.log('event fired: wlan-status-changed'); 29 | // console.log(data); 30 | // }); 31 | // setTimeout(done, 30 * 1000) 32 | // }); 33 | }); 34 | }); --------------------------------------------------------------------------------