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.
- package/README.md +779 -96
- package/binding.gyp +30 -0
- package/build/binding.sln +6 -0
- package/build/eventlogasync.vcxproj +148 -0
- package/build/eventlogasync.vcxproj.filters +43 -0
- package/doc/ASYNC_STATUS.md +104 -0
- package/doc/COHERENCIA_APIS.md +154 -0
- package/doc/CONTRIBUTING.md +64 -0
- package/doc/CORRECCION_NAPI.md +74 -0
- package/doc/CPU_EFFICIENCY_GUIDE.md +199 -0
- package/doc/NAPI-SETUP.md +294 -0
- package/doc/PREBUILDS.md +180 -0
- package/doc/README_eventlogasync.md +134 -0
- package/doc/RESUMABLE_READER.md +250 -0
- package/doc/USAGE.md +527 -0
- package/index.js +202 -25
- package/native/eventlogasync.cc +687 -0
- package/package.json +37 -9
- package/prebuilds/metadata.json +24 -0
- package/prebuilds/win32-x64/eventlog.node +0 -0
- package/prebuilds/win32-x64/eventlogasync.node +0 -0
- package/prebuilds/win32-x64/meta.json +20 -0
- package/prebuilds/win32-x64/nwinread.node +0 -0
- package/scripts/generate-prebuilds-advanced.js +186 -0
- package/scripts/generate-prebuilds.js +86 -0
- package/scripts/prebuilds/win32-x64/meta.json +20 -0
- package/scripts/verify-setup.js +2 -1
- package/test/README.md +105 -0
- package/test/example_async.js +107 -0
- package/test/example_sync.js +76 -0
- package/test/test_beginning_mode.js +40 -0
- package/test/test_build_version.js +46 -0
- package/test/test_callback_simple.js +46 -0
- package/test/test_modes_comparison.js +74 -0
- package/test/test_watermark_realistic.js +75 -0
- package/test/test_watermark_specific.js +88 -0
- package/test/test_wrapper_vs_native.js +58 -0
- package/test/verify_sync_events.js +19 -0
- package/CHANGES.md +0 -120
- package/NAPI-SETUP.md +0 -142
- 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)
|