nwinread 1.1.0 → 1.2.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 (41) hide show
  1. package/README.md +779 -96
  2. package/binding.gyp +30 -0
  3. package/build/binding.sln +6 -0
  4. package/build/eventlogasync.vcxproj +148 -0
  5. package/build/eventlogasync.vcxproj.filters +43 -0
  6. package/doc/ASYNC_STATUS.md +104 -0
  7. package/doc/COHERENCIA_APIS.md +154 -0
  8. package/doc/CONTRIBUTING.md +64 -0
  9. package/doc/CORRECCION_NAPI.md +74 -0
  10. package/doc/CPU_EFFICIENCY_GUIDE.md +199 -0
  11. package/doc/NAPI-SETUP.md +294 -0
  12. package/doc/PREBUILDS.md +180 -0
  13. package/doc/README_eventlogasync.md +134 -0
  14. package/doc/RESUMABLE_READER.md +250 -0
  15. package/doc/USAGE.md +527 -0
  16. package/index.js +202 -25
  17. package/native/eventlogasync.cc +687 -0
  18. package/package.json +37 -9
  19. package/prebuilds/metadata.json +24 -0
  20. package/prebuilds/win32-x64/eventlog.node +0 -0
  21. package/prebuilds/win32-x64/eventlogasync.node +0 -0
  22. package/prebuilds/win32-x64/meta.json +20 -0
  23. package/prebuilds/win32-x64/nwinread.node +0 -0
  24. package/scripts/generate-prebuilds-advanced.js +186 -0
  25. package/scripts/generate-prebuilds.js +86 -0
  26. package/scripts/prebuilds/win32-x64/meta.json +20 -0
  27. package/scripts/verify-setup.js +2 -1
  28. package/test/README.md +105 -0
  29. package/test/example_async.js +107 -0
  30. package/test/example_sync.js +76 -0
  31. package/test/test_beginning_mode.js +40 -0
  32. package/test/test_build_version.js +46 -0
  33. package/test/test_callback_simple.js +46 -0
  34. package/test/test_modes_comparison.js +74 -0
  35. package/test/test_watermark_realistic.js +75 -0
  36. package/test/test_watermark_specific.js +88 -0
  37. package/test/test_wrapper_vs_native.js +58 -0
  38. package/test/verify_sync_events.js +19 -0
  39. package/CHANGES.md +0 -120
  40. package/NAPI-SETUP.md +0 -142
  41. package/test.js +0 -34
