@openziti/ziti-sdk-nodejs 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.github/workflows/build.yml +220 -0
  2. package/.github/workflows/codeql-analysis.yml +71 -0
  3. package/.github/workflows/mattermost-ziti-webhook.yml +26 -0
  4. package/.gitmodules +4 -0
  5. package/.travis.yml-obsolete +99 -0
  6. package/CODE_OF_CONDUCT.md +17 -0
  7. package/CONTRIBUTING.md +6 -0
  8. package/LICENSE +201 -0
  9. package/README.md +155 -0
  10. package/appveyor.yml-obsolete +32 -0
  11. package/binding.gyp +227 -0
  12. package/lib/index.js +17 -0
  13. package/lib/ziti.js +40 -0
  14. package/package.json +56 -0
  15. package/scripts/build-appveyor.bat +198 -0
  16. package/scripts/install_node.sh +99 -0
  17. package/scripts/validate_tag.sh +24 -0
  18. package/src/Ziti_https_request.c +960 -0
  19. package/src/Ziti_https_request_data.c +250 -0
  20. package/src/Ziti_https_request_end.c +79 -0
  21. package/src/stack_traces.c +334 -0
  22. package/src/utils.c +108 -0
  23. package/src/utils.h +85 -0
  24. package/src/ziti-add-on.c +88 -0
  25. package/src/ziti-nodejs.h +209 -0
  26. package/src/ziti_close.c +79 -0
  27. package/src/ziti_dial.c +375 -0
  28. package/src/ziti_enroll.c +245 -0
  29. package/src/ziti_hello.c +52 -0
  30. package/src/ziti_init.c +315 -0
  31. package/src/ziti_service_available.c +222 -0
  32. package/src/ziti_shutdown.c +47 -0
  33. package/src/ziti_websocket_connect.c +458 -0
  34. package/src/ziti_websocket_write.c +235 -0
  35. package/src/ziti_write.c +223 -0
  36. package/tests/enroll-test.js +38 -0
  37. package/tests/hello.js +12 -0
  38. package/tests/https-test.js +206 -0
  39. package/tests/mattermost-test.js +124 -0
  40. package/tests/websocket-test.js +119 -0
  41. package/ziti.js +1 -0
  42. package/ziti.png +0 -0
