├── .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 | });
--------------------------------------------------------------------------------