@@ -0,0 +1,687 @@
1
+ #include <node_api.h>
2
+ #include <windows.h>
3
+ #include <winevt.h>
4
+ #include <vector>
5
+ #include <string>
6
+ #include <cassert>
7
+ #include <thread>
8
+ #include <mutex>
9
+ #include <memory>
10
+ #include <atomic>
11
+ #include <functional>
12
+ #include <fstream>
13
+ #include <iostream>
14
+ #include <sstream>
15
+
16
+ #pragma comment(lib, "wevtapi.lib")
17
+
18
+ enum ReadMode {
19
+ FROM_BEGINNING = 0,
20
+ FROM_END = 1,
21
+ FROM_WATERMARK = 2
22
+ };
23
+
24
+ // Estructura para mantener el estado de la suscripción
25
+ struct SubscriptionContext {
26
+ napi_env env;
27
+ napi_ref callback_ref;
28
+ napi_ref error_callback_ref;
29
+ napi_threadsafe_function tsfn;
30
+ napi_threadsafe_function error_tsfn;
31
+ EVT_HANDLE subscription;
32
+ std::atomic<bool> should_stop;
33
+ uint64_t last_record_id;
34
+ std::vector<int32_t> event_ids;
35
+
36
+ SubscriptionContext() : env(nullptr), callback_ref(nullptr), error_callback_ref(nullptr),
37
+ tsfn(nullptr), error_tsfn(nullptr), subscription(nullptr),
38
+ should_stop(false), last_record_id(0) {}
39
+ };
40
+
41
+ // Array global para mantener las suscripciones activas
42
+ static std::vector<std::shared_ptr<SubscriptionContext>> active_subscriptions;
43
+ static std::mutex subscriptions_mutex;
44
+
45
+ // Funciones de conversión UTF-8 <-> Wide
46
+ std::wstring Utf8ToWide(const std::string& str) {
47
+ if (str.empty()) return std::wstring();
48
+
49
+ int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, NULL, 0);
50
+ if (size == 0) return std::wstring();
51
+
52
+ std::wstring w(size - 1, 0); // size -1 para excluir el terminador nulo
53
+ MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &w[0], size);
54
+ return w;
55
+ }
56
+
57
+ std::string WideToUtf8(const std::wstring& wstr) {
58
+ if (wstr.empty()) return std::string();
59
+
60
+ int size = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, NULL, 0, NULL, NULL);
61
+ if (size == 0) return std::string();
62
+
63
+ std::string str(size - 1, 0); // size-1 para quitar el null terminator
64
+ WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &str[0], size, NULL, NULL);
65
+ return str;
66
+ }
67
+
68
+ // Estructura para pasar datos al callback JavaScript
69
+ struct CallbackData {
70
+ std::string xml;
71
+ uint64_t record_id;
72
+ bool is_error;
73
+ std::string error_msg;
74
+ DWORD error_code;
75
+
76
+ CallbackData() : record_id(0), is_error(false), error_code(0) {}
77
+ };
78
+
79
+ // Callback para threadsafe function (eventos)
80
+ void CallJS(napi_env env, napi_value js_callback, void* context, void* data) {
81
+ if (data == nullptr || js_callback == nullptr) return;
82
+
83
+ std::unique_ptr<CallbackData> callback_data(static_cast<CallbackData*>(data));
84
+
85
+ napi_status status;
86
+ napi_value event_obj, xml_val, record_id_val;
87
+
88
+ // Crear objeto de evento
89
+ status = napi_create_object(env, &event_obj);
90
+ if (status != napi_ok) return;
91
+
92
+ // Agregar XML
93
+ status = napi_create_string_utf8(env, callback_data->xml.c_str(),
94
+ callback_data->xml.length(), &xml_val);
95
+ if (status == napi_ok) {
96
+ napi_set_named_property(env, event_obj, "xml", xml_val);
97
+ }
98
+
99
+ // Agregar record ID
100
+ status = napi_create_double(env, (double)callback_data->record_id, &record_id_val);
101
+ if (status == napi_ok) {
102
+ napi_set_named_property(env, event_obj, "recordId", record_id_val);
103
+ }
104
+
105
+ // Llamar al callback JavaScript con manejo de excepciones
106
+ napi_value global, result;
107
+ status = napi_get_global(env, &global);
108
+ if (status == napi_ok) {
109
+ status = napi_call_function(env, global, js_callback, 1, &event_obj, &result);
110
+ if (status != napi_ok) {
111
+ // Limpiar cualquier excepción pendiente
112
+ bool pending;
113
+ if (napi_is_exception_pending(env, &pending) == napi_ok && pending) {
114
+ napi_value exception;
115
+ napi_get_and_clear_last_exception(env, &exception);
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ // Callback para errores
122
+ void CallJSError(napi_env env, napi_value js_callback, void* context, void* data) {
123
+ if (data == nullptr || js_callback == nullptr) return;
124
+
125
+ std::unique_ptr<CallbackData> callback_data(static_cast<CallbackData*>(data));
126
+
127
+ napi_status status;
128
+ napi_value error_obj, msg_val, code_val;
129
+
130
+ // Crear objeto de error
131
+ status = napi_create_object(env, &error_obj);
132
+ if (status != napi_ok) return;
133
+
134
+ // Agregar mensaje
135
+ status = napi_create_string_utf8(env, callback_data->error_msg.c_str(),
136
+ callback_data->error_msg.length(), &msg_val);
137
+ if (status == napi_ok) {
138
+ napi_set_named_property(env, error_obj, "message", msg_val);
139
+ }
140
+
141
+ // Agregar código de error
142
+ status = napi_create_int32(env, callback_data->error_code, &code_val);
143
+ if (status == napi_ok) {
144
+ napi_set_named_property(env, error_obj, "code", code_val);
145
+ }
146
+
147
+ // Llamar al callback JavaScript de error con manejo de excepciones
148
+ napi_value global, result;
149
+ status = napi_get_global(env, &global);
150
+ if (status == napi_ok) {
151
+ status = napi_call_function(env, global, js_callback, 1, &error_obj, &result);
152
+ if (status != napi_ok) {
153
+ // Limpiar cualquier excepción pendiente
154
+ bool pending;
155
+ if (napi_is_exception_pending(env, &pending) == napi_ok && pending) {
156
+ napi_value exception;
157
+ napi_get_and_clear_last_exception(env, &exception);
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ // Callback de Windows Event Log (se ejecuta cuando llegan eventos)
164
+ DWORD WINAPI SubscriptionCallback(EVT_SUBSCRIBE_NOTIFY_ACTION action,
165
+ PVOID pContext,
166
+ EVT_HANDLE hEvent) {
167
+ if (action != EvtSubscribeActionDeliver) {
168
+ return ERROR_SUCCESS;
169
+ }
170
+
171
+ SubscriptionContext* ctx = static_cast<SubscriptionContext*>(pContext);
172
+ if (ctx == nullptr || ctx->should_stop.load()) {
173
+ return ERROR_SUCCESS;
174
+ }
175
+
176
+ DWORD bufferUsed = 0;
177
+ DWORD propCount = 0;
178
+
179
+ // Primera llamada para obtener el tamaño del buffer
180
+ EvtRender(NULL, hEvent, EvtRenderEventXml, 0, NULL, &bufferUsed, &propCount);
181
+
182
+ if (bufferUsed == 0) {
183
+ return ERROR_SUCCESS;
184
+ }
185
+
186
+ std::vector<wchar_t> buffer(bufferUsed / sizeof(wchar_t));
187
+
188
+ // Segunda llamada para obtener los datos
189
+ if (EvtRender(NULL, hEvent, EvtRenderEventXml, bufferUsed,
190
+ buffer.data(), &bufferUsed, &propCount)) {
191
+
192
+
193
+ std::wstring xml(buffer.data());
194
+ std::string xmlUtf8 = WideToUtf8(xml);
195
+
196
+ // Extraer EventRecordID del XML
197
+ uint64_t recordId = 0;
198
+ size_t pos = xml.find(L"<EventRecordID>");
199
+ if (pos != std::wstring::npos) {
200
+ size_t end = xml.find(L"</EventRecordID>", pos);
201
+ if (end != std::wstring::npos) {
202
+ std::wstring recordIdStr = xml.substr(pos + 15, end - (pos + 15));
203
+ try {
204
+ recordId = std::stoull(recordIdStr);
205
+ ctx->last_record_id = recordId;
206
+ } catch (...) {
207
+ // Mantener valor anterior si hay error
208
+ }
209
+ }
210
+ }
211
+ // Crear datos para el callback
212
+ CallbackData* callback_data = new CallbackData();
213
+ callback_data->xml = xmlUtf8;
214
+ callback_data->record_id = recordId;
215
+
216
+ // Enviar evento al JavaScript callback
217
+ napi_status status = napi_call_threadsafe_function(ctx->tsfn, callback_data, napi_tsfn_blocking);
218
+ if (status != napi_ok) {
219
+ delete callback_data;
220
+ }
221
+ } else {
222
+ // Error al renderizar el evento
223
+ DWORD errorCode = GetLastError();
224
+
225
+ CallbackData* error_data = new CallbackData();
226
+ error_data->is_error = true;
227
+ error_data->error_msg = "Failed to render event";
228
+ error_data->error_code = errorCode;
229
+
230
+ napi_status status = napi_call_threadsafe_function(ctx->error_tsfn, error_data, napi_tsfn_blocking);
231
+ if (status != napi_ok) {
232
+ delete error_data;
233
+ }
234
+ }
235
+
236
+ return ERROR_SUCCESS;
237
+ }
238
+
239
+ // Función para crear el query XPath
240
+ std::wstring BuildXPathQuery(const std::vector<int32_t>& eventIds, uint64_t watermark) {
241
+ std::wstring query;
242
+
243
+ // Construir filtro de Event IDs si se proporcionaron
244
+ std::wstring eventIdFilter;
245
+ if (!eventIds.empty()) {
246
+ eventIdFilter = L"System/EventID=";
247
+ for (size_t i = 0; i < eventIds.size(); i++) {
248
+ if (i > 0) {
249
+ eventIdFilter += L" or System/EventID=";
250
+ }
251
+ eventIdFilter += std::to_wstring(eventIds[i]);
252
+ }
253
+ }
254
+ // Construir filtro de watermark si es necesario
255
+ std::wstring watermarkFilter;
256
+ if (watermark > 0) {
257
+ watermarkFilter = L"System/EventRecordID > " + std::to_wstring(watermark);
258
+ }
259
+
260
+ // Combinar filtros
261
+ if (!eventIdFilter.empty() || !watermarkFilter.empty()) {
262
+ query = L"*[";
263
+
264
+ if (!eventIdFilter.empty() && !watermarkFilter.empty()) {
265
+ query += L"(" + eventIdFilter + L") and (" + watermarkFilter + L")";
266
+ } else if (!eventIdFilter.empty()) {
267
+ query += eventIdFilter;
268
+ } else {
269
+ query += watermarkFilter;
270
+ }
271
+
272
+ query += L"]";
273
+ }
274
+
275
+ return query;
276
+ }
277
+
278
+ // Función JavaScript para iniciar suscripción asíncrona
279
+ napi_value SubscribeEventLog(napi_env env, napi_callback_info info) {
280
+
281
+ napi_status status;
282
+ size_t argc = 6;
283
+ napi_value args[6];
284
+
285
+ status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
286
+ assert(status == napi_ok);
287
+
288
+
289
+ if (argc < 4) {
290
+ napi_throw_type_error(env, nullptr, "Expected 4-6 arguments: (channel, watermark, eventCallback, errorCallback, eventIds?, mode?)");
291
+ return nullptr;
292
+ }
293
+
294
+ // Extraer channel
295
+ size_t channel_len;
296
+ status = napi_get_value_string_utf8(env, args[0], nullptr, 0, &channel_len);
297
+ assert(status == napi_ok);
298
+
299
+ std::string channel(channel_len, '\0');
300
+ status = napi_get_value_string_utf8(env, args[0], &channel[0], channel_len + 1, &channel_len);
301
+ assert(status == napi_ok);
302
+
303
+
304
+ // Extraer watermark
305
+ int64_t watermark;
306
+ status = napi_get_value_int64(env, args[1], &watermark);
307
+ assert(status == napi_ok);
308
+
309
+
310
+ // Los callbacks son args[2] y args[3]
311
+ napi_value event_callback = args[2];
312
+ napi_value error_callback = args[3];
313
+
314
+ // Validar que son funciones
315
+ napi_valuetype callback_type, error_callback_type;
316
+ status = napi_typeof(env, event_callback, &callback_type);
317
+ assert(status == napi_ok);
318
+ status = napi_typeof(env, error_callback, &error_callback_type);
319
+ assert(status == napi_ok);
320
+
321
+ if (callback_type != napi_function || error_callback_type != napi_function) {
322
+ napi_throw_type_error(env, nullptr, "Event callback and error callback must be functions");
323
+ return nullptr;
324
+ }
325
+
326
+ // Procesar eventIds opcionales
327
+ std::vector<int32_t> eventIds;
328
+ if (argc >= 5) {
329
+ napi_valuetype valueType;
330
+ status = napi_typeof(env, args[4], &valueType);
331
+
332
+ if (valueType != napi_null && valueType != napi_undefined) {
333
+ bool isArray;
334
+ status = napi_is_array(env, args[4], &isArray);
335
+ assert(status == napi_ok);
336
+
337
+ if (isArray) {
338
+ uint32_t arrayLength;
339
+ status = napi_get_array_length(env, args[4], &arrayLength);
340
+ assert(status == napi_ok);
341
+
342
+ for (uint32_t i = 0; i < arrayLength; i++) {
343
+ napi_value element;
344
+ status = napi_get_element(env, args[4], i, &element);
345
+ assert(status == napi_ok);
346
+
347
+ int32_t eventId;
348
+ status = napi_get_value_int32(env, element, &eventId);
349
+ if (status == napi_ok && eventId > 0) {
350
+ eventIds.push_back(eventId);
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ // Procesar modo opcional (argumento 6)
358
+ int32_t mode = FROM_END; // Default: solo eventos futuros
359
+ if (argc >= 6) {
360
+ napi_valuetype valueType;
361
+ status = napi_typeof(env, args[5], &valueType);
362
+
363
+ if (valueType == napi_number) {
364
+ status = napi_get_value_int32(env, args[5], &mode);
365
+ if (status != napi_ok) {
366
+ mode = FROM_END; // Fallback
367
+ }
368
+ }
369
+ }
370
+
371
+
372
+ // Crear contexto de suscripción
373
+ auto ctx = std::make_shared<SubscriptionContext>();
374
+ ctx->env = env;
375
+ ctx->last_record_id = watermark > 0 ? watermark : 0;
376
+ ctx->event_ids = eventIds;
377
+
378
+ // Crear referencias persistentes a los callbacks
379
+ status = napi_create_reference(env, event_callback, 1, &ctx->callback_ref);
380
+ assert(status == napi_ok);
381
+ status = napi_create_reference(env, error_callback, 1, &ctx->error_callback_ref);
382
+ assert(status == napi_ok);
383
+
384
+ // Crear threadsafe functions
385
+ napi_value tsfn_name, error_tsfn_name;
386
+ status = napi_create_string_utf8(env, "EventLogSubscription", NAPI_AUTO_LENGTH, &tsfn_name);
387
+ assert(status == napi_ok);
388
+ status = napi_create_string_utf8(env, "EventLogSubscriptionError", NAPI_AUTO_LENGTH, &error_tsfn_name);
389
+ assert(status == napi_ok);
390
+
391
+ status = napi_create_threadsafe_function(
392
+ env, event_callback, nullptr, tsfn_name, 0, 1, nullptr, nullptr,
393
+ ctx.get(), CallJS, &ctx->tsfn);
394
+ assert(status == napi_ok);
395
+
396
+ status = napi_create_threadsafe_function(
397
+ env, error_callback, nullptr, error_tsfn_name, 0, 1, nullptr, nullptr,
398
+ ctx.get(), CallJSError, &ctx->error_tsfn);
399
+ assert(status == napi_ok);
400
+
401
+ // Construir query XPath y determinar flags de suscripción basado en el modo
402
+ std::wstring query;
403
+ DWORD subscribeFlags = EvtSubscribeToFutureEvents; // Default
404
+ EVT_HANDLE bookmark = NULL;
405
+
406
+ // DEBUG: Imprimir información inicial
407
+
408
+ switch (mode) {
409
+ case FROM_BEGINNING:
410
+ // Para BEGINNING: usar oldest record, sin filtro de watermark
411
+ subscribeFlags = EvtSubscribeStartAtOldestRecord;
412
+ query = BuildXPathQuery(eventIds, 0); // Sin filtro de watermark
413
+ break;
414
+
415
+ case FROM_END:
416
+ // Para END: eventos futuros, sin filtro de watermark
417
+ subscribeFlags = EvtSubscribeToFutureEvents;
418
+ query = BuildXPathQuery(eventIds, 0); // Sin filtro de watermark
419
+ break;
420
+
421
+ case FROM_WATERMARK:
422
+ // Para WATERMARK: crear bookmark XML y usar EvtSubscribeStartAfterBookmark
423
+ subscribeFlags = EvtSubscribeStartAfterBookmark;
424
+ query = BuildXPathQuery(eventIds, 0); // Sin filtro de watermark en query
425
+
426
+ // Crear bookmark directamente desde XML (método más eficiente)
427
+ if (watermark > 0) {
428
+ std::wstring channelW = Utf8ToWide(channel);
429
+ std::wstring watermarkStr = std::to_wstring(watermark);
430
+
431
+ // NUEVO ENFOQUE: Buscar eventos >= watermark para crear bookmark válido
432
+
433
+ // Crear una query que busque eventos mayor o igual al watermark
434
+ std::wstring queryStr = Utf8ToWide("*[System/EventRecordID >= " + std::to_string(watermark) + "]");
435
+ EVT_HANDLE queryHandle = EvtQuery(NULL, channelW.c_str(), queryStr.c_str(),
436
+ EvtQueryChannelPath | EvtQueryForwardDirection);
437
+
438
+ if (queryHandle) {
439
+ // Intentar leer al menos un evento para posicionar la query
440
+ EVT_HANDLE events[1];
441
+ DWORD returned;
442
+ if (EvtNext(queryHandle, 1, events, INFINITE, 0, &returned) && returned > 0) {
443
+ // Crear bookmark desde la posición actual de la query
444
+ bookmark = EvtCreateBookmark(NULL);
445
+ if (bookmark) {
446
+ // Actualizar bookmark con la posición actual
447
+ if (EvtUpdateBookmark(bookmark, events[0])) {
448
+ // Bookmark creado correctamente
449
+ } else {
450
+ EvtClose(bookmark);
451
+ bookmark = NULL;
452
+ }
453
+ }
454
+ EvtClose(events[0]);
455
+ } else {
456
+ // No hay eventos >= watermark, usar eventos futuros
457
+ }
458
+ EvtClose(queryHandle);
459
+ } else {
460
+ // Error en query, usar eventos futuros como fallback
461
+ }
462
+
463
+ if (!bookmark) {
464
+ // Si no se pudo crear el bookmark, usar eventos futuros
465
+ subscribeFlags = EvtSubscribeToFutureEvents;
466
+ }
467
+ } else {
468
+ subscribeFlags = EvtSubscribeToFutureEvents;
469
+ }
470
+ break;
471
+
472
+ default:
473
+ // Fallback: eventos futuros sin filtro
474
+ subscribeFlags = EvtSubscribeToFutureEvents;
475
+ query = BuildXPathQuery(eventIds, 0);
476
+ break;
477
+ }
478
+
479
+ if (!query.empty()) {
480
+ } else {
481
+ }
482
+
483
+ // Crear suscripción
484
+ ctx->subscription = EvtSubscribe(
485
+ NULL,
486
+ NULL,
487
+ Utf8ToWide(channel).c_str(),
488
+ query.empty() ? NULL : query.c_str(),
489
+ bookmark, // Usar bookmark en lugar de NULL
490
+ ctx.get(),
491
+ (EVT_SUBSCRIBE_CALLBACK)SubscriptionCallback,
492
+ subscribeFlags
493
+ );
494
+
495
+ if (ctx->subscription) {
496
+ // Suscripción creada exitosamente - agregar a lista de suscripciones activas
497
+ {
498
+ std::lock_guard<std::mutex> lock(subscriptions_mutex);
499
+ active_subscriptions.push_back(ctx);
500
+ }
501
+ } else {
502
+ DWORD error = GetLastError();
503
+ // Limpiar recursos antes de fallar
504
+ napi_delete_reference(env, ctx->callback_ref);
505
+ napi_delete_reference(env, ctx->error_callback_ref);
506
+ napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
507
+ napi_release_threadsafe_function(ctx->error_tsfn, napi_tsfn_abort);
508
+
509
+ std::string errorMsg = "EvtSubscribe failed with error code: " + std::to_string(error);
510
+ napi_throw_error(env, nullptr, errorMsg.c_str());
511
+ return nullptr;
512
+ }
513
+
514
+ // Limpiar bookmark si se creó
515
+ if (bookmark) {
516
+ EvtClose(bookmark);
517
+ }
518
+
519
+ if (ctx->subscription) {
520
+ // Suscripción creada exitosamente - agregar a lista de suscripciones activas
521
+ size_t subscription_id;
522
+ {
523
+ std::lock_guard<std::mutex> lock(subscriptions_mutex);
524
+ active_subscriptions.push_back(ctx);
525
+ subscription_id = active_subscriptions.size() - 1;
526
+ }
527
+
528
+ // Retornar ID de la suscripción
529
+ napi_value subscription_id_val;
530
+ status = napi_create_double(env, (double)subscription_id, &subscription_id_val);
531
+ assert(status == napi_ok);
532
+
533
+ return subscription_id_val;
534
+ } else {
535
+ DWORD error = GetLastError();
536
+ // Limpiar recursos antes de fallar
537
+ napi_delete_reference(env, ctx->callback_ref);
538
+ napi_delete_reference(env, ctx->error_callback_ref);
539
+ napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
540
+ napi_release_threadsafe_function(ctx->error_tsfn, napi_tsfn_abort);
541
+
542
+ std::string errorMsg = "EvtSubscribe failed with error code: " + std::to_string(error);
543
+ napi_throw_error(env, nullptr, errorMsg.c_str());
544
+ return nullptr;
545
+ }
546
+ }
547
+
548
+ // Función JavaScript para detener suscripción
549
+ napi_value UnsubscribeEventLog(napi_env env, napi_callback_info info) {
550
+ napi_status status;
551
+ size_t argc = 1;
552
+ napi_value args[1];
553
+
554
+ status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
555
+ assert(status == napi_ok);
556
+
557
+ if (argc < 1) {
558
+ napi_throw_type_error(env, nullptr, "Expected 1 argument: subscription_id");
559
+ return nullptr;
560
+ }
561
+
562
+ double subscription_id_double;
563
+ status = napi_get_value_double(env, args[0], &subscription_id_double);
564
+ assert(status == napi_ok);
565
+
566
+ size_t subscription_id = (size_t)subscription_id_double;
567
+
568
+ std::lock_guard<std::mutex> lock(subscriptions_mutex);
569
+
570
+ if (subscription_id >= active_subscriptions.size() ||
571
+ !active_subscriptions[subscription_id]) {
572
+ napi_throw_type_error(env, nullptr, "Invalid subscription ID");
573
+ return nullptr;
574
+ }
575
+
576
+ auto ctx = active_subscriptions[subscription_id];
577
+
578
+ // Marcar para detener
579
+ ctx->should_stop.store(true);
580
+
581
+ // Cerrar suscripción de Windows
582
+ if (ctx->subscription) {
583
+ EvtClose(ctx->subscription);
584
+ ctx->subscription = nullptr;
585
+ }
586
+
587
+ // Limpiar recursos de N-API
588
+ if (ctx->callback_ref) {
589
+ napi_delete_reference(env, ctx->callback_ref);
590
+ ctx->callback_ref = nullptr;
591
+ }
592
+
593
+ if (ctx->error_callback_ref) {
594
+ napi_delete_reference(env, ctx->error_callback_ref);
595
+ ctx->error_callback_ref = nullptr;
596
+ }
597
+
598
+ // Liberar threadsafe functions
599
+ if (ctx->tsfn) {
600
+ napi_release_threadsafe_function(ctx->tsfn, napi_tsfn_abort);
601
+ ctx->tsfn = nullptr;
602
+ }
603
+
604
+ if (ctx->error_tsfn) {
605
+ napi_release_threadsafe_function(ctx->error_tsfn, napi_tsfn_abort);
606
+ ctx->error_tsfn = nullptr;
607
+ }
608
+
609
+ // Remover de la lista
610
+ active_subscriptions[subscription_id].reset();
611
+
612
+ // Retornar éxito
613
+ napi_value result;
614
+ status = napi_get_boolean(env, true, &result);
615
+ assert(status == napi_ok);
616
+
617
+ return result;
618
+ }
619
+
620
+ // Función para obtener el último record ID de una suscripción
621
+ napi_value GetLastRecordId(napi_env env, napi_callback_info info) {
622
+ napi_status status;
623
+ size_t argc = 1;
624
+ napi_value args[1];
625
+
626
+ status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
627
+ assert(status == napi_ok);
628
+
629
+ if (argc < 1) {
630
+ napi_throw_type_error(env, nullptr, "Expected 1 argument: subscription_id");
631
+ return nullptr;
632
+ }
633
+
634
+ double subscription_id_double;
635
+ status = napi_get_value_double(env, args[0], &subscription_id_double);
636
+ assert(status == napi_ok);
637
+
638
+ size_t subscription_id = (size_t)subscription_id_double;
639
+
640
+ std::lock_guard<std::mutex> lock(subscriptions_mutex);
641
+
642
+ if (subscription_id >= active_subscriptions.size() ||
643
+ !active_subscriptions[subscription_id]) {
644
+ napi_throw_type_error(env, nullptr, "Invalid subscription ID");
645
+ return nullptr;
646
+ }
647
+
648
+ auto ctx = active_subscriptions[subscription_id];
649
+
650
+ napi_value result;
651
+ status = napi_create_double(env, (double)ctx->last_record_id, &result);
652
+ assert(status == napi_ok);
653
+
654
+ return result;
655
+ }
656
+
657
+ // Inicialización del módulo
658
+ napi_value Init(napi_env env, napi_value exports) {
659
+ napi_status status;
660
+ napi_value subscribe_fn, unsubscribe_fn, get_last_record_fn;
661
+
662
+ // Crear función de suscripción
663
+ status = napi_create_function(env, nullptr, 0, SubscribeEventLog, nullptr, &subscribe_fn);
664
+ assert(status == napi_ok);
665
+
666
+ // Crear función de cancelar suscripción
667
+ status = napi_create_function(env, nullptr, 0, UnsubscribeEventLog, nullptr, &unsubscribe_fn);
668
+ assert(status == napi_ok);
669
+
670
+ // Crear función para obtener último record ID
671
+ status = napi_create_function(env, nullptr, 0, GetLastRecordId, nullptr, &get_last_record_fn);
672
+ assert(status == napi_ok);
673
+
674
+ // Exportar funciones
675
+ status = napi_set_named_property(env, exports, "subscribe", subscribe_fn);
676
+ assert(status == napi_ok);
677
+
678
+ status = napi_set_named_property(env, exports, "unsubscribe", unsubscribe_fn);
679
+ assert(status == napi_ok);
680
+
681
+ status = napi_set_named_property(env, exports, "getLastRecordId", get_last_record_fn);
682
+ assert(status == napi_ok);
683
+
684
+ return exports;
685
+ }
686
+
687
+ NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)