@@ -0,0 +1,315 @@
1
+ /*
2
+ Copyright 2019-2020 Netfoundry, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ #include "ziti-nodejs.h"
18
+
19
+ ziti_context ztx = NULL;
20
+ uv_loop_t *loop = NULL;
21
+
22
+ uv_thread_t thread;
23
+ uv_async_t async;
24
+
25
+
26
+ typedef struct {
27
+ napi_async_work work;
28
+ napi_threadsafe_function tsfn;
29
+ } AddonData;
30
+
31
+
32
+ static const char *ALL_CONFIG_TYPES[] = {
33
+ "all",
34
+ NULL
35
+ };
36
+
37
+
38
+ /**
39
+ * This function is responsible for calling the JavaScript callback function
40
+ * that was specified when the ziti_init(...) was called from JavaScript.
41
+ */
42
+ static void CallJs(napi_env env, napi_value js_cb, void* context, void* data) {
43
+ napi_status status;
44
+
45
+ // This parameter is not used.
46
+ (void) context;
47
+
48
+ // env and js_cb may both be NULL if Node.js is in its cleanup phase, and
49
+ // items are left over from earlier thread-safe calls from the worker thread.
50
+ // When env is NULL, we simply skip over the call into Javascript
51
+ if (env != NULL) {
52
+ napi_value undefined, js_rc;
53
+
54
+ // Retrieve the JavaScript `undefined` value so we can use it as the `this`
55
+ // value of the JavaScript function call.
56
+ status = napi_get_undefined(env, &undefined);
57
+
58
+ // Retrieve the rc created by the worker thread.
59
+ int rc = (int)data;
60
+ status = napi_create_int64(env, (int64_t)rc, &js_rc);
61
+ if (status != napi_ok) {
62
+ napi_throw_error(env, NULL, "Failed to napi_create_int64");
63
+ }
64
+
65
+ // Call the JavaScript function and pass it the rc
66
+ status = napi_call_function(
67
+ env,
68
+ undefined,
69
+ js_cb,
70
+ 1,
71
+ &js_rc,
72
+ NULL
73
+ );
74
+ if (status != napi_ok) {
75
+ napi_throw_error(env, NULL, "Failed to napi_call_function");
76
+ }
77
+ }
78
+ }
79
+
80
+
81
+ /**
82
+ *
83
+ */
84
+ void on_ziti_init(ziti_context _ztx, int status, void* ctx) {
85
+ napi_status nstatus;
86
+
87
+ ZITI_NODEJS_LOG(DEBUG, "_ztx: %p", _ztx);
88
+
89
+ // Set the global ztx context variable
90
+ ztx = _ztx;
91
+
92
+ if (status == ZITI_OK) {
93
+
94
+ ziti_set_timeout(ztx, 5*1000);
95
+
96
+ }
97
+
98
+ AddonData* addon_data = (AddonData*)ctx;
99
+
100
+ // Initiate the call into the JavaScript callback.
101
+ // The call into JavaScript will not have happened
102
+ // when this function returns, but it will be queued.
103
+ nstatus = napi_call_threadsafe_function(
104
+ addon_data->tsfn,
105
+ (void*) (long) status,
106
+ napi_tsfn_blocking);
107
+ if (nstatus != napi_ok) {
108
+ ZITI_NODEJS_LOG(ERROR, "Unable to napi_call_threadsafe_function");
109
+ }
110
+ }
111
+
112
+
113
+ /**
114
+ *
115
+ */
116
+ static void on_ziti_event(ziti_context _ztx, const ziti_event_t *event) {
117
+ napi_status nstatus;
118
+
119
+ AddonData* addon_data = (AddonData*)ziti_app_ctx(_ztx);
120
+
121
+ switch (event->type) {
122
+
123
+ case ZitiContextEvent:
124
+ // Set the global ztx context variable
125
+ ztx = _ztx;
126
+
127
+ if (event->event.ctx.ctrl_status == ZITI_OK) {
128
+ const ziti_version *ctrl_ver = ziti_get_controller_version(_ztx);
129
+ const ziti_identity *proxy_id = ziti_get_identity(_ztx);
130
+ ZITI_NODEJS_LOG(INFO, "controller version = %s(%s)[%s]", ctrl_ver->version, ctrl_ver->revision, ctrl_ver->build_date);
131
+ ZITI_NODEJS_LOG(INFO, "identity = <%s>[%s]@%s", proxy_id->name, proxy_id->id, ziti_get_controller(_ztx));
132
+ }
133
+ else {
134
+ ZITI_NODEJS_LOG(ERROR, "Failed to connect to controller: %s", event->event.ctx.err);
135
+ }
136
+
137
+ // Initiate the call into the JavaScript callback.
138
+ // The call into JavaScript will not have happened
139
+ // when this function returns, but it will be queued.
140
+ nstatus = napi_call_threadsafe_function(
141
+ addon_data->tsfn,
142
+ (void*) (long) event->event.ctx.ctrl_status,
143
+ napi_tsfn_blocking);
144
+ if (nstatus != napi_ok) {
145
+ ZITI_NODEJS_LOG(ERROR, "Unable to napi_call_threadsafe_function");
146
+ }
147
+ break;
148
+
149
+ case ZitiServiceEvent:
150
+ if (event->event.service.removed != NULL) {
151
+ for (ziti_service **sp = event->event.service.removed; *sp != NULL; sp++) {
152
+ // service_check_cb(ztx, *sp, ZITI_SERVICE_UNAVAILABLE, app_ctx);
153
+ }
154
+ }
155
+
156
+ if (event->event.service.added != NULL) {
157
+ for (ziti_service **sp = event->event.service.added; *sp != NULL; sp++) {
158
+ // service_check_cb(ztx, *sp, ZITI_OK, app_ctx);
159
+ }
160
+ }
161
+ break;
162
+
163
+ case ZitiRouterEvent:
164
+ switch (event->event.router.status){
165
+ case EdgeRouterConnected:
166
+ ZITI_NODEJS_LOG(INFO, "ziti connected to edge router %s\nversion = %s", event->event.router.name, event->event.router.version);
167
+ break;
168
+ case EdgeRouterDisconnected:
169
+ ZITI_NODEJS_LOG(INFO, "ziti disconnected from edge router %s", event->event.router.name);
170
+ break;
171
+ case EdgeRouterRemoved:
172
+ ZITI_NODEJS_LOG(INFO, "ziti removed edge router %s", event->event.router.name);
173
+ break;
174
+ case EdgeRouterUnavailable:
175
+ ZITI_NODEJS_LOG(INFO, "edge router %s is not available", event->event.router.name);
176
+ break;
177
+ case EdgeRouterAdded:
178
+ ZITI_NODEJS_LOG(INFO, "edge router %s was added", event->event.router.name);
179
+ break;
180
+ }
181
+
182
+ default:
183
+ break;
184
+ }
185
+ }
186
+
187
+
188
+
189
+ static void child_thread(void *data){
190
+ uv_loop_t *thread_loop = (uv_loop_t *) data;
191
+
192
+ //Start this loop
193
+ uv_run(thread_loop, UV_RUN_DEFAULT);
194
+ }
195
+
196
+ static void consumer_notify(uv_async_t *handle, int status) { }
197
+
198
+ /**
199
+ *
200
+ */
201
+ napi_value _ziti_init(napi_env env, const napi_callback_info info) {
202
+ napi_status status;
203
+ napi_value jsRetval;
204
+
205
+ ZITI_NODEJS_LOG(DEBUG, "initializing");
206
+
207
+ // uv_mbed_set_debug(TRACE, stdout);
208
+
209
+ size_t argc = 2;
210
+ napi_value args[2];
211
+ status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
212
+ if (status != napi_ok) {
213
+ napi_throw_error(env, NULL, "Failed to parse arguments");
214
+ }
215
+
216
+ if (argc < 2) {
217
+ napi_throw_error(env, "EINVAL", "Too few arguments");
218
+ return NULL;
219
+ }
220
+
221
+ // Obtain length of location of identity file
222
+ size_t result;
223
+ size_t config_path_len;
224
+ status = napi_get_value_string_utf8(env, args[0], NULL, 0, &config_path_len);
225
+ if (status != napi_ok) {
226
+ napi_throw_error(env, "EINVAL", "location of identity file is not a string");
227
+ }
228
+ // Obtain length of location of identity file
229
+ char* config_file_name = calloc(1, config_path_len+2);
230
+ status = napi_get_value_string_utf8(env, args[0], config_file_name, config_path_len+1, &result);
231
+ if (status != napi_ok) {
232
+ napi_throw_error(env, "EINVAL", "Failed to obtain location of identity file");
233
+ }
234
+
235
+ ZITI_NODEJS_LOG(DEBUG, "config_file_name: %s", config_file_name);
236
+
237
+ // Obtain ptr to JS callback function
238
+ napi_value js_cb = args[1];
239
+ napi_value work_name;
240
+ AddonData* addon_data = malloc(sizeof(AddonData));
241
+
242
+ // Create a string to describe this asynchronous operation.
243
+ status = napi_create_string_utf8(
244
+ env,
245
+ "N-API on_ziti_init",
246
+ NAPI_AUTO_LENGTH,
247
+ &work_name);
248
+ if (status != napi_ok) {
249
+ napi_throw_error(env, NULL, "Failed to napi_create_string_utf8");
250
+ }
251
+
252
+ // Convert the callback retrieved from JavaScript into a thread-safe function (tsfn)
253
+ // which we can call from a worker thread.
254
+ status = napi_create_threadsafe_function(
255
+ env,
256
+ js_cb,
257
+ NULL,
258
+ work_name,
259
+ 0,
260
+ 1,
261
+ NULL,
262
+ NULL,
263
+ NULL,
264
+ CallJs,
265
+ &(addon_data->tsfn));
266
+ if (status != napi_ok) {
267
+ napi_throw_error(env, NULL, "Failed to napi_create_threadsafe_function");
268
+ }
269
+
270
+ // Create and set up the consumer thread
271
+ if (NULL == thread_loop) { // Spawn the loop only once
272
+ ZITI_NODEJS_LOG(DEBUG, "calling uv_loop_new()");
273
+ thread_loop = uv_loop_new();
274
+ uv_async_init(thread_loop, &async, (uv_async_cb)consumer_notify);
275
+ uv_thread_create(&thread, (uv_thread_cb)child_thread, thread_loop);
276
+ }
277
+
278
+ // Light this candle!
279
+ ziti_options *opts = calloc(1, sizeof(ziti_options));
280
+
281
+ opts->config = config_file_name;
282
+ opts->events = ZitiContextEvent|ZitiServiceEvent|ZitiRouterEvent;
283
+ opts->event_cb = on_ziti_event;
284
+ opts->refresh_interval = 60;
285
+ opts->router_keepalive = 10;
286
+ opts->app_ctx = addon_data;
287
+ opts->config_types = ALL_CONFIG_TYPES;
288
+ opts->metrics_type = INSTANT;
289
+
290
+ int rc = ziti_init_opts(opts, thread_loop);
291
+
292
+ status = napi_create_int32(env, rc, &jsRetval);
293
+ if (status != napi_ok) {
294
+ napi_throw_error(env, NULL, "Unable to create return value");
295
+ }
296
+
297
+ return jsRetval;
298
+ }
299
+
300
+
301
+ void expose_ziti_init(napi_env env, napi_value exports) {
302
+ napi_status status;
303
+ napi_value fn;
304
+
305
+ status = napi_create_function(env, NULL, 0, _ziti_init, NULL, &fn);
306
+ if (status != napi_ok) {
307
+ napi_throw_error(env, NULL, "Unable to wrap native function '_ziti_init");
308
+ }
309
+
310
+ status = napi_set_named_property(env, exports, "ziti_init", fn);
311
+ if (status != napi_ok) {
312
+ napi_throw_error(env, NULL, "Unable to populate exports for 'ziti_init");
313
+ }
314
+
315
+ }
@@ -0,0 +1,222 @@
1
+ /*
2
+ Copyright 2019-2020 Netfoundry, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ #include "ziti-nodejs.h"
18
+ #include <time.h>
19
+
20
+
21
+ typedef struct {
22
+ napi_async_work work;
23
+ napi_threadsafe_function tsfn;
24
+ } AddonData;
25
+
26
+ // An item that will be generated here and passed into the JavaScript service_available callback
27
+ typedef struct ServiceAvailableItem {
28
+ ssize_t status;
29
+ int permissions;
30
+ } ServiceAvailableItem;
31
+
32
+
33
+ /**
34
+ * This function is responsible for calling the JavaScript 'service_available' callback function
35
+ * that was specified when the ziti_service_available(...) was called from JavaScript.
36
+ */
37
+ static void CallJs_on_service_available(napi_env env, napi_value js_cb, void* context, void* data) {
38
+ napi_status status;
39
+
40
+ // This parameter is not used.
41
+ (void) context;
42
+
43
+ // Retrieve the ServiceAvailableItem created by the worker thread.
44
+ ServiceAvailableItem* item = (ServiceAvailableItem*)data;
45
+
46
+ // env and js_cb may both be NULL if Node.js is in its cleanup phase, and
47
+ // items are left over from earlier thread-safe calls from the worker thread.
48
+ // When env is NULL, we simply skip over the call into Javascript and free the
49
+ // items.
50
+ if (env != NULL) {
51
+
52
+ napi_value undefined;
53
+
54
+ // const obj = {}
55
+ napi_value js_service_available_item, js_status, js_permissions;
56
+ status = napi_create_object(env, &js_service_available_item);
57
+ if (status != napi_ok) {
58
+ napi_throw_error(env, NULL, "Unable to napi_create_object");
59
+ }
60
+
61
+ // obj.status = status
62
+ status = napi_create_int64(env, (int64_t)item->status, &js_status);
63
+ if (status != napi_ok) {
64
+ napi_throw_error(env, NULL, "Unable to napi_create_int64");
65
+ }
66
+ status = napi_set_named_property(env, js_service_available_item, "status", js_status);
67
+ if (status != napi_ok) {
68
+ napi_throw_error(env, NULL, "Unable to napi_set_named_property");
69
+ }
70
+
71
+ // obj.permissions = permissions
72
+ status = napi_create_int64(env, (int64_t)item->permissions, &js_permissions);
73
+ if (status != napi_ok) {
74
+ napi_throw_error(env, NULL, "Unable to napi_create_int64");
75
+ }
76
+ status = napi_set_named_property(env, js_service_available_item, "permissions", js_permissions);
77
+ if (status != napi_ok) {
78
+ napi_throw_error(env, NULL, "Unable to napi_set_named_property");
79
+ }
80
+
81
+ // Retrieve the JavaScript `undefined` value so we can use it as the `this`
82
+ // value of the JavaScript function call.
83
+ status = napi_get_undefined(env, &undefined);
84
+ if (status != napi_ok) {
85
+ napi_throw_error(env, NULL, "Unable to napi_get_undefined (4)");
86
+ }
87
+
88
+ // Call the JavaScript function and pass it the ServiceAvailableItem
89
+ status = napi_call_function(
90
+ env,
91
+ undefined,
92
+ js_cb,
93
+ 1,
94
+ &js_service_available_item,
95
+ NULL);
96
+ if (status != napi_ok) {
97
+ napi_throw_error(env, NULL, "Unable to napi_call_function");
98
+ }
99
+ }
100
+ }
101
+
102
+
103
+ /**
104
+ *
105
+ */
106
+ static void on_service_available(ziti_context nf_ctx, ziti_service* service, int status, void *ctx) {
107
+ napi_status nstatus;
108
+
109
+ AddonData* addon_data = (AddonData*)ctx;
110
+
111
+ ServiceAvailableItem* item = memset(malloc(sizeof(*item)), 0, sizeof(*item));
112
+ item->status = status;
113
+
114
+ if (ZITI_OK == status) {
115
+ item->permissions = service->perm_flags;
116
+ }
117
+
118
+ // Initiate the call into the JavaScript callback.
119
+ // The call into JavaScript will not have happened
120
+ // when this function returns, but it will be queued.
121
+ nstatus = napi_call_threadsafe_function(
122
+ addon_data->tsfn,
123
+ item,
124
+ napi_tsfn_blocking);
125
+ if (nstatus != napi_ok) {
126
+ ZITI_NODEJS_LOG(ERROR, "Unable to napi_call_threadsafe_function");
127
+ }
128
+ }
129
+
130
+ /**
131
+ *
132
+ */
133
+ napi_value _ziti_service_available(napi_env env, const napi_callback_info info) {
134
+ napi_status status;
135
+ size_t argc = 2;
136
+ napi_value args[2];
137
+ napi_value jsRetval;
138
+
139
+ status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
140
+ if (status != napi_ok) {
141
+ napi_throw_error(env, NULL, "Failed to parse arguments");
142
+ }
143
+
144
+ if (argc < 2) {
145
+ napi_throw_error(env, "EINVAL", "Too few arguments");
146
+ return NULL;
147
+ }
148
+
149
+ // Obtain service name
150
+ size_t result;
151
+ char* ServiceName = malloc(256); // TODO: make this smarter
152
+ status = napi_get_value_string_utf8(env, args[0], ServiceName, 100, &result);
153
+ if (status != napi_ok) {
154
+ napi_throw_error(env, NULL, "Service Name not provided");
155
+ }
156
+
157
+ // Obtain ptr to JS 'service_available' callback function
158
+ napi_value js_write_cb = args[1];
159
+ napi_value work_name;
160
+ AddonData* addon_data = malloc(sizeof(AddonData));
161
+
162
+ // Create a string to describe this asynchronous operation.
163
+ status = napi_create_string_utf8(
164
+ env,
165
+ "N-API on_service_available",
166
+ NAPI_AUTO_LENGTH,
167
+ &work_name);
168
+ if (status != napi_ok) {
169
+ napi_throw_error(env, NULL, "Unable to napi_create_string_utf8");
170
+ }
171
+
172
+ // Convert the callback retrieved from JavaScript into a thread-safe function (tsfn)
173
+ // which we can call from a worker thread.
174
+ status = napi_create_threadsafe_function(
175
+ env,
176
+ js_write_cb,
177
+ NULL,
178
+ work_name,
179
+ 0,
180
+ 1,
181
+ NULL,
182
+ NULL,
183
+ NULL,
184
+ CallJs_on_service_available,
185
+ &(addon_data->tsfn));
186
+ if (status != napi_ok) {
187
+ napi_throw_error(env, NULL, "Unable to napi_call_threadsafe_function");
188
+ }
189
+
190
+ // Now, call the C-SDK to see if the service name is present
191
+ ziti_service_available(ztx, ServiceName, on_service_available, addon_data);
192
+
193
+ status = napi_create_int32(env, 0 /* always succeed here, it is the cb that tells the real tale */, &jsRetval);
194
+ if (status != napi_ok) {
195
+ napi_throw_error(env, NULL, "Unable to create return value");
196
+ }
197
+
198
+ free(ServiceName);
199
+
200
+ return jsRetval;
201
+ }
202
+
203
+
204
+ /**
205
+ *
206
+ */
207
+ void expose_ziti_service_available(napi_env env, napi_value exports) {
208
+ napi_status status;
209
+ napi_value fn;
210
+
211
+ status = napi_create_function(env, NULL, 0, _ziti_service_available, NULL, &fn);
212
+ if (status != napi_ok) {
213
+ napi_throw_error(env, NULL, "Unable to wrap native function '_ziti_service_available");
214
+ }
215
+
216
+ status = napi_set_named_property(env, exports, "ziti_service_available", fn);
217
+ if (status != napi_ok) {
218
+ napi_throw_error(env, NULL, "Unable to populate exports for 'ziti_service_available");
219
+ }
220
+
221
+ }
222
+
@@ -0,0 +1,47 @@
1
+ /*
2
+ Copyright 2019-2020 Netfoundry, Inc.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ https://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ #include "ziti-nodejs.h"
18
+
19
+
20
+ /**
21
+ *
22
+ */
23
+ napi_value _ziti_shutdown(napi_env env, const napi_callback_info info) {
24
+
25
+ ZITI_NODEJS_LOG(DEBUG, "ztx: %p", ztx);
26
+
27
+ ziti_shutdown(ztx);
28
+
29
+ return NULL;
30
+ }
31
+
32
+
33
+ void expose_ziti_shutdown(napi_env env, napi_value exports) {
34
+ napi_status status;
35
+ napi_value fn;
36
+
37
+ status = napi_create_function(env, NULL, 0, _ziti_shutdown, NULL, &fn);
38
+ if (status != napi_ok) {
39
+ napi_throw_error(env, NULL, "Unable to wrap native function '_ziti_shutdown");
40
+ }
41
+
42
+ status = napi_set_named_property(env, exports, "ziti_shutdown", fn);
43
+ if (status != napi_ok) {
44
+ napi_throw_error(env, NULL, "Unable to populate exports for 'ziti_shutdown");
45
+ }
46
+
47
+ }