zklib-ts 1.0.0-development

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.
@@ -0,0 +1,2715 @@
1
+ 'use strict';
2
+
3
+ var net = require('net');
4
+ var fs = require('fs');
5
+ var dgram = require('node:dgram');
6
+
7
+ function _interopNamespaceDefault(e) {
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var dgram__namespace = /*#__PURE__*/_interopNamespaceDefault(dgram);
25
+
26
+ const COMMANDS = {
27
+ CMD_CONNECT: 1000,
28
+ CMD_EXIT: 1001,
29
+ CMD_ENABLEDEVICE: 1002,
30
+ CMD_DISABLEDEVICE: 1003,
31
+ CMD_RESTART: 1004,
32
+ CMD_POWEROFF: 1005,
33
+ CMD_SLEEP: 1006,
34
+ CMD_RESUME: 1007,
35
+ CMD_CAPTUREFINGER: 1009,
36
+ CMD_TEST_TEMP: 1011,
37
+ CMD_CAPTUREIMAGE: 1012,
38
+ CMD_REFRESHDATA: 1013,
39
+ CMD_REFRESHOPTION: 1014,
40
+ CMD_TESTVOICE: 1017,
41
+ CMD_GET_VERSION: 1100,
42
+ CMD_CHANGE_SPEED: 1101,
43
+ CMD_AUTH: 1102,
44
+ CMD_PREPARE_DATA: 1500,
45
+ CMD_DATA: 1501,
46
+ CMD_FREE_DATA: 1502,
47
+ CMD_DATA_WRRQ: 1503,
48
+ CMD_DATA_RDY: 1504,
49
+ CMD_DB_RRQ: 7,
50
+ CMD_USER_WRQ: 8,
51
+ CMD_USERTEMP_RRQ: 9,
52
+ CMD_USERTEMP_WRQ: 10,
53
+ CMD_OPTIONS_RRQ: 11,
54
+ CMD_OPTIONS_WRQ: 12,
55
+ CMD_ATTLOG_RRQ: 13,
56
+ CMD_CLEAR_DATA: 14,
57
+ CMD_CLEAR_ATTLOG: 15,
58
+ CMD_DELETE_USER: 18,
59
+ CMD_DELETE_USERTEMP: 19,
60
+ CMD_CLEAR_ADMIN: 20,
61
+ CMD_USERGRP_RRQ: 21,
62
+ CMD_USERGRP_WRQ: 22,
63
+ CMD_USERTZ_RRQ: 23,
64
+ CMD_USERTZ_WRQ: 24,
65
+ CMD_GRPTZ_RRQ: 25,
66
+ CMD_GRPTZ_WRQ: 26,
67
+ CMD_TZ_RRQ: 27,
68
+ CMD_TZ_WRQ: 28,
69
+ CMD_ULG_RRQ: 29,
70
+ CMD_ULG_WRQ: 30,
71
+ CMD_UNLOCK: 31,
72
+ CMD_CLEAR_ACC: 32,
73
+ CMD_CLEAR_OPLOG: 33,
74
+ CMD_OPLOG_RRQ: 34,
75
+ CMD_GET_FREE_SIZES: 50,
76
+ CMD_ENABLE_CLOCK: 57,
77
+ CMD_STARTVERIFY: 60,
78
+ CMD_STARTENROLL: 61,
79
+ CMD_CANCELCAPTURE: 62,
80
+ CMD_STATE_RRQ: 64,
81
+ CMD_WRITE_LCD: 66,
82
+ CMD_CLEAR_LCD: 67,
83
+ CMD_GET_PINWIDTH: 69,
84
+ CMD_SMS_WRQ: 70,
85
+ CMD_SMS_RRQ: 71,
86
+ CMD_DELETE_SMS: 72,
87
+ CMD_UDATA_WRQ: 73,
88
+ CMD_DELETE_UDATA: 74,
89
+ CMD_DOORSTATE_RRQ: 75,
90
+ CMD_WRITE_MIFARE: 76,
91
+ CMD_EMPTY_MIFARE: 78,
92
+ CMD_VERIFY_WRQ: 79,
93
+ CMD_VERIFY_RRQ: 80,
94
+ CMD_TMP_WRITE: 87,
95
+ CMD_GET_USERTEMP: 88,
96
+ CMD_CHECKSUM_BUFFER: 119,
97
+ CMD_DEL_FPTMP: 134,
98
+ CMD_GET_TIME: 201,
99
+ CMD_SET_TIME: 202,
100
+ CMD_REG_EVENT: 500,
101
+ CMD_ACK_OK: 2000,
102
+ CMD_ACK_ERROR: 2001,
103
+ CMD_ACK_DATA: 2002,
104
+ CMD_ACK_RETRY: 2003,
105
+ CMD_ACK_REPEAT: 2004,
106
+ CMD_ACK_UNAUTH: 2005,
107
+ CMD_ACK_UNKNOWN: 65535,
108
+ CMD_ACK_ERROR_CMD: 65533,
109
+ CMD_ACK_ERROR_INIT: 65532,
110
+ CMD_ACK_ERROR_DATA: 65531,
111
+ EF_ATTLOG: 1,
112
+ EF_FINGER: 2,
113
+ EF_ENROLLUSER: 4,
114
+ EF_ENROLLFINGER: 8,
115
+ EF_BUTTON: 16,
116
+ EF_UNLOCK: 32,
117
+ EF_VERIFY: 128,
118
+ EF_FPFTR: 256,
119
+ EF_ALARM: 512
120
+ };
121
+ const USHRT_MAX = 65535;
122
+ const MAX_CHUNK = 65472;
123
+ const REQUEST_DATA = {
124
+ DISABLE_DEVICE: Buffer.from([0, 0, 0, 0]),
125
+ GET_REAL_TIME_EVENT: Buffer.from([0x01, 0x00, 0x00, 0x00]),
126
+ GET_ATTENDANCE_LOGS: Buffer.from([0x01, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
127
+ GET_USERS: Buffer.from([0x01, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
128
+ GET_TEMPLATES: Buffer.from([0x01, 0x07, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
129
+ };
130
+
131
+ /**
132
+ *
133
+ * @param {number} time
134
+ */
135
+ const decode = time => {
136
+ const second = time % 60;
137
+ time = (time - second) / 60;
138
+ const minute = time % 60;
139
+ time = (time - minute) / 60;
140
+ const hour = time % 24;
141
+ time = (time - hour) / 24;
142
+ const day = time % 31 + 1;
143
+ time = (time - (day - 1)) / 31;
144
+ const month = time % 12;
145
+ time = (time - month) / 12;
146
+ const year = time + 2000;
147
+ return new Date(year, month, day, hour, minute, second);
148
+ };
149
+ /**
150
+ *
151
+ * @param {Date} date
152
+ */
153
+ const encode = date => {
154
+ return (((date.getFullYear() % 100) * 12 * 31 + date.getMonth() * 31 + date.getDate() - 1) * (24 * 60 * 60) +
155
+ (date.getHours() * 60 + date.getMinutes()) * 60 +
156
+ date.getSeconds());
157
+ };
158
+ var timeParser = { encode, decode };
159
+
160
+ const parseCurrentTime = () => {
161
+ const currentTime = new Date();
162
+ return {
163
+ year: currentTime.getFullYear(),
164
+ month: currentTime.getMonth() + 1,
165
+ day: currentTime.getDate(),
166
+ hour: currentTime.getHours(),
167
+ second: currentTime.getSeconds()
168
+ };
169
+ };
170
+ const log = (text) => {
171
+ const currentTime = parseCurrentTime();
172
+ const fileName = `${currentTime.day}`.padStart(2, '0') +
173
+ `${currentTime.month}`.padStart(2, '0') +
174
+ `${currentTime.year}.err.log`;
175
+ const logMessage = `\n [${currentTime.hour}:${currentTime.second}] ${text}`;
176
+ fs.appendFile(fileName, logMessage, () => { });
177
+ };
178
+
179
+ /**
180
+ * Represents a User as is from ZkDevice and contain methods
181
+ * */
182
+ class User {
183
+ uid;
184
+ name;
185
+ privilege;
186
+ password;
187
+ group_id;
188
+ user_id;
189
+ card;
190
+ /**
191
+ * Creates a new User instance
192
+ * @param uid User ID
193
+ * @param name User name
194
+ * @param privilege Privilege level
195
+ * @param password User password (default: "")
196
+ * @param group_id Group ID (default: "")
197
+ * @param user_id Alternate user ID (default: "")
198
+ * @param card Card number (default: 0)
199
+ */
200
+ constructor(uid, name, privilege, password = "", group_id = "", user_id = "", card = 0) {
201
+ this.uid = uid;
202
+ this.name = name;
203
+ this.privilege = privilege;
204
+ this.password = password;
205
+ this.group_id = group_id;
206
+ this.user_id = user_id;
207
+ this.card = card;
208
+ }
209
+ ensureEncoding(string) {
210
+ try {
211
+ return decodeURIComponent(string);
212
+ }
213
+ catch (e) {
214
+ return unescape(string);
215
+ }
216
+ }
217
+ repack29() {
218
+ // Pack format: <BHB5s8sIxBhI (total 29 bytes)
219
+ const buf = Buffer.alloc(29);
220
+ let offset = 0;
221
+ buf.writeUInt8(2, offset);
222
+ offset += 1;
223
+ buf.writeUInt16LE(this.uid, offset);
224
+ offset += 2;
225
+ buf.writeUInt8(this.privilege, offset);
226
+ offset += 1;
227
+ const passwordBuf = Buffer.from(this.ensureEncoding(this.password));
228
+ passwordBuf.copy(buf, offset, 0, 5);
229
+ offset += 5;
230
+ const nameBuf = Buffer.from(this.ensureEncoding(this.name));
231
+ nameBuf.copy(buf, offset, 0, 8);
232
+ offset += 8;
233
+ buf.writeUInt32LE(this.card, offset);
234
+ offset += 4;
235
+ offset += 1; // padding byte
236
+ buf.writeUInt8(0, offset);
237
+ offset += 1;
238
+ buf.writeUInt32LE(parseInt(this.user_id) || 0, offset);
239
+ return buf;
240
+ }
241
+ repack73() {
242
+ // Pack format: <BHB8s24sIB7sx24s (total 73 bytes)
243
+ const buf = Buffer.alloc(73);
244
+ let offset = 0;
245
+ buf.writeUInt8(2, offset);
246
+ offset += 1;
247
+ buf.writeUInt16LE(this.uid, offset);
248
+ offset += 2;
249
+ buf.writeUInt8(this.privilege, offset);
250
+ offset += 1;
251
+ const passwordBuf = Buffer.from(this.ensureEncoding(this.password));
252
+ passwordBuf.copy(buf, offset, 0, 8);
253
+ offset += 8;
254
+ const nameBuf = Buffer.from(this.ensureEncoding(this.name));
255
+ nameBuf.copy(buf, offset, 0, 24);
256
+ offset += 24;
257
+ buf.writeUInt32LE(this.card, offset);
258
+ offset += 4;
259
+ buf.writeUInt8(1, offset);
260
+ offset += 1;
261
+ const groupBuf = Buffer.from(this.ensureEncoding(String(this.group_id)));
262
+ groupBuf.copy(buf, offset, 0, 7);
263
+ offset += 8;
264
+ const userIdBuf = Buffer.from(this.ensureEncoding(String(this.user_id)));
265
+ userIdBuf.copy(buf, offset, 0, 24);
266
+ return buf;
267
+ }
268
+ }
269
+
270
+ class Attendance {
271
+ sn;
272
+ user_id;
273
+ record_time;
274
+ type;
275
+ state;
276
+ _ip;
277
+ constructor(sn, user_id, record_time, type, state) {
278
+ this.sn = sn;
279
+ this.user_id = user_id;
280
+ this.record_time = record_time;
281
+ this.type = type;
282
+ this.state = state;
283
+ }
284
+ set ip(value) {
285
+ this._ip = value;
286
+ }
287
+ }
288
+
289
+ const parseHexToTime = (hex) => {
290
+ const time = {
291
+ year: hex.readUIntLE(0, 1),
292
+ month: hex.readUIntLE(1, 1),
293
+ date: hex.readUIntLE(2, 1),
294
+ hour: hex.readUIntLE(3, 1),
295
+ minute: hex.readUIntLE(4, 1),
296
+ second: hex.readUIntLE(5, 1)
297
+ };
298
+ return new Date(2000 + time.year, time.month - 1, time.date, time.hour, time.minute, time.second);
299
+ };
300
+ const createChkSum = (buf) => {
301
+ let chksum = 0;
302
+ for (let i = 0; i < buf.length; i += 2) {
303
+ if (i === buf.length - 1) {
304
+ chksum += buf[i];
305
+ }
306
+ else {
307
+ chksum += buf.readUInt16LE(i);
308
+ }
309
+ chksum %= USHRT_MAX;
310
+ }
311
+ chksum = USHRT_MAX - chksum - 1;
312
+ return chksum;
313
+ };
314
+ const createUDPHeader = (command, sessionId, replyId, data) => {
315
+ const dataBuffer = Buffer.from(data);
316
+ const buf = Buffer.alloc(8 + dataBuffer.length);
317
+ buf.writeUInt16LE(command, 0);
318
+ buf.writeUInt16LE(0, 2);
319
+ buf.writeUInt16LE(sessionId, 4);
320
+ buf.writeUInt16LE(replyId, 6);
321
+ dataBuffer.copy(buf, 8);
322
+ const chksum2 = createChkSum(buf);
323
+ buf.writeUInt16LE(chksum2, 2);
324
+ replyId = (replyId + 1) % USHRT_MAX;
325
+ buf.writeUInt16LE(replyId, 6);
326
+ return buf;
327
+ };
328
+ const createTCPHeader = (command, sessionId, replyId, data) => {
329
+ const dataBuffer = Buffer.from(data);
330
+ const buf = Buffer.alloc(8 + dataBuffer.length);
331
+ buf.writeUInt16LE(command, 0);
332
+ buf.writeUInt16LE(0, 2);
333
+ buf.writeUInt16LE(sessionId, 4);
334
+ buf.writeUInt16LE(replyId, 6);
335
+ dataBuffer.copy(buf, 8);
336
+ const chksum2 = createChkSum(buf);
337
+ buf.writeUInt16LE(chksum2, 2);
338
+ replyId = (replyId + 1) % USHRT_MAX;
339
+ buf.writeUInt16LE(replyId, 6);
340
+ const prefixBuf = Buffer.from([0x50, 0x50, 0x82, 0x7d, 0x13, 0x00, 0x00, 0x00]);
341
+ prefixBuf.writeUInt16LE(buf.length, 4);
342
+ return Buffer.concat([prefixBuf, buf]);
343
+ };
344
+ const removeTcpHeader = (buf) => {
345
+ if (buf.length < 8) {
346
+ return buf;
347
+ }
348
+ if (buf.compare(Buffer.from([0x50, 0x50, 0x82, 0x7d]), 0, 4, 0, 4) !== 0) {
349
+ return buf;
350
+ }
351
+ return buf.slice(8);
352
+ };
353
+ const parseTimeToDate = (time) => {
354
+ const second = time % 60;
355
+ time = (time - second) / 60;
356
+ const minute = time % 60;
357
+ time = (time - minute) / 60;
358
+ const hour = time % 24;
359
+ time = (time - hour) / 24;
360
+ const day = time % 31 + 1;
361
+ time = (time - (day - 1)) / 31;
362
+ const month = time % 12;
363
+ time = (time - month) / 12;
364
+ const year = time + 2000;
365
+ return new Date(year, month, day, hour, minute, second);
366
+ };
367
+ const decodeUserData28 = (userData) => {
368
+ return {
369
+ uid: userData.readUIntLE(0, 2),
370
+ privilege: userData.readUIntLE(2, 1),
371
+ name: userData
372
+ .slice(8, 8 + 8)
373
+ .toString('ascii')
374
+ .split('\0')
375
+ .shift() || '',
376
+ user_id: userData.readUIntLE(24, 4).toString(),
377
+ };
378
+ };
379
+ const decodeUserData72 = (userData) => {
380
+ return new User(userData.readUIntLE(0, 2), userData
381
+ .slice(11)
382
+ .toString('ascii')
383
+ .split('\0')
384
+ .shift() || '', userData.readUIntLE(2, 1), userData
385
+ .subarray(3, 3 + 8)
386
+ .toString('ascii')
387
+ .split('\0')
388
+ .shift() || '', userData.readUIntLE(39, 1), userData
389
+ .slice(48, 48 + 9)
390
+ .toString('ascii')
391
+ .split('\0')
392
+ .shift() || '', userData.readUIntLE(35, 4));
393
+ };
394
+ const decodeRecordData40 = (recordData) => {
395
+ return new Attendance(recordData.readUIntLE(0, 2), recordData
396
+ .slice(2, 2 + 9)
397
+ .toString('ascii')
398
+ .split('\0')
399
+ .shift() || '', parseTimeToDate(recordData.readUInt32LE(27)), recordData.readUIntLE(26, 1), recordData.readUIntLE(31, 1));
400
+ };
401
+ const decodeRecordData16 = (recordData) => {
402
+ return {
403
+ user_id: recordData.readUIntLE(0, 2).toString(),
404
+ record_time: parseTimeToDate(recordData.readUInt32LE(4))
405
+ };
406
+ };
407
+ const decodeRecordRealTimeLog18 = (recordData) => {
408
+ const user_id = recordData.readUIntLE(8, 1).toString();
409
+ const record_time = parseHexToTime(recordData.subarray(12, 18));
410
+ return { user_id, record_time };
411
+ };
412
+ const decodeRecordRealTimeLog52 = (recordData) => {
413
+ const payload = removeTcpHeader(recordData);
414
+ const recvData = payload.subarray(8);
415
+ const user_id = recvData.slice(0, 9)
416
+ .toString('ascii')
417
+ .split('\0')
418
+ .shift() || '';
419
+ const record_time = parseHexToTime(recvData.subarray(26, 26 + 6));
420
+ return { user_id, record_time };
421
+ };
422
+ const decodeUDPHeader = (header) => {
423
+ return {
424
+ commandId: header.readUIntLE(0, 2),
425
+ checkSum: header.readUIntLE(2, 2),
426
+ sessionId: header.readUIntLE(4, 2),
427
+ replyId: header.readUIntLE(6, 2)
428
+ };
429
+ };
430
+ const decodeTCPHeader = (header) => {
431
+ const recvData = header.subarray(8);
432
+ const payloadSize = header.readUIntLE(4, 2);
433
+ return {
434
+ commandId: recvData.readUIntLE(0, 2),
435
+ checkSum: recvData.readUIntLE(2, 2),
436
+ sessionId: recvData.readUIntLE(4, 2),
437
+ replyId: recvData.readUIntLE(6, 2),
438
+ payloadSize
439
+ };
440
+ };
441
+ const exportErrorMessage = (commandValue) => {
442
+ const keys = Object.keys(COMMANDS);
443
+ for (const key of keys) {
444
+ if (COMMANDS[key] === commandValue) {
445
+ return key.toString();
446
+ }
447
+ }
448
+ return 'AN UNKNOWN ERROR';
449
+ };
450
+ const checkNotEventTCP = (data) => {
451
+ try {
452
+ const cleanedData = removeTcpHeader(data);
453
+ const commandId = cleanedData.readUIntLE(0, 2);
454
+ const event = cleanedData.readUIntLE(4, 2);
455
+ return event === COMMANDS.EF_ATTLOG && commandId === COMMANDS.CMD_REG_EVENT;
456
+ }
457
+ catch (err) {
458
+ log(`[228] : ${err.toString()} ,${data.toString('hex')} `);
459
+ return false;
460
+ }
461
+ };
462
+ const checkNotEventUDP = (data) => {
463
+ const { commandId } = decodeUDPHeader(data.subarray(0, 8));
464
+ return commandId === COMMANDS.CMD_REG_EVENT;
465
+ };
466
+ const makeKey = (key, sessionId) => {
467
+ let k = 0;
468
+ for (let i = 0; i < 32; i++) {
469
+ if ((key & (1 << i)) !== 0) {
470
+ k = (k << 1) | 1;
471
+ }
472
+ else {
473
+ k = k << 1;
474
+ }
475
+ }
476
+ k += sessionId;
477
+ let hex = k.toString(16).padStart(8, "0");
478
+ let response = new Uint8Array(4);
479
+ let index = 3;
480
+ while (hex.length > 0) {
481
+ response[index] = parseInt(hex.substring(0, 2), 16);
482
+ index--;
483
+ hex = hex.substring(2);
484
+ }
485
+ response[0] ^= 'Z'.charCodeAt(0);
486
+ response[1] ^= 'K'.charCodeAt(0);
487
+ response[2] ^= 'S'.charCodeAt(0);
488
+ response[3] ^= 'O'.charCodeAt(0);
489
+ let finalKey = response[0] +
490
+ (response[1] << 8) +
491
+ (response[2] << 16) +
492
+ (response[3] << 24);
493
+ let swp = finalKey >>> 16;
494
+ finalKey = (finalKey << 16) | swp;
495
+ return finalKey >>> 0;
496
+ };
497
+ const authKey = (comKey, sessionId) => {
498
+ let k = makeKey(comKey, sessionId) >>> 0;
499
+ let rand = Math.floor(Math.random() * 256);
500
+ let hex = k.toString(16).padStart(8, "0");
501
+ let response = new Uint8Array(4);
502
+ let index = 3;
503
+ while (index >= 0) {
504
+ response[index] = parseInt(hex.substring(0, 2), 16);
505
+ index--;
506
+ hex = hex.substring(2);
507
+ }
508
+ response[0] ^= rand;
509
+ response[1] ^= rand;
510
+ response[2] = rand;
511
+ response[3] ^= rand;
512
+ return Array.from(response);
513
+ };
514
+
515
+ /**
516
+ * Represents a fingerprint template with associated metadata
517
+ */
518
+ class Finger {
519
+ uid;
520
+ fid;
521
+ valid;
522
+ template;
523
+ size;
524
+ mark;
525
+ /**
526
+ * Creates a new Finger instance
527
+ * @param uid User internal reference
528
+ * @param fid Finger ID (value >= 0 && value <= 9)
529
+ * @param valid Flag indicating 0 = invalid | 1 = valid | 3 = duress
530
+ * @param template Fingerprint template data buffer
531
+ */
532
+ constructor(uid, fid, valid, template) {
533
+ this.uid = Number(uid);
534
+ this.fid = Number(fid);
535
+ this.valid = Number(valid);
536
+ this.template = template;
537
+ this.size = template.length;
538
+ // Create mark showing first and last 8 bytes as hex
539
+ const start = template.slice(0, 8).toString('hex');
540
+ const end = template.slice(-8).toString('hex');
541
+ this.mark = `${start}...${end}`;
542
+ }
543
+ /**
544
+ * Packs the fingerprint data with metadata into a Buffer
545
+ * @returns Buffer containing packed fingerprint data
546
+ */
547
+ repack() {
548
+ // pack("HHbb%is" % (self.size), self.size+6, self.uid, self.fid, self.valid, self.template)
549
+ const buf = Buffer.alloc(6 + this.size); // HHbb = 6 bytes + template size
550
+ let offset = 0;
551
+ buf.writeUInt16LE(this.size + 6, offset);
552
+ offset += 2;
553
+ buf.writeUInt16LE(this.uid, offset);
554
+ offset += 2;
555
+ buf.writeUInt8(this.fid, offset);
556
+ offset += 1;
557
+ buf.writeUInt8(this.valid, offset);
558
+ offset += 1;
559
+ this.template.copy(buf, offset);
560
+ return buf;
561
+ }
562
+ /**
563
+ * Packs only the fingerprint template data into a Buffer
564
+ * @returns Buffer containing just the template data
565
+ */
566
+ repackOnly() {
567
+ // pack("H%is" % (self.size), self.size, self.template)
568
+ const buf = Buffer.alloc(2 + this.size); // H = 2 bytes + template size
569
+ buf.writeUInt16LE(this.size, 0);
570
+ this.template.copy(buf, 2);
571
+ return buf;
572
+ }
573
+ /**
574
+ * Compares this fingerprint with another for equality
575
+ * @param other Another Finger instance to compare with
576
+ * @returns true if all properties and template data match
577
+ */
578
+ equals(other) {
579
+ if (!(other instanceof Finger))
580
+ return false;
581
+ return this.uid === other.uid &&
582
+ this.fid === other.fid &&
583
+ this.valid === other.valid &&
584
+ this.template.equals(other.template);
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Error types for device communication
590
+ */
591
+ const ERROR_TYPES = {
592
+ ECONNRESET: 'ECONNRESET',
593
+ ECONNREFUSED: 'ECONNREFUSED'};
594
+ /**
595
+ * Custom error class for device communication errors
596
+ */
597
+ class ZkError {
598
+ err;
599
+ ip;
600
+ command;
601
+ /**
602
+ * Creates a new ZkError instance
603
+ * @param err The error object
604
+ * @param command The command that caused the error
605
+ * @param ip The IP address of the device
606
+ */
607
+ constructor(err, command, ip) {
608
+ this.err = err;
609
+ this.ip = ip;
610
+ this.command = command;
611
+ }
612
+ /**
613
+ * Gets a user-friendly error message
614
+ * @returns A formatted error message
615
+ */
616
+ toast() {
617
+ if (this.err.code === ERROR_TYPES.ECONNRESET) {
618
+ return 'Another device is connecting to the device so the connection is interrupted';
619
+ }
620
+ else if (this.err.code === ERROR_TYPES.ECONNREFUSED) {
621
+ return 'IP of the device is refused';
622
+ }
623
+ return this.err.message;
624
+ }
625
+ /**
626
+ * Gets detailed error information
627
+ * @returns An object containing error details
628
+ */
629
+ getError() {
630
+ return {
631
+ err: {
632
+ message: this.err.message,
633
+ code: this.err.code
634
+ },
635
+ ip: this.ip,
636
+ command: this.command
637
+ };
638
+ }
639
+ }
640
+
641
+ class ZTCP {
642
+ /**
643
+ * @param_ip ip address of device
644
+ * @param_port port number of device
645
+ * @param_timeout connection timout
646
+ * @param_comm_key communication key of device (if the case)
647
+ * @return Zkteco TCP socket connection instance
648
+ */
649
+ ip;
650
+ port;
651
+ timeout;
652
+ sessionId = 0;
653
+ replyId = 0;
654
+ socket;
655
+ comm_key;
656
+ user_count = 0;
657
+ fp_count = 0;
658
+ pwd_count = 0;
659
+ oplog_count = 0;
660
+ attlog_count = 0;
661
+ fp_cap = 0;
662
+ user_cap = 0;
663
+ attlog_cap = 0;
664
+ fp_av = 0;
665
+ user_av = 0;
666
+ attlog_av = 0;
667
+ face_count = 0;
668
+ face_cap = 0;
669
+ userPacketSize = 72;
670
+ verbose = false;
671
+ constructor(ip, port, timeout, comm_key, verbose) {
672
+ this.ip = ip;
673
+ this.port = port;
674
+ this.timeout = timeout;
675
+ this.replyId = 0;
676
+ this.comm_key = comm_key;
677
+ this.verbose = verbose;
678
+ }
679
+ createSocket(cbError, cbClose) {
680
+ return new Promise((resolve, reject) => {
681
+ this.socket = new net.Socket();
682
+ // Handle socket error
683
+ this.socket.once('error', (err) => {
684
+ this.socket = undefined; // Ensure socket reference is cleared
685
+ reject(err);
686
+ if (typeof cbError === 'function')
687
+ cbError(err);
688
+ });
689
+ // Handle successful connection
690
+ this.socket.once('connect', () => {
691
+ resolve(this.socket);
692
+ });
693
+ // Handle socket closure
694
+ this.socket.once('close', () => {
695
+ this.socket = undefined; // Ensure socket reference is cleared
696
+ if (typeof cbClose === 'function')
697
+ cbClose('tcp');
698
+ });
699
+ // Set socket timeout if provided
700
+ if (this.timeout) {
701
+ this.socket.setTimeout(this.timeout);
702
+ }
703
+ // Initiate connection
704
+ this.socket.connect(this.port, this.ip);
705
+ });
706
+ }
707
+ async connect() {
708
+ try {
709
+ let reply = await this.executeCmd(COMMANDS.CMD_CONNECT, '');
710
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_OK) {
711
+ return true;
712
+ }
713
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_UNAUTH) {
714
+ const hashedCommkey = authKey(this.comm_key, this.sessionId);
715
+ reply = await this.executeCmd(COMMANDS.CMD_AUTH, hashedCommkey);
716
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_OK) {
717
+ return true;
718
+ }
719
+ else {
720
+ throw new Error("error de authenticacion");
721
+ }
722
+ }
723
+ else {
724
+ // No reply received; throw an error
725
+ throw new Error('NO_REPLY_ON_CMD_CONNECT');
726
+ }
727
+ }
728
+ catch (err) {
729
+ // Log the error for debugging, if necessary
730
+ console.error('Failed to connect:', err);
731
+ // Re-throw the error for handling by the caller
732
+ throw err;
733
+ }
734
+ }
735
+ async closeSocket() {
736
+ return new Promise((resolve, reject) => {
737
+ // If no socket is present, resolve immediately
738
+ if (!this.socket) {
739
+ return resolve(true);
740
+ }
741
+ // Clean up listeners to avoid potential memory leaks or duplicate handling
742
+ this.socket.removeAllListeners('data');
743
+ // Set a timeout to handle cases where socket.end might not resolve
744
+ const timer = setTimeout(() => {
745
+ this.socket.destroy(); // Forcibly close the socket if not closed properly
746
+ resolve(true); // Resolve even if the socket was not closed properly
747
+ }, 2000);
748
+ // Close the socket and clear the timeout upon successful completion
749
+ this.socket.end(() => {
750
+ clearTimeout(timer);
751
+ resolve(true); // Resolve once the socket has ended
752
+ });
753
+ // Handle socket errors during closing
754
+ this.socket.once('error', (err) => {
755
+ clearTimeout(timer);
756
+ reject(err); // Reject the promise with the error
757
+ });
758
+ });
759
+ }
760
+ writeMessage(msg, connect) {
761
+ return new Promise((resolve, reject) => {
762
+ // Check if the socket is initialized
763
+ if (!this.socket) {
764
+ return reject(new Error('Socket is not initialized'));
765
+ }
766
+ // Define a variable for the timeout reference
767
+ let timer = null;
768
+ // Handle incoming data
769
+ const onData = (data) => {
770
+ // Check if the socket is still valid before trying to remove the listener
771
+ if (this.socket) {
772
+ this.socket.removeListener('data', onData); // Remove the data event listener
773
+ }
774
+ clearTimeout(timer); // Clear the timeout once data is received
775
+ resolve(data); // Resolve the promise with the received data
776
+ };
777
+ // Attach the data event listener
778
+ this.socket.once('data', onData);
779
+ // Attempt to write the message to the socket
780
+ this.socket.write(msg, null, (err) => {
781
+ if (err) {
782
+ // Check if the socket is still valid before trying to remove the listener
783
+ if (this.socket) {
784
+ this.socket.removeListener('data', onData); // Clean up listener on write error
785
+ }
786
+ return reject(err); // Reject the promise with the write error
787
+ }
788
+ // If a timeout is set, configure it
789
+ if (this.timeout) {
790
+ timer = setTimeout(() => {
791
+ // Check if the socket is still valid before trying to remove the listener
792
+ if (this.socket) {
793
+ this.socket.removeListener('data', onData); // Remove listener on timeout
794
+ }
795
+ reject(new Error('TIMEOUT_ON_WRITING_MESSAGE')); // Reject the promise on timeout
796
+ }, connect ? 2000 : this.timeout);
797
+ }
798
+ });
799
+ });
800
+ }
801
+ async requestData(msg) {
802
+ try {
803
+ return await new Promise((resolve, reject) => {
804
+ let timer = null;
805
+ let replyBuffer = Buffer.from([]);
806
+ // Internal callback to handle data reception
807
+ const internalCallback = (data_1) => {
808
+ if (this.socket) {
809
+ this.socket.removeListener('data', handleOnData); // Clean up listener
810
+ }
811
+ if (timer)
812
+ clearTimeout(timer); // Clear the timeout
813
+ resolve(data_1); // Resolve the promise with the data
814
+ };
815
+ // Handle incoming data
816
+ const handleOnData = (data_3) => {
817
+ replyBuffer = Buffer.concat([replyBuffer, data_3]); // Accumulate data
818
+ // Check if the data is a valid TCP event
819
+ if (checkNotEventTCP(data_3))
820
+ return;
821
+ // Decode the TCP header
822
+ const header = decodeTCPHeader(replyBuffer.subarray(0, 16));
823
+ if (this.verbose) {
824
+ console.log("linea 232: replyId: ", header.replyId, " command: ", header.commandId, Object.keys(COMMANDS).find(c => COMMANDS[c] == header.commandId));
825
+ }
826
+ // Handle based on command ID
827
+ if (header.commandId === COMMANDS.CMD_DATA) {
828
+ // Set a timeout to handle delayed responses
829
+ timer = setTimeout(() => {
830
+ internalCallback(replyBuffer); // Resolve with accumulated buffer
831
+ }, 1000);
832
+ }
833
+ else {
834
+ // Set a timeout to handle errors
835
+ timer = setTimeout(() => {
836
+ if (this.socket) {
837
+ this.socket.removeListener('data', handleOnData); // Clean up listener on timeout
838
+ }
839
+ reject(new Error('TIMEOUT_ON_RECEIVING_REQUEST_DATA')); // Reject on timeout
840
+ }, this.timeout);
841
+ // Extract packet length and handle accordingly
842
+ const packetLength = data_3.readUIntLE(4, 2);
843
+ if (packetLength > 8) {
844
+ internalCallback(data_3); // Resolve immediately if sufficient data
845
+ }
846
+ }
847
+ };
848
+ // Ensure the socket is valid before attaching the listener
849
+ if (this.socket) {
850
+ this.socket.on('data', handleOnData);
851
+ // Write the message to the socket
852
+ this.socket.write(msg, null, (err) => {
853
+ if (err) {
854
+ if (this.socket) {
855
+ this.socket.removeListener('data', handleOnData); // Clean up listener on error
856
+ }
857
+ return reject(err); // Reject the promise with the error
858
+ }
859
+ // Set a timeout to handle cases where no response is received
860
+ timer = setTimeout(() => {
861
+ if (this.socket) {
862
+ this.socket.removeListener('data', handleOnData); // Clean up listener on timeout
863
+ }
864
+ reject(new Error('TIMEOUT_IN_RECEIVING_RESPONSE_AFTER_REQUESTING_DATA')); // Reject on timeout
865
+ }, this.timeout);
866
+ });
867
+ }
868
+ else {
869
+ reject(new Error('SOCKET_NOT_INITIALIZED')); // Reject if socket is not initialized
870
+ }
871
+ });
872
+ }
873
+ catch (err_1) {
874
+ console.error("Promise Rejected:", err_1); // Log the rejection reason
875
+ throw err_1; // Re-throw the error to be handled by the caller
876
+ }
877
+ }
878
+ /**
879
+ *
880
+ * @param {*} command
881
+ * @param {*} data
882
+ *
883
+ *
884
+ * reject error when command fail and resolve data when success
885
+ */
886
+ async executeCmd(command, data) {
887
+ // Reset sessionId and replyId for connection commands
888
+ if (command === COMMANDS.CMD_CONNECT) {
889
+ this.sessionId = 0;
890
+ this.replyId = 0;
891
+ }
892
+ else {
893
+ this.replyId++;
894
+ }
895
+ if (this.verbose) {
896
+ console.log("linea 305: replyId: ", this.replyId, " command: ", command, Object.keys(COMMANDS).find(u => COMMANDS[u] == command));
897
+ }
898
+ const buf = createTCPHeader(command, this.sessionId, this.replyId, data);
899
+ try {
900
+ // Write the message to the socket and wait for a response
901
+ const reply = await this.writeMessage(buf, command === COMMANDS.CMD_CONNECT || command === COMMANDS.CMD_EXIT);
902
+ // Remove TCP header from the response
903
+ const rReply = removeTcpHeader(reply);
904
+ // Update sessionId for connection command responses
905
+ if (command === COMMANDS.CMD_CONNECT && rReply && rReply.length >= 6) { // Assuming sessionId is located at offset 4 and is 2 bytes long
906
+ this.sessionId = rReply.readUInt16LE(4);
907
+ }
908
+ return rReply;
909
+ }
910
+ catch (err) {
911
+ // Log or handle the error if necessary
912
+ console.error('Error executing command:', err);
913
+ throw err; // Re-throw the error for handling by the caller
914
+ }
915
+ }
916
+ async sendChunkRequest(start, size) {
917
+ this.replyId++;
918
+ const reqData = Buffer.alloc(8);
919
+ reqData.writeUInt32LE(start, 0);
920
+ reqData.writeUInt32LE(size, 4);
921
+ const buf = createTCPHeader(COMMANDS.CMD_DATA_RDY, this.sessionId, this.replyId, reqData);
922
+ try {
923
+ await new Promise((resolve, reject) => {
924
+ this.socket.write(buf, null, (err) => {
925
+ if (err) {
926
+ console.error(`[TCP][SEND_CHUNK_REQUEST] Error sending chunk request: ${err.message}`);
927
+ reject(err); // Reject the promise if there is an error
928
+ }
929
+ else {
930
+ resolve(true); // Resolve the promise if the write operation succeeds
931
+ }
932
+ });
933
+ });
934
+ }
935
+ catch (err) {
936
+ // Handle or log the error as needed
937
+ console.error(`[TCP][SEND_CHUNK_REQUEST] Exception: ${err.message}`);
938
+ throw err; // Re-throw the error for handling by the caller
939
+ }
940
+ }
941
+ /**
942
+ *
943
+ * @param {*} reqData - indicate the type of data that need to receive ( user or attLog)
944
+ * @param {*} cb - callback is triggered when receiving packets
945
+ *
946
+ * readWithBuffer will reject error if it'wrong when starting request data
947
+ * readWithBuffer will return { data: replyData , err: Error } when receiving requested data
948
+ */
949
+ readWithBuffer(reqData, cb = null) {
950
+ return new Promise(async (resolve, reject) => {
951
+ this.replyId++;
952
+ const buf = createTCPHeader(COMMANDS.CMD_DATA_WRRQ, this.sessionId, this.replyId, reqData);
953
+ let reply = null;
954
+ try {
955
+ reply = await this.requestData(buf);
956
+ }
957
+ catch (err) {
958
+ reject(err);
959
+ console.log(reply);
960
+ }
961
+ const header = decodeTCPHeader(reply.subarray(0, 16));
962
+ switch (header.commandId) {
963
+ case COMMANDS.CMD_DATA: {
964
+ resolve({ data: reply.subarray(16), mode: 8 });
965
+ break;
966
+ }
967
+ case COMMANDS.CMD_ACK_OK:
968
+ case COMMANDS.CMD_PREPARE_DATA: {
969
+ // this case show that data is prepared => send command to get these data
970
+ // reply variable includes information about the size of following data
971
+ const recvData = reply.subarray(16);
972
+ const size = recvData.readUIntLE(1, 4);
973
+ // We need to split the data to many chunks to receive , because it's to large
974
+ // After receiving all chunk data , we concat it to TotalBuffer variable , that 's the data we want
975
+ let remain = size % MAX_CHUNK;
976
+ let numberChunks = Math.round(size - remain) / MAX_CHUNK;
977
+ let totalPackets = numberChunks + (remain > 0 ? 1 : 0);
978
+ let replyData = Buffer.from([]);
979
+ let totalBuffer = Buffer.from([]);
980
+ let realTotalBuffer = Buffer.from([]);
981
+ const timeout = 10000;
982
+ let timer = setTimeout(() => {
983
+ internalCallback(replyData, new Error('TIMEOUT WHEN RECEIVING PACKET'));
984
+ }, timeout);
985
+ const internalCallback = (replyData, err = null) => {
986
+ // this.socket && this.socket.removeListener('data', handleOnData)
987
+ timer && clearTimeout(timer);
988
+ resolve({ data: replyData, err });
989
+ };
990
+ const handleOnData = (reply) => {
991
+ if (checkNotEventTCP(reply))
992
+ return;
993
+ clearTimeout(timer);
994
+ timer = setTimeout(() => {
995
+ internalCallback(replyData, new Error(`TIME OUT !! ${totalPackets} PACKETS REMAIN !`));
996
+ }, timeout);
997
+ totalBuffer = Buffer.concat([totalBuffer, reply]);
998
+ const packetLength = totalBuffer.readUIntLE(4, 2);
999
+ if (totalBuffer.length >= 8 + packetLength) {
1000
+ realTotalBuffer = Buffer.concat([realTotalBuffer, totalBuffer.subarray(16, 8 + packetLength)]);
1001
+ totalBuffer = totalBuffer.subarray(8 + packetLength);
1002
+ if ((totalPackets > 1 && realTotalBuffer.length === MAX_CHUNK + 8)
1003
+ || (totalPackets === 1 && realTotalBuffer.length === remain + 8)) {
1004
+ replyData = Buffer.concat([replyData, realTotalBuffer.subarray(8)]);
1005
+ totalBuffer = Buffer.from([]);
1006
+ realTotalBuffer = Buffer.from([]);
1007
+ totalPackets -= 1;
1008
+ cb && cb(replyData.length, size);
1009
+ if (totalPackets <= 0) {
1010
+ internalCallback(replyData);
1011
+ }
1012
+ }
1013
+ }
1014
+ };
1015
+ this.socket.once('close', () => {
1016
+ internalCallback(replyData, new Error('Socket is disconnected unexpectedly'));
1017
+ });
1018
+ this.socket.on('data', handleOnData);
1019
+ for (let i = 0; i <= numberChunks; i++) {
1020
+ if (i === numberChunks) {
1021
+ await this.sendChunkRequest(numberChunks * MAX_CHUNK, remain);
1022
+ }
1023
+ else {
1024
+ await this.sendChunkRequest(i * MAX_CHUNK, MAX_CHUNK);
1025
+ }
1026
+ }
1027
+ break;
1028
+ }
1029
+ default: {
1030
+ reject(new Error('ERROR_IN_UNHANDLE_CMD ' + exportErrorMessage(header.commandId)));
1031
+ }
1032
+ }
1033
+ });
1034
+ }
1035
+ /**
1036
+ * reject error when starting request data
1037
+ * @return {Record<string, User[] | Error>} when receiving requested data
1038
+ */
1039
+ async getUsers() {
1040
+ try {
1041
+ // Free any existing buffer data to prepare for a new request
1042
+ if (this.socket) {
1043
+ await this.freeData();
1044
+ }
1045
+ // Request user data
1046
+ const data = await this.readWithBuffer(REQUEST_DATA.GET_USERS);
1047
+ // Free buffer data after receiving the data
1048
+ if (this.socket) {
1049
+ await this.freeData();
1050
+ }
1051
+ // Constants for user data processing
1052
+ const USER_PACKET_SIZE = 72;
1053
+ // Ensure data.data is a valid buffer
1054
+ if (!data.data || !(data.data instanceof Buffer)) {
1055
+ throw new Error('Invalid data received');
1056
+ }
1057
+ let userData = data.data.subarray(4); // Skip the first 4 bytes (headers)
1058
+ const users = [];
1059
+ // Process each user packet
1060
+ while (userData.length >= USER_PACKET_SIZE) {
1061
+ // Decode user data and add to the users array
1062
+ const user = decodeUserData72(userData.subarray(0, USER_PACKET_SIZE));
1063
+ users.push(user);
1064
+ userData = userData.subarray(USER_PACKET_SIZE); // Move to the next packet
1065
+ }
1066
+ // Return the list of users
1067
+ return { data: users };
1068
+ }
1069
+ catch (err) {
1070
+ // Log the error for debugging
1071
+ console.error('Error getting users:', err);
1072
+ // Re-throw the error to be handled by the caller
1073
+ throw err;
1074
+ }
1075
+ }
1076
+ /**
1077
+ *
1078
+ * @param {*} ip
1079
+ * @param {*} callbackInProcess
1080
+ * reject error when starting request data
1081
+ * return { data: records, err: Error } when receiving requested data
1082
+ */
1083
+ async getAttendances(callbackInProcess = () => { }) {
1084
+ try {
1085
+ // Free any existing buffer data to prepare for a new request
1086
+ if (this.socket) {
1087
+ await this.freeData();
1088
+ }
1089
+ // Request attendance logs and handle chunked data
1090
+ const data = await this.readWithBuffer(REQUEST_DATA.GET_ATTENDANCE_LOGS, callbackInProcess);
1091
+ // Free buffer data after receiving the attendance logs
1092
+ if (this.socket) {
1093
+ await this.freeData();
1094
+ }
1095
+ // Constants for record processing
1096
+ const RECORD_PACKET_SIZE = 40;
1097
+ // Ensure data.data is a valid buffer
1098
+ if (!data.data || !(data.data instanceof Buffer)) {
1099
+ throw new Error('Invalid data received');
1100
+ }
1101
+ // Process the record data
1102
+ let recordData = data.data.subarray(4); // Skip header
1103
+ const records = [];
1104
+ // Process each attendance record
1105
+ while (recordData.length >= RECORD_PACKET_SIZE) {
1106
+ const record = decodeRecordData40(recordData.subarray(0, RECORD_PACKET_SIZE));
1107
+ records.push({ ...record, ip: this.ip }); // Add IP address to each record
1108
+ recordData = recordData.subarray(RECORD_PACKET_SIZE); // Move to the next packet
1109
+ }
1110
+ // Return the list of attendance records
1111
+ return { data: records };
1112
+ }
1113
+ catch (err) {
1114
+ // Log and re-throw the error
1115
+ console.error('Error getting attendance records:', err);
1116
+ throw err; // Re-throw the error for handling by the caller
1117
+ }
1118
+ }
1119
+ async freeData() {
1120
+ try {
1121
+ const resp = await this.executeCmd(COMMANDS.CMD_FREE_DATA, '');
1122
+ return !!resp;
1123
+ }
1124
+ catch (err) {
1125
+ console.error('Error freeing data:', err);
1126
+ throw err; // Optionally, re-throw the error if you need to handle it upstream
1127
+ }
1128
+ }
1129
+ async disableDevice() {
1130
+ try {
1131
+ const resp = await this.executeCmd(COMMANDS.CMD_DISABLEDEVICE, REQUEST_DATA.DISABLE_DEVICE);
1132
+ return !!resp;
1133
+ }
1134
+ catch (err) {
1135
+ console.error('Error disabling device:', err);
1136
+ throw err; // Optionally, re-throw the error if you need to handle it upstream
1137
+ }
1138
+ }
1139
+ async enableDevice() {
1140
+ try {
1141
+ const resp = await this.executeCmd(COMMANDS.CMD_ENABLEDEVICE, '');
1142
+ return !!resp;
1143
+ }
1144
+ catch (err) {
1145
+ console.error('Error enabling device:', err);
1146
+ throw err; // Optionally, re-throw the error if you need to handle it upstream
1147
+ }
1148
+ }
1149
+ async disconnect() {
1150
+ try {
1151
+ // Attempt to execute the disconnect command
1152
+ await this.executeCmd(COMMANDS.CMD_EXIT, '');
1153
+ }
1154
+ catch (err) {
1155
+ // Log any errors encountered during command execution
1156
+ console.error('Error during disconnection:', err);
1157
+ // Optionally, add more handling or recovery logic here
1158
+ }
1159
+ // Attempt to close the socket and return the result
1160
+ try {
1161
+ await this.closeSocket();
1162
+ }
1163
+ catch (err) {
1164
+ // Log any errors encountered while closing the socket
1165
+ console.error('Error during socket closure:', err);
1166
+ // Optionally, rethrow or handle the error if necessary
1167
+ throw err; // Re-throwing to propagate the error
1168
+ }
1169
+ }
1170
+ async getInfo() {
1171
+ try {
1172
+ // Execute the command to retrieve free sizes from the device
1173
+ const data = await this.executeCmd(COMMANDS.CMD_GET_FREE_SIZES, '');
1174
+ // Parse the response data to extract and return relevant information
1175
+ return {
1176
+ userCounts: data.readUIntLE(24, 4), // Number of users
1177
+ logCounts: data.readUIntLE(40, 4), // Number of logs
1178
+ logCapacity: data.readUIntLE(72, 4) // Capacity of logs in bytes
1179
+ };
1180
+ }
1181
+ catch (err) {
1182
+ // Log the error for debugging purposes
1183
+ console.error('Error getting device info:', err);
1184
+ // Re-throw the error to allow upstream error handling
1185
+ throw err;
1186
+ }
1187
+ }
1188
+ async getSizes() {
1189
+ try {
1190
+ // Execute the command to retrieve free sizes from the device
1191
+ const data = await this.executeCmd(COMMANDS.CMD_GET_FREE_SIZES, '');
1192
+ // Parse the response data to extract and return relevant information
1193
+ const buf = data.slice(8); // remove header
1194
+ this.user_count = buf.readUIntLE(16, 4);
1195
+ this.fp_count = buf.readUIntLE(24, 4);
1196
+ this.pwd_count = buf.readUIntLE(52, 4);
1197
+ this.oplog_count = buf.readUIntLE(40, 4);
1198
+ this.attlog_count = buf.readUIntLE(32, 4);
1199
+ this.fp_cap = buf.readUIntLE(56, 4);
1200
+ this.user_cap = buf.readUIntLE(60, 4);
1201
+ this.attlog_cap = buf.readUIntLE(64, 4);
1202
+ this.fp_av = buf.readUIntLE(68, 4);
1203
+ this.user_av = buf.readUIntLE(72, 4);
1204
+ this.attlog_av = buf.readUIntLE(76, 4);
1205
+ this.face_count = buf.readUIntLE(80, 4);
1206
+ this.face_cap = buf.readUIntLE(88, 4);
1207
+ return {
1208
+ userCounts: this.user_count, // Number of users
1209
+ logCounts: this.attlog_count, // Number of logs
1210
+ fingerCount: this.fp_count,
1211
+ adminCount: this.pwd_count,
1212
+ opLogCount: this.oplog_count,
1213
+ logCapacity: this.attlog_cap, // Capacity of logs in bytes
1214
+ fingerCapacity: this.fp_cap,
1215
+ userCapacity: this.user_cap,
1216
+ attLogCapacity: this.attlog_cap,
1217
+ fingerAvailable: this.fp_av,
1218
+ userAvailable: this.user_av,
1219
+ attLogAvailable: this.attlog_av,
1220
+ faceCount: this.face_count,
1221
+ faceCapacity: this.face_cap
1222
+ };
1223
+ }
1224
+ catch (err) {
1225
+ // Log the error for debugging purposes
1226
+ console.error('Error getting device info:', err);
1227
+ // Re-throw the error to allow upstream error handling
1228
+ throw err;
1229
+ }
1230
+ }
1231
+ async getVendor() {
1232
+ const keyword = '~OEMVendor';
1233
+ try {
1234
+ // Execute the command to get serial number
1235
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1236
+ // Extract and format the serial number from the response data
1237
+ const vendor = data.slice(8) // Skip the first 8 bytes (header)
1238
+ .toString('ascii') // Convert buffer to string
1239
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1240
+ .replace(/\u0000/g, ''); // Remove null characters
1241
+ return vendor;
1242
+ }
1243
+ catch (err) {
1244
+ // Log the error for debugging
1245
+ console.error('Error getting vendor:', err);
1246
+ // Re-throw the error for higher-level handling
1247
+ throw err;
1248
+ }
1249
+ }
1250
+ async getProductTime() {
1251
+ const keyword = '~ProductTime';
1252
+ try {
1253
+ // Execute the command to get serial number
1254
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1255
+ // Extract and format the serial number from the response data
1256
+ const ProductTime = data.slice(8) // Skip the first 8 bytes (header)
1257
+ .toString('ascii') // Convert buffer to string
1258
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1259
+ .replace(/\u0000/g, ''); // Remove null characters
1260
+ return new Date(ProductTime);
1261
+ }
1262
+ catch (err) {
1263
+ // Log the error for debugging
1264
+ console.error('Error getting Product Time:', err);
1265
+ // Re-throw the error for higher-level handling
1266
+ throw err;
1267
+ }
1268
+ }
1269
+ async getMacAddress() {
1270
+ const keyword = 'MAC';
1271
+ try {
1272
+ // Execute the command to get serial number
1273
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1274
+ // Extract and format the serial number from the response data
1275
+ const macAddr = data.slice(8) // Skip the first 8 bytes (header)
1276
+ .toString('ascii') // Convert buffer to string
1277
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1278
+ .replace(/\u0000/g, ''); // Remove null characters
1279
+ return macAddr;
1280
+ }
1281
+ catch (err) {
1282
+ // Log the error for debugging
1283
+ console.error('Error getting MAC address:', err);
1284
+ // Re-throw the error for higher-level handling
1285
+ throw err;
1286
+ }
1287
+ }
1288
+ async getSerialNumber() {
1289
+ const keyword = '~SerialNumber';
1290
+ try {
1291
+ // Execute the command to get serial number
1292
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1293
+ // Extract and format the serial number from the response data
1294
+ const serialNumber = data.slice(8) // Skip the first 8 bytes (header)
1295
+ .toString('utf-8') // Convert buffer to string
1296
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1297
+ .replace(/\u0000/g, ''); // Remove null characters
1298
+ return serialNumber;
1299
+ }
1300
+ catch (err) {
1301
+ // Log the error for debugging
1302
+ console.error('Error getting serial number:', err);
1303
+ // Re-throw the error for higher-level handling
1304
+ throw err;
1305
+ }
1306
+ }
1307
+ async getDeviceVersion() {
1308
+ const keyword = '~ZKFPVersion';
1309
+ try {
1310
+ // Execute the command to get device version
1311
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1312
+ // Extract and format the device version from the response data
1313
+ // Remove null characters
1314
+ return data.slice(8) // Skip the first 8 bytes (header)
1315
+ .toString('ascii') // Convert buffer to ASCII string
1316
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1317
+ .replace(/\u0000/g, '');
1318
+ }
1319
+ catch (err) {
1320
+ // Log the error for debugging
1321
+ console.error('Error getting device version:', err);
1322
+ // Re-throw the error for higher-level handling
1323
+ throw err;
1324
+ }
1325
+ }
1326
+ async getDeviceName() {
1327
+ const keyword = '~DeviceName';
1328
+ try {
1329
+ // Execute the command to get the device name
1330
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1331
+ // Extract and format the device name from the response data
1332
+ // Remove null characters
1333
+ return data.slice(8) // Skip the first 8 bytes (header)
1334
+ .toString('ascii') // Convert buffer to ASCII string
1335
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1336
+ .replace(/\u0000/g, '');
1337
+ }
1338
+ catch (err) {
1339
+ // Log the error for debugging
1340
+ console.error('Error getting device name:', err);
1341
+ // Re-throw the error for higher-level handling
1342
+ throw err;
1343
+ }
1344
+ }
1345
+ async getPlatform() {
1346
+ const keyword = '~Platform';
1347
+ try {
1348
+ // Execute the command to get the platform information
1349
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1350
+ // Extract and format the platform information from the response data
1351
+ // Remove null characters
1352
+ return data.slice(8) // Skip the first 8 bytes (header)
1353
+ .toString('ascii') // Convert buffer to ASCII string
1354
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1355
+ .replace(/\u0000/g, '');
1356
+ }
1357
+ catch (err) {
1358
+ // Log the error for debugging
1359
+ console.error('Error getting platform information:', err);
1360
+ // Re-throw the error for higher-level handling
1361
+ throw err;
1362
+ }
1363
+ }
1364
+ async getOS() {
1365
+ const keyword = '~OS';
1366
+ try {
1367
+ // Execute the command to get the OS information
1368
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1369
+ // Extract and format the OS information from the response data
1370
+ // Remove null characters
1371
+ return data.slice(8) // Skip the first 8 bytes (header)
1372
+ .toString('ascii') // Convert buffer to ASCII string
1373
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1374
+ .replace(/\u0000/g, '');
1375
+ }
1376
+ catch (err) {
1377
+ // Log the error for debugging
1378
+ console.error('Error getting OS information:', err);
1379
+ // Re-throw the error for higher-level handling
1380
+ throw err;
1381
+ }
1382
+ }
1383
+ async getWorkCode() {
1384
+ const keyword = 'WorkCode';
1385
+ try {
1386
+ // Execute the command to get the WorkCode information
1387
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1388
+ // Extract and format the WorkCode information from the response data
1389
+ // Remove null characters
1390
+ return data.slice(8) // Skip the first 8 bytes (header)
1391
+ .toString('ascii') // Convert buffer to ASCII string
1392
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1393
+ .replace(/\u0000/g, '');
1394
+ }
1395
+ catch (err) {
1396
+ // Log the error for debugging
1397
+ console.error('Error getting WorkCode:', err);
1398
+ // Re-throw the error to be handled by the caller
1399
+ throw err;
1400
+ }
1401
+ }
1402
+ async getPIN() {
1403
+ const keyword = '~PIN2Width';
1404
+ try {
1405
+ // Execute the command to get the PIN information
1406
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1407
+ // Extract and format the PIN information from the response data
1408
+ // Remove null characters
1409
+ return data.slice(8) // Skip the first 8 bytes (header)
1410
+ .toString('ascii') // Convert buffer to ASCII string
1411
+ .replace(`${keyword}=`, '') // Remove the keyword prefix
1412
+ .replace(/\u0000/g, '');
1413
+ }
1414
+ catch (err) {
1415
+ // Log the error for debugging
1416
+ console.error('Error getting PIN:', err);
1417
+ // Re-throw the error to be handled by the caller
1418
+ throw err;
1419
+ }
1420
+ }
1421
+ async getFaceOn() {
1422
+ const keyword = 'FaceFunOn';
1423
+ try {
1424
+ // Execute the command to get the face function status
1425
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1426
+ // Extract and process the status from the response data
1427
+ const status = data.slice(8) // Skip the first 8 bytes (header)
1428
+ .toString('ascii') // Convert buffer to ASCII string
1429
+ .replace(`${keyword}=`, ''); // Remove the keyword prefix
1430
+ // Determine and return the face function status
1431
+ return status.includes('0') ? 'No' : 'Yes';
1432
+ }
1433
+ catch (err) {
1434
+ // Log the error for debugging
1435
+ console.error('Error getting face function status:', err);
1436
+ // Re-throw the error to be handled by the caller
1437
+ throw err;
1438
+ }
1439
+ }
1440
+ async getSSR() {
1441
+ const keyword = '~SSR';
1442
+ try {
1443
+ // Execute the command to get the SSR value
1444
+ const data = await this.executeCmd(COMMANDS.CMD_OPTIONS_RRQ, keyword);
1445
+ // Extract and process the SSR value from the response data
1446
+ // Remove the keyword prefix
1447
+ // Return the SSR value
1448
+ return data.slice(8) // Skip the first 8 bytes (header)
1449
+ .toString('ascii') // Convert buffer to ASCII string
1450
+ .replace(`${keyword}=`, '');
1451
+ }
1452
+ catch (err) {
1453
+ // Log the error for debugging
1454
+ console.error('Error getting SSR value:', err);
1455
+ // Re-throw the error to be handled by the caller
1456
+ throw err;
1457
+ }
1458
+ }
1459
+ async getFirmware() {
1460
+ try {
1461
+ // Execute the command to get firmware information
1462
+ const data = await this.executeCmd(1100, '');
1463
+ // Extract and return the firmware version from the response data
1464
+ return data.slice(8).toString('ascii'); // Skip the first 8 bytes (header) and convert to ASCII string
1465
+ }
1466
+ catch (err) {
1467
+ // Log the error for debugging
1468
+ console.error('Error getting firmware version:', err);
1469
+ // Re-throw the error to be handled by the caller
1470
+ throw err;
1471
+ }
1472
+ }
1473
+ async getTime() {
1474
+ try {
1475
+ // Execute the command to get the current time
1476
+ const response = await this.executeCmd(COMMANDS.CMD_GET_TIME, '');
1477
+ // Check if the response is valid
1478
+ if (!response || response.length < 12) {
1479
+ throw new Error('Invalid response received for time command');
1480
+ }
1481
+ // Extract and decode the time value from the response
1482
+ const timeValue = response.readUInt32LE(8); // Read 4 bytes starting at offset 8
1483
+ return timeParser.decode(timeValue); // Parse and return the decoded time
1484
+ }
1485
+ catch (err) {
1486
+ // Log the error for debugging
1487
+ console.error('Error getting time:', err);
1488
+ // Re-throw the error for the caller to handle
1489
+ throw err;
1490
+ }
1491
+ }
1492
+ async setTime(tm) {
1493
+ try {
1494
+ // Validate the input time
1495
+ if (!(tm instanceof Date) && typeof tm !== 'number') {
1496
+ throw new TypeError('Invalid time parameter. Must be a Date object or a timestamp.');
1497
+ }
1498
+ // Convert the input time to a Date object if it's not already
1499
+ const date = (tm instanceof Date) ? tm : new Date(tm);
1500
+ // Encode the time into the required format
1501
+ const encodedTime = timeParser.encode(date);
1502
+ // Create a buffer and write the encoded time
1503
+ const commandString = Buffer.alloc(32);
1504
+ commandString.writeUInt32LE(encodedTime, 0);
1505
+ // Send the command to set the time
1506
+ const time = await this.executeCmd(COMMANDS.CMD_SET_TIME, commandString);
1507
+ return !!time;
1508
+ }
1509
+ catch (err) {
1510
+ // Log the error for debugging
1511
+ console.error('Error setting time:', err);
1512
+ // Re-throw the error for the caller to handle
1513
+ throw err;
1514
+ }
1515
+ }
1516
+ async voiceTest() {
1517
+ try {
1518
+ // Define the command data for the voice test
1519
+ const commandData = Buffer.from('\x00\x00', 'binary');
1520
+ await this.executeCmd(COMMANDS.CMD_TESTVOICE, commandData);
1521
+ // Execute the command and return the result
1522
+ }
1523
+ catch (err) {
1524
+ // Log the error for debugging purposes
1525
+ console.error('Error executing voice test:', err);
1526
+ // Re-throw the error to be handled by the caller
1527
+ throw err;
1528
+ }
1529
+ }
1530
+ async setUser(uid, userid, name, password, role = 0, cardno = 0) {
1531
+ try {
1532
+ // Validate input parameters
1533
+ if (uid <= 0 || uid > 3000 ||
1534
+ userid.length > 9 ||
1535
+ name.length > 24 ||
1536
+ password.length > 8 ||
1537
+ typeof role !== 'number' ||
1538
+ cardno.toString().length > 10) {
1539
+ throw new Error('Invalid input parameters');
1540
+ }
1541
+ // Allocate and initialize the buffer
1542
+ const commandBuffer = Buffer.alloc(72);
1543
+ // Fill the buffer with user data
1544
+ commandBuffer.writeUInt16LE(uid, 0);
1545
+ commandBuffer.writeUInt16LE(role, 2);
1546
+ commandBuffer.write(password.padEnd(8, '\0'), 3, 8); // Ensure password is 8 bytes
1547
+ commandBuffer.write(name.padEnd(24, '\0'), 11, 24); // Ensure name is 24 bytes
1548
+ commandBuffer.writeUInt16LE(cardno, 35);
1549
+ commandBuffer.writeUInt32LE(0, 40); // Placeholder or reserved field
1550
+ commandBuffer.write(userid.padEnd(9, '\0'), 48, 9); // Ensure userid is 9 bytes
1551
+ // Send the command and return the result
1552
+ const created = await this.executeCmd(COMMANDS.CMD_USER_WRQ, commandBuffer);
1553
+ return !!created;
1554
+ }
1555
+ catch (err) {
1556
+ // Log error details for debugging
1557
+ console.error('Error setting user:', err);
1558
+ // Re-throw error for upstream handling
1559
+ throw err;
1560
+ }
1561
+ }
1562
+ async deleteUser(uid) {
1563
+ try {
1564
+ // Validate input parameter
1565
+ if (uid <= 0 || uid > 3000) {
1566
+ throw new Error('Invalid UID: must be between 1 and 3000');
1567
+ }
1568
+ // Allocate and initialize the buffer
1569
+ const commandBuffer = Buffer.alloc(72);
1570
+ // Write UID to the buffer
1571
+ commandBuffer.writeUInt16LE(uid, 0);
1572
+ // Send the delete command and return the result
1573
+ const deleted = await this.executeCmd(COMMANDS.CMD_DELETE_USER, commandBuffer);
1574
+ return !!deleted;
1575
+ }
1576
+ catch (err) {
1577
+ // Log error details for debugging
1578
+ console.error('Error deleting user:', err);
1579
+ // Re-throw error for upstream handling
1580
+ throw err;
1581
+ }
1582
+ }
1583
+ async getAttendanceSize() {
1584
+ try {
1585
+ // Execute command to get free sizes
1586
+ const data = await this.executeCmd(COMMANDS.CMD_GET_FREE_SIZES, '');
1587
+ // Parse and return the attendance size
1588
+ return data.readUIntLE(40, 4); // Assuming data at offset 40 represents the attendance size
1589
+ }
1590
+ catch (err) {
1591
+ // Log error details for debugging
1592
+ console.error('Error getting attendance size:', err);
1593
+ // Re-throw the error to be handled by the caller
1594
+ throw err;
1595
+ }
1596
+ }
1597
+ // Clears the attendance logs on the device
1598
+ async clearAttendanceLog() {
1599
+ try {
1600
+ // Execute the command to clear attendance logs
1601
+ return await this.executeCmd(COMMANDS.CMD_CLEAR_ATTLOG, '');
1602
+ }
1603
+ catch (err) {
1604
+ // Log the error for debugging purposes
1605
+ console.error('Error clearing attendance log:', err);
1606
+ // Re-throw the error to be handled by the caller
1607
+ throw err;
1608
+ }
1609
+ }
1610
+ // Clears all data on the device
1611
+ async clearData() {
1612
+ try {
1613
+ // Execute the command to clear all data
1614
+ return await this.executeCmd(COMMANDS.CMD_CLEAR_DATA, '');
1615
+ }
1616
+ catch (err) {
1617
+ // Log the error for debugging purposes
1618
+ console.error('Error clearing data:', err);
1619
+ // Re-throw the error to be handled by the caller
1620
+ throw err;
1621
+ }
1622
+ }
1623
+ async getRealTimeLogs(cb = (realTimeLog) => { }) {
1624
+ this.replyId++; // Increment the reply ID for this request
1625
+ try {
1626
+ // Create a buffer with the command header to request real-time logs
1627
+ const buf = createTCPHeader(COMMANDS.CMD_REG_EVENT, this.sessionId, this.replyId, Buffer.from([0x01, 0x00, 0x00, 0x00]));
1628
+ // Send the request to the device
1629
+ this.socket.write(buf, null, (err) => {
1630
+ if (err) {
1631
+ // Log and reject the promise if there is an error writing to the socket
1632
+ console.error('Error sending real-time logs request:', err);
1633
+ throw err;
1634
+ }
1635
+ });
1636
+ // Ensure data listeners are added only once
1637
+ if (this.socket.listenerCount('data') === 0) {
1638
+ this.socket.on('data', (data) => {
1639
+ // Check if the data is an event and not just a regular response
1640
+ if (checkNotEventTCP(data)) {
1641
+ // Process the data if it is of the expected length
1642
+ if (data.length > 16) {
1643
+ // Decode and pass the log to the callback
1644
+ cb(decodeRecordRealTimeLog52(data));
1645
+ }
1646
+ }
1647
+ });
1648
+ }
1649
+ }
1650
+ catch (err) {
1651
+ // Handle errors and reject the promise
1652
+ console.error('Error getting real-time logs:', err);
1653
+ throw err;
1654
+ }
1655
+ }
1656
+ async getTemplates() {
1657
+ try {
1658
+ await this.freeData();
1659
+ await this.disableDevice();
1660
+ const Buffer = await this.readWithBuffer(REQUEST_DATA.GET_TEMPLATES);
1661
+ let templateData = Buffer.data.slice(4);
1662
+ let totalSize = Buffer.data.readUIntLE(0, 4);
1663
+ let templates = [];
1664
+ while (totalSize) {
1665
+ const buf = templateData.slice(0, 6);
1666
+ const size = buf.readUIntLE(0, 2);
1667
+ templates.push(new Finger(buf.readUIntLE(2, 2), buf.readUIntLE(4, 1), buf.readUIntLE(5, 1), templateData.slice(6, size)));
1668
+ templateData = templateData.slice(size);
1669
+ totalSize -= size;
1670
+ }
1671
+ return templates;
1672
+ }
1673
+ catch (err) {
1674
+ console.error('Error getting user templates: ', err);
1675
+ throw err;
1676
+ }
1677
+ finally {
1678
+ await this.enableDevice();
1679
+ await this.freeData();
1680
+ }
1681
+ }
1682
+ async refreshData() {
1683
+ try {
1684
+ const reply = await this.executeCmd(COMMANDS.CMD_REFRESHDATA, '');
1685
+ return !!reply;
1686
+ }
1687
+ catch (err) {
1688
+ console.error('Error getting user templates: ', err);
1689
+ throw err;
1690
+ }
1691
+ }
1692
+ async sendWithBuffer(buffer) {
1693
+ const MAX_CHUNK = 1024;
1694
+ const size = buffer.length;
1695
+ await this.freeData();
1696
+ const commandString = Buffer.alloc(4); // 'I' is 4 bytes
1697
+ commandString.writeUInt32LE(size, 0);
1698
+ try {
1699
+ const cmdResponse = await this.executeCmd(COMMANDS.CMD_PREPARE_DATA, commandString);
1700
+ // responds with 2000 = CMD_ACK_OK
1701
+ if (!cmdResponse) {
1702
+ throw new Error("Can't prepare data");
1703
+ }
1704
+ }
1705
+ catch (e) {
1706
+ console.error(e);
1707
+ }
1708
+ const remain = size % MAX_CHUNK;
1709
+ const packets = Math.floor((size - remain) / MAX_CHUNK);
1710
+ let start = 0;
1711
+ try {
1712
+ for (let i = 0; i < packets; i++) {
1713
+ const resp = await this.sendChunk(buffer.slice(start, start + MAX_CHUNK));
1714
+ if (resp) {
1715
+ start += MAX_CHUNK;
1716
+ if (i == packets - 1 && remain) {
1717
+ const lastPacket = await this.sendChunk(buffer.slice(start, start + remain));
1718
+ return lastPacket;
1719
+ }
1720
+ }
1721
+ }
1722
+ }
1723
+ catch (e) {
1724
+ console.error(e);
1725
+ }
1726
+ }
1727
+ async sendChunk(commandString) {
1728
+ try {
1729
+ return await new Promise((resolve, reject) => {
1730
+ resolve(this.executeCmd(COMMANDS.CMD_DATA, commandString));
1731
+ });
1732
+ }
1733
+ catch (e) {
1734
+ throw new ZkError(e, COMMANDS.CMD_DATA, this.ip);
1735
+ }
1736
+ }
1737
+ /**
1738
+ * save user and template
1739
+ *
1740
+ * @param {User | number | string} user - User class object | uid | user_id
1741
+ * @param {Finger[]} fingers - Array of finger class. `0 <= index <= 9`
1742
+ */
1743
+ async saveUserTemplate(user, fingers = []) {
1744
+ if (fingers.length > 9 || fingers.length == 0)
1745
+ throw new Error("maximum finger length is 10 and can't be empty");
1746
+ try {
1747
+ await this.disableDevice();
1748
+ const users = await this.getUsers();
1749
+ //check users exists
1750
+ if (!users.data.some(u => u.uid == user.uid || +u.user_id == +user.user_id))
1751
+ throw new Error("error validating user input");
1752
+ if (!(user instanceof User)) {
1753
+ let tusers = users.data.filter(x => x.uid === +user.uid);
1754
+ if (tusers.length === 1) {
1755
+ user = tusers[0];
1756
+ }
1757
+ else {
1758
+ tusers = users.data.filter(x => x.user_id === String(user));
1759
+ if (tusers.length === 1) {
1760
+ user = tusers[0];
1761
+ }
1762
+ else {
1763
+ throw new Error("Can't find user");
1764
+ }
1765
+ }
1766
+ }
1767
+ if (fingers instanceof Finger) {
1768
+ fingers = [fingers];
1769
+ }
1770
+ let fpack = Buffer.alloc(0);
1771
+ let table = Buffer.alloc(0);
1772
+ const fnum = 0x10;
1773
+ let tstart = 0;
1774
+ for (const finger of fingers) {
1775
+ const tfp = finger.repackOnly();
1776
+ const tableEntry = Buffer.alloc(11); // b=1, H=2, b=1, I=4 => 1+2+1+4=8? Wait, bHbI is 1+2+1+4=8 bytes
1777
+ tableEntry.writeInt8(2, 0);
1778
+ tableEntry.writeUInt16LE(user.uid, 1);
1779
+ tableEntry.writeInt8(fnum + finger.fid, 3);
1780
+ tableEntry.writeUInt32LE(tstart, 4);
1781
+ table = Buffer.concat([table, tableEntry]);
1782
+ tstart += tfp.length;
1783
+ fpack = Buffer.concat([fpack, tfp]);
1784
+ }
1785
+ let upack;
1786
+ if (this.userPacketSize === 28) {
1787
+ upack = user.repack29();
1788
+ }
1789
+ else {
1790
+ upack = user.repack73();
1791
+ }
1792
+ const head = Buffer.alloc(12); // III = 3*4 bytes
1793
+ head.writeUInt32LE(upack.length, 0);
1794
+ head.writeUInt32LE(table.length, 4);
1795
+ head.writeUInt32LE(fpack.length, 8);
1796
+ const packet = Buffer.concat([head, upack, table, fpack]);
1797
+ const bufferResponse = await this.sendWithBuffer(packet);
1798
+ const command = 110;
1799
+ const commandString = Buffer.alloc(8); // <IHH = I(4) + H(2) + H(2) = 8 bytes
1800
+ commandString.writeUInt32LE(12, 0);
1801
+ commandString.writeUInt16LE(0, 4);
1802
+ commandString.writeUInt16LE(8, 6);
1803
+ const cmdResponse = await this.executeCmd(command, commandString);
1804
+ if (this.verbose)
1805
+ console.log("finally bulk save user templates: \n", cmdResponse.readUInt16LE(0));
1806
+ }
1807
+ catch (error) {
1808
+ throw error;
1809
+ }
1810
+ finally {
1811
+ await this.refreshData();
1812
+ await this.enableDevice();
1813
+ }
1814
+ }
1815
+ async deleteFinger(uid, fid) {
1816
+ try {
1817
+ const buf = Buffer.alloc(4);
1818
+ buf.writeUInt16LE(uid, 0);
1819
+ buf.writeUint16LE(fid, 2);
1820
+ const reply = await this.executeCmd(COMMANDS.CMD_DELETE_USERTEMP, buf);
1821
+ return !!reply;
1822
+ }
1823
+ catch (error) {
1824
+ throw new Error("Can't save utemp");
1825
+ }
1826
+ finally {
1827
+ await this.refreshData();
1828
+ }
1829
+ }
1830
+ async enrollUser(uid, tempId, userId = '') {
1831
+ let done = false;
1832
+ try {
1833
+ //validate user exists
1834
+ const users = await this.getUsers();
1835
+ const filteredUsers = users.data.filter(x => x.uid === uid);
1836
+ if (filteredUsers.length >= 1) {
1837
+ userId = filteredUsers[0].user_id;
1838
+ }
1839
+ else {
1840
+ throw new Error("user not found");
1841
+ }
1842
+ const userBuf = Buffer.alloc(24);
1843
+ userBuf.write(userId.toString(), 0, 24, 'ascii');
1844
+ let commandString = Buffer.concat([
1845
+ userBuf,
1846
+ Buffer.from([tempId, 1])
1847
+ ]);
1848
+ const cancel = await this.cancelCapture();
1849
+ const cmdResponse = await this.executeCmd(COMMANDS.CMD_STARTENROLL, commandString);
1850
+ this.timeout = 60000; // 60 seconds timeout
1851
+ let attempts = 3;
1852
+ while (attempts > 0) {
1853
+ if (this.verbose)
1854
+ console.log(`A:${attempts} esperando primer regevent`);
1855
+ let dataRecv = await this.readSocket(17);
1856
+ await this.ackOk();
1857
+ if (dataRecv.length > 16) {
1858
+ const padded = Buffer.concat([dataRecv, Buffer.alloc(24 - dataRecv.length)]);
1859
+ const res = padded.readUInt16LE(16);
1860
+ if (this.verbose)
1861
+ console.log(`res ${res}`);
1862
+ if (res === 0 || res === 6 || res === 4) {
1863
+ if (this.verbose)
1864
+ console.log("posible timeout o reg Fallido");
1865
+ break;
1866
+ }
1867
+ }
1868
+ if (this.verbose)
1869
+ console.log(`A:${attempts} esperando 2do regevent`);
1870
+ dataRecv = await this.readSocket(17);
1871
+ await this.ackOk();
1872
+ if (this.verbose)
1873
+ console.log(dataRecv);
1874
+ if (dataRecv.length > 8) {
1875
+ const padded = Buffer.concat([dataRecv, Buffer.alloc(24 - dataRecv.length)]);
1876
+ const res = padded.readUInt16LE(16);
1877
+ if (this.verbose)
1878
+ console.log(`res ${res}`);
1879
+ if (res === 6 || res === 4) {
1880
+ if (this.verbose)
1881
+ console.log("posible timeout o reg Fallido");
1882
+ break;
1883
+ }
1884
+ else if (res === 0x64) {
1885
+ if (this.verbose)
1886
+ console.log("ok, continue?");
1887
+ attempts--;
1888
+ }
1889
+ }
1890
+ }
1891
+ if (attempts === 0) {
1892
+ const dataRecv = await this.readSocket(17);
1893
+ await this.ackOk();
1894
+ if (this.verbose)
1895
+ console.log(dataRecv.toString('hex'));
1896
+ const padded = Buffer.concat([dataRecv, Buffer.alloc(24 - dataRecv.length)]);
1897
+ let res = padded.readUInt16LE(16);
1898
+ if (this.verbose)
1899
+ console.log(`res ${res}`);
1900
+ if (res === 5) {
1901
+ if (this.verbose)
1902
+ console.log("finger duplicate");
1903
+ }
1904
+ if (res === 6 || res === 4) {
1905
+ if (this.verbose)
1906
+ console.log("posible timeout");
1907
+ }
1908
+ if (res === 0) {
1909
+ const size = padded.readUInt16LE(10);
1910
+ const pos = padded.readUInt16LE(12);
1911
+ if (this.verbose)
1912
+ console.log(`enroll ok ${size} ${pos}`);
1913
+ done = true;
1914
+ }
1915
+ }
1916
+ //this.__sock.setTimeout(this.__timeout);
1917
+ await this.regEvent(0); // TODO: test
1918
+ return done;
1919
+ }
1920
+ catch (error) {
1921
+ throw error;
1922
+ }
1923
+ finally {
1924
+ await this.cancelCapture();
1925
+ await this.verifyUser(undefined);
1926
+ }
1927
+ }
1928
+ async readSocket(length, cb = null) {
1929
+ let replyBufer = Buffer.from([]);
1930
+ let totalPackets = 0;
1931
+ return new Promise((resolve, reject) => {
1932
+ let timer = setTimeout(() => {
1933
+ internalCallback(replyBufer, new Error('TIMEOUT WHEN RECEIVING PACKET'));
1934
+ }, this.timeout);
1935
+ const internalCallback = (replyData, err = null) => {
1936
+ this.socket && this.socket.removeListener('data', onDataEnroll);
1937
+ timer && clearTimeout(timer);
1938
+ resolve({ data: replyData, err: err });
1939
+ };
1940
+ function onDataEnroll(data) {
1941
+ clearTimeout(timer);
1942
+ timer = setTimeout(() => {
1943
+ internalCallback(replyBufer, new Error(`TIME OUT !! ${totalPackets} PACKETS REMAIN !`));
1944
+ }, this.timeout);
1945
+ replyBufer = Buffer.concat([replyBufer, data], replyBufer.length + data.length);
1946
+ if (data.length == length) {
1947
+ internalCallback(data);
1948
+ }
1949
+ }
1950
+ this.socket.once('close', () => {
1951
+ internalCallback(replyBufer, new Error('Socket is disconnected unexpectedly'));
1952
+ });
1953
+ this.socket.on('data', onDataEnroll);
1954
+ }).catch((err) => {
1955
+ console.error("Promise Rejected:", err); // Log the rejection reason
1956
+ throw err; // Re-throw the error to be handled by the caller
1957
+ });
1958
+ }
1959
+ /**
1960
+ * Register events
1961
+ * @param {number} flags - Event flags
1962
+ * @returns {Promise<void>}
1963
+ * @throws {ZKErrorResponse} If registration fails
1964
+ */
1965
+ async regEvent(flags) {
1966
+ try {
1967
+ const commandString = Buffer.alloc(4); // 'I' format is 4 bytes
1968
+ commandString.writeUInt32LE(flags, 0); // Little-endian unsigned int
1969
+ const cmdResponse = await this.executeCmd(COMMANDS.CMD_REG_EVENT, commandString);
1970
+ if (this.verbose)
1971
+ console.log("regEvent: ", cmdResponse.readUInt16LE(0));
1972
+ }
1973
+ catch (e) {
1974
+ throw new ZkError(e, COMMANDS.CMD_REG_EVENT, this.ip);
1975
+ }
1976
+ }
1977
+ async ackOk() {
1978
+ try {
1979
+ const buf = createTCPHeader(COMMANDS.CMD_ACK_OK, this.sessionId, USHRT_MAX - 1, Buffer.from([]));
1980
+ this.socket.write(buf);
1981
+ }
1982
+ catch (e) {
1983
+ throw new ZkError(e, COMMANDS.CMD_ACK_OK, this.ip);
1984
+ }
1985
+ }
1986
+ async cancelCapture() {
1987
+ try {
1988
+ const reply = await this.executeCmd(COMMANDS.CMD_CANCELCAPTURE, '');
1989
+ return !!reply;
1990
+ }
1991
+ catch (e) {
1992
+ throw new ZkError(e, COMMANDS.CMD_CANCELCAPTURE, this.ip);
1993
+ }
1994
+ }
1995
+ async verifyUser(uid) {
1996
+ try {
1997
+ let command_string = '';
1998
+ if (uid) {
1999
+ command_string = Buffer.alloc(4);
2000
+ command_string.writeUInt32LE(uid, 0);
2001
+ }
2002
+ const reply = await this.executeCmd(COMMANDS.CMD_STARTVERIFY, command_string);
2003
+ if (this.verbose)
2004
+ console.log(reply.readUInt16LE(0));
2005
+ return !!reply;
2006
+ }
2007
+ catch (error) {
2008
+ console.error(error);
2009
+ }
2010
+ }
2011
+ async restartDevice() {
2012
+ try {
2013
+ await this.executeCmd(COMMANDS.CMD_RESTART, '');
2014
+ }
2015
+ catch (e) {
2016
+ throw new ZkError(e, COMMANDS.CMD_RESTART, this.ip);
2017
+ }
2018
+ }
2019
+ }
2020
+
2021
+ class ZUDP {
2022
+ ip;
2023
+ port;
2024
+ timeout;
2025
+ socket;
2026
+ sessionId;
2027
+ replyId;
2028
+ inport;
2029
+ comm_key;
2030
+ constructor(ip, port, timeout, inport, comm_key = 0) {
2031
+ this.ip = ip;
2032
+ this.port = port;
2033
+ this.timeout = timeout;
2034
+ this.socket = null;
2035
+ this.sessionId = null;
2036
+ this.replyId = 0;
2037
+ this.inport = inport;
2038
+ this.comm_key = comm_key;
2039
+ }
2040
+ createSocket(cbError, cbClose) {
2041
+ return new Promise((resolve, reject) => {
2042
+ this.socket = dgram__namespace.createSocket('udp4');
2043
+ this.socket.setMaxListeners(Infinity);
2044
+ this.socket.once('error', (err) => {
2045
+ this.socket = null;
2046
+ reject(err);
2047
+ if (cbError)
2048
+ cbError(err);
2049
+ });
2050
+ this.socket.once('close', () => {
2051
+ this.socket = null;
2052
+ if (cbClose)
2053
+ cbClose('udp');
2054
+ });
2055
+ this.socket.once('listening', () => {
2056
+ resolve(this.socket);
2057
+ });
2058
+ try {
2059
+ this.socket.bind(this.inport);
2060
+ }
2061
+ catch (err) {
2062
+ this.socket = null;
2063
+ reject(err);
2064
+ if (cbError)
2065
+ cbError(err);
2066
+ }
2067
+ });
2068
+ }
2069
+ async connect() {
2070
+ try {
2071
+ let reply = await this.executeCmd(COMMANDS.CMD_CONNECT, '');
2072
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_OK) {
2073
+ return true;
2074
+ }
2075
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_UNAUTH) {
2076
+ const hashedCommkey = authKey(this.comm_key, this.sessionId);
2077
+ reply = await this.executeCmd(COMMANDS.CMD_AUTH, hashedCommkey);
2078
+ if (reply.readUInt16LE(0) === COMMANDS.CMD_ACK_OK) {
2079
+ return true;
2080
+ }
2081
+ else {
2082
+ throw new Error("Authentication error");
2083
+ }
2084
+ }
2085
+ else {
2086
+ throw new Error('NO_REPLY_ON_CMD_CONNECT');
2087
+ }
2088
+ }
2089
+ catch (err) {
2090
+ console.error('Error in connect method:', err);
2091
+ throw err;
2092
+ }
2093
+ }
2094
+ async closeSocket() {
2095
+ return new Promise((resolve, reject) => {
2096
+ if (!this.socket) {
2097
+ resolve(true);
2098
+ return;
2099
+ }
2100
+ const timeout = 2000;
2101
+ const timer = setTimeout(() => {
2102
+ console.warn('Socket close timeout');
2103
+ resolve(true);
2104
+ }, timeout);
2105
+ this.socket.removeAllListeners('message');
2106
+ // @ts-ignore
2107
+ this.socket.close((err) => {
2108
+ clearTimeout(timer);
2109
+ if (err) {
2110
+ console.error('Error closing socket:', err);
2111
+ reject(err);
2112
+ }
2113
+ else {
2114
+ resolve(true);
2115
+ }
2116
+ this.socket = null;
2117
+ });
2118
+ });
2119
+ }
2120
+ writeMessage(msg, connect) {
2121
+ return new Promise((resolve, reject) => {
2122
+ if (!this.socket) {
2123
+ reject(new Error('Socket not initialized'));
2124
+ return;
2125
+ }
2126
+ let sendTimeoutId;
2127
+ const onMessage = (data) => {
2128
+ clearTimeout(sendTimeoutId);
2129
+ this.socket.removeListener('message', onMessage);
2130
+ resolve(data);
2131
+ };
2132
+ this.socket.once('message', onMessage);
2133
+ this.socket.send(msg, 0, msg.length, this.port, this.ip, (err) => {
2134
+ if (err) {
2135
+ this.socket.removeListener('message', onMessage);
2136
+ reject(err);
2137
+ return;
2138
+ }
2139
+ if (this.timeout) {
2140
+ sendTimeoutId = setTimeout(() => {
2141
+ this.socket.removeListener('message', onMessage);
2142
+ reject(new Error('TIMEOUT_ON_WRITING_MESSAGE'));
2143
+ }, connect ? 2000 : this.timeout);
2144
+ }
2145
+ });
2146
+ });
2147
+ }
2148
+ requestData(msg) {
2149
+ return new Promise((resolve, reject) => {
2150
+ if (!this.socket) {
2151
+ reject(new Error('Socket not initialized'));
2152
+ return;
2153
+ }
2154
+ let sendTimeoutId;
2155
+ let responseTimeoutId;
2156
+ const handleOnData = (data) => {
2157
+ if (checkNotEventUDP(data))
2158
+ return;
2159
+ clearTimeout(sendTimeoutId);
2160
+ clearTimeout(responseTimeoutId);
2161
+ this.socket.removeListener('message', handleOnData);
2162
+ resolve(data);
2163
+ };
2164
+ const onReceiveTimeout = () => {
2165
+ this.socket.removeListener('message', handleOnData);
2166
+ reject(new Error('TIMEOUT_ON_RECEIVING_REQUEST_DATA'));
2167
+ };
2168
+ this.socket.on('message', handleOnData);
2169
+ this.socket.send(msg, 0, msg.length, this.port, this.ip, (err) => {
2170
+ if (err) {
2171
+ this.socket.removeListener('message', handleOnData);
2172
+ reject(err);
2173
+ return;
2174
+ }
2175
+ responseTimeoutId = setTimeout(onReceiveTimeout, this.timeout);
2176
+ });
2177
+ sendTimeoutId = setTimeout(() => {
2178
+ this.socket.removeListener('message', handleOnData);
2179
+ reject(new Error('TIMEOUT_IN_RECEIVING_RESPONSE_AFTER_REQUESTING_DATA'));
2180
+ }, this.timeout);
2181
+ });
2182
+ }
2183
+ async executeCmd(command, data) {
2184
+ try {
2185
+ if (command === COMMANDS.CMD_CONNECT) {
2186
+ this.sessionId = 0;
2187
+ this.replyId = 0;
2188
+ }
2189
+ else {
2190
+ this.replyId++;
2191
+ }
2192
+ const buf = createUDPHeader(command, this.sessionId, this.replyId, data);
2193
+ const reply = await this.writeMessage(buf, command === COMMANDS.CMD_CONNECT || command === COMMANDS.CMD_EXIT);
2194
+ if (reply && reply.length > 0) {
2195
+ if (command === COMMANDS.CMD_CONNECT) {
2196
+ this.sessionId = reply.readUInt16LE(4);
2197
+ }
2198
+ }
2199
+ return reply;
2200
+ }
2201
+ catch (err) {
2202
+ console.error(`Error executing command ${command}:`, err);
2203
+ throw err;
2204
+ }
2205
+ }
2206
+ async sendChunkRequest(start, size) {
2207
+ this.replyId++;
2208
+ const reqData = Buffer.alloc(8);
2209
+ reqData.writeUInt32LE(start, 0);
2210
+ reqData.writeUInt32LE(size, 4);
2211
+ const buf = createUDPHeader(COMMANDS.CMD_DATA_RDY, this.sessionId, this.replyId, reqData);
2212
+ try {
2213
+ await new Promise((resolve, reject) => {
2214
+ this.socket.send(buf, 0, buf.length, this.port, this.ip, (err) => {
2215
+ if (err) {
2216
+ log(`[UDP][SEND_CHUNK_REQUEST] Error sending chunk request: ${err.message}`);
2217
+ reject(err);
2218
+ }
2219
+ else {
2220
+ resolve();
2221
+ }
2222
+ });
2223
+ });
2224
+ }
2225
+ catch (error) {
2226
+ log(`[UDP][SEND_CHUNK_REQUEST] Exception: ${error.message}`);
2227
+ throw error;
2228
+ }
2229
+ }
2230
+ async readWithBuffer(reqData, cb = null) {
2231
+ this.replyId++;
2232
+ const buf = createUDPHeader(COMMANDS.CMD_DATA_WRRQ, this.sessionId, this.replyId, reqData);
2233
+ try {
2234
+ const reply = await this.requestData(buf);
2235
+ const header = decodeUDPHeader(reply.subarray(0, 8));
2236
+ switch (header.commandId) {
2237
+ case COMMANDS.CMD_DATA:
2238
+ return { data: reply.subarray(8), err: null };
2239
+ case COMMANDS.CMD_ACK_OK:
2240
+ case COMMANDS.CMD_PREPARE_DATA:
2241
+ return await this.handleChunkedData(reply, header.commandId, cb);
2242
+ default:
2243
+ throw new Error('ERROR_IN_UNHANDLE_CMD ' + exportErrorMessage(header.commandId));
2244
+ }
2245
+ }
2246
+ catch (err) {
2247
+ return { data: null, err: err };
2248
+ }
2249
+ }
2250
+ async handleChunkedData(reply, commandId, cb) {
2251
+ return new Promise((resolve) => {
2252
+ const recvData = reply.subarray(8);
2253
+ const size = recvData.readUIntLE(1, 4);
2254
+ let totalBuffer = Buffer.from([]);
2255
+ const timeout = 3000;
2256
+ let timer = setTimeout(() => {
2257
+ this.socket.removeListener('message', handleOnData);
2258
+ resolve({ data: null, err: new Error('TIMEOUT WHEN RECEIVING PACKET') });
2259
+ }, timeout);
2260
+ const internalCallback = (replyData, err = null) => {
2261
+ this.socket.removeListener('message', handleOnData);
2262
+ clearTimeout(timer);
2263
+ resolve({ data: err ? null : replyData, err });
2264
+ };
2265
+ const handleOnData = (reply) => {
2266
+ if (checkNotEventUDP(reply))
2267
+ return;
2268
+ clearTimeout(timer);
2269
+ timer = setTimeout(() => {
2270
+ internalCallback(totalBuffer, new Error(`TIMEOUT !! ${(size - totalBuffer.length) / size} % REMAIN !`));
2271
+ }, timeout);
2272
+ const header = decodeUDPHeader(reply);
2273
+ switch (header.commandId) {
2274
+ case COMMANDS.CMD_PREPARE_DATA:
2275
+ break;
2276
+ case COMMANDS.CMD_DATA:
2277
+ totalBuffer = Buffer.concat([totalBuffer, reply.subarray(8)]);
2278
+ cb && cb(totalBuffer.length, size);
2279
+ break;
2280
+ case COMMANDS.CMD_ACK_OK:
2281
+ if (totalBuffer.length === size) {
2282
+ internalCallback(totalBuffer);
2283
+ }
2284
+ break;
2285
+ default:
2286
+ internalCallback(Buffer.from([]), new Error('ERROR_IN_UNHANDLE_CMD ' + exportErrorMessage(header.commandId)));
2287
+ }
2288
+ };
2289
+ this.socket.on('message', handleOnData);
2290
+ const chunkCount = Math.ceil(size / MAX_CHUNK);
2291
+ for (let i = 0; i < chunkCount; i++) {
2292
+ const start = i * MAX_CHUNK;
2293
+ const chunkSize = (i === chunkCount - 1) ? size % MAX_CHUNK : MAX_CHUNK;
2294
+ this.sendChunkRequest(start, chunkSize).catch(err => {
2295
+ internalCallback(Buffer.from([]), err);
2296
+ });
2297
+ }
2298
+ });
2299
+ }
2300
+ async getUsers() {
2301
+ try {
2302
+ if (this.socket) {
2303
+ await this.freeData();
2304
+ }
2305
+ const data = await this.readWithBuffer(REQUEST_DATA.GET_USERS);
2306
+ if (this.socket) {
2307
+ await this.freeData();
2308
+ }
2309
+ const USER_PACKET_SIZE = 28;
2310
+ let userData = data.data?.subarray(4) || Buffer.from([]);
2311
+ const users = [];
2312
+ while (userData.length >= USER_PACKET_SIZE) {
2313
+ const user = decodeUserData28(userData.subarray(0, USER_PACKET_SIZE));
2314
+ users.push(user);
2315
+ userData = userData.subarray(USER_PACKET_SIZE);
2316
+ }
2317
+ return { data: users };
2318
+ }
2319
+ catch (err) {
2320
+ throw new Error(err.message);
2321
+ }
2322
+ }
2323
+ async getAttendances(callbackInProcess) {
2324
+ try {
2325
+ if (this.socket) {
2326
+ await this.freeData();
2327
+ }
2328
+ const data = await this.readWithBuffer(REQUEST_DATA.GET_ATTENDANCE_LOGS);
2329
+ if (this.socket) {
2330
+ await this.freeData();
2331
+ }
2332
+ const RECORD_PACKET_SIZE = 16;
2333
+ let recordData = data.data?.subarray(4) || Buffer.from([]);
2334
+ const records = [];
2335
+ while (recordData.length >= RECORD_PACKET_SIZE) {
2336
+ const record = decodeRecordData16(recordData.subarray(0, RECORD_PACKET_SIZE));
2337
+ records.push({ ...record, ip: this.ip });
2338
+ recordData = recordData.subarray(RECORD_PACKET_SIZE);
2339
+ }
2340
+ return { data: records, err: data.err };
2341
+ }
2342
+ catch (err) {
2343
+ return { data: [], err: err };
2344
+ }
2345
+ }
2346
+ async freeData() {
2347
+ try {
2348
+ const resp = await this.executeCmd(COMMANDS.CMD_FREE_DATA, Buffer.alloc(0));
2349
+ return !!resp;
2350
+ }
2351
+ catch (err) {
2352
+ console.error('Error freeing data:', err);
2353
+ throw err;
2354
+ }
2355
+ }
2356
+ async getInfo() {
2357
+ try {
2358
+ const data = await this.executeCmd(COMMANDS.CMD_GET_FREE_SIZES, Buffer.alloc(0));
2359
+ return {
2360
+ userCounts: data.readUIntLE(24, 4),
2361
+ logCounts: data.readUIntLE(40, 4),
2362
+ logCapacity: data.readUIntLE(72, 4)
2363
+ };
2364
+ }
2365
+ catch (err) {
2366
+ console.error('Error retrieving info:', err);
2367
+ throw err;
2368
+ }
2369
+ }
2370
+ async getTime() {
2371
+ try {
2372
+ const response = await this.executeCmd(COMMANDS.CMD_GET_TIME, Buffer.alloc(0));
2373
+ const timeValue = response.readUInt32LE(8);
2374
+ return timeParser.decode(timeValue);
2375
+ }
2376
+ catch (err) {
2377
+ console.error('Error retrieving time:', err);
2378
+ throw err;
2379
+ }
2380
+ }
2381
+ async setTime(tm) {
2382
+ try {
2383
+ const commandBuffer = Buffer.alloc(32);
2384
+ commandBuffer.writeUInt32LE(timeParser.encode(new Date(tm)), 0);
2385
+ await this.executeCmd(COMMANDS.CMD_SET_TIME, commandBuffer);
2386
+ return true;
2387
+ }
2388
+ catch (err) {
2389
+ console.error('Error setting time:', err);
2390
+ throw err;
2391
+ }
2392
+ }
2393
+ async clearAttendanceLog() {
2394
+ try {
2395
+ return await this.executeCmd(COMMANDS.CMD_CLEAR_ATTLOG, Buffer.alloc(0));
2396
+ }
2397
+ catch (err) {
2398
+ console.error('Error clearing attendance log:', err);
2399
+ throw err;
2400
+ }
2401
+ }
2402
+ async clearData() {
2403
+ try {
2404
+ return await this.executeCmd(COMMANDS.CMD_CLEAR_DATA, Buffer.alloc(0));
2405
+ }
2406
+ catch (err) {
2407
+ console.error('Error clearing data:', err);
2408
+ throw err;
2409
+ }
2410
+ }
2411
+ async disableDevice() {
2412
+ try {
2413
+ const resp = await this.executeCmd(COMMANDS.CMD_DISABLEDEVICE, REQUEST_DATA.DISABLE_DEVICE);
2414
+ return !!resp;
2415
+ }
2416
+ catch (err) {
2417
+ console.error('Error disabling device:', err);
2418
+ throw err;
2419
+ }
2420
+ }
2421
+ async enableDevice() {
2422
+ try {
2423
+ const resp = await this.executeCmd(COMMANDS.CMD_ENABLEDEVICE, Buffer.alloc(0));
2424
+ return !!resp;
2425
+ }
2426
+ catch (err) {
2427
+ console.error('Error enabling device:', err);
2428
+ throw err;
2429
+ }
2430
+ }
2431
+ async disconnect() {
2432
+ try {
2433
+ await this.executeCmd(COMMANDS.CMD_EXIT, Buffer.alloc(0));
2434
+ }
2435
+ catch (err) {
2436
+ console.error('Error executing disconnect command:', err);
2437
+ }
2438
+ try {
2439
+ await this.closeSocket();
2440
+ }
2441
+ catch (err) {
2442
+ console.error('Error closing the socket:', err);
2443
+ }
2444
+ }
2445
+ async getRealTimeLogs(cb = () => { }) {
2446
+ this.replyId++;
2447
+ const buf = createUDPHeader(COMMANDS.CMD_REG_EVENT, this.sessionId, this.replyId, REQUEST_DATA.GET_REAL_TIME_EVENT);
2448
+ try {
2449
+ this.socket.send(buf, 0, buf.length, this.port, this.ip, (err) => {
2450
+ if (err) {
2451
+ console.error('Error sending UDP message:', err);
2452
+ return;
2453
+ }
2454
+ console.log('UDP message sent successfully');
2455
+ });
2456
+ }
2457
+ catch (err) {
2458
+ console.error('Error during send operation:', err);
2459
+ return;
2460
+ }
2461
+ const handleMessage = (data) => {
2462
+ if (!checkNotEventUDP(data))
2463
+ return;
2464
+ if (data.length === 18) {
2465
+ cb(decodeRecordRealTimeLog18(data));
2466
+ }
2467
+ };
2468
+ if (this.socket.listenerCount('message') === 0) {
2469
+ this.socket.on('message', handleMessage);
2470
+ }
2471
+ else {
2472
+ console.warn('Multiple message listeners detected. Ensure only one listener is attached.');
2473
+ }
2474
+ }
2475
+ }
2476
+
2477
+ class Zklib {
2478
+ set connectionType(value) {
2479
+ this._connectionType = value;
2480
+ }
2481
+ _connectionType = null;
2482
+ ztcp;
2483
+ zudp;
2484
+ interval = null;
2485
+ timer = null;
2486
+ isBusy = false;
2487
+ ip;
2488
+ comm_key;
2489
+ get connectionType() {
2490
+ return this._connectionType;
2491
+ }
2492
+ /**
2493
+ * Creates a new Zkteco device connection instance
2494
+ * @param ip IP address of device
2495
+ * @param port Port number of device
2496
+ * @param timeout Connection timeout in milliseconds
2497
+ * @param inport Required only for UDP connection (default: 10000)
2498
+ * @param comm_key Communication key of device (default: 0)
2499
+ * @param verbose Console log some data
2500
+ */
2501
+ constructor(ip, port = 4370, timeout = 5000, inport = 10000, comm_key = 0, verbose = false) {
2502
+ this.ip = ip;
2503
+ this.comm_key = comm_key;
2504
+ this.ztcp = new ZTCP(ip, port, timeout, comm_key, verbose);
2505
+ this.zudp = new ZUDP(ip, port, timeout, inport);
2506
+ }
2507
+ async functionWrapper(tcpCallback, udpCallback, command) {
2508
+ try {
2509
+ switch (this._connectionType) {
2510
+ case 'tcp':
2511
+ if (this.ztcp && this.ztcp.socket) {
2512
+ return await tcpCallback();
2513
+ }
2514
+ else {
2515
+ throw new ZkError(new Error(`TCP socket isn't connected!`), `[TCP] ${command}`, this.ip);
2516
+ }
2517
+ case 'udp':
2518
+ if (this.zudp && this.zudp.socket) {
2519
+ return await udpCallback();
2520
+ }
2521
+ else {
2522
+ throw new ZkError(new Error(`UDP socket isn't connected!`), `[UDP] ${command}`, this.ip);
2523
+ }
2524
+ default:
2525
+ throw new ZkError(new Error(`Unsupported connection type or socket isn't connected!`), '', this.ip);
2526
+ }
2527
+ }
2528
+ catch (err) {
2529
+ throw new ZkError(err, `[${this._connectionType?.toUpperCase()}] ${command}`, this.ip);
2530
+ }
2531
+ }
2532
+ async createSocket(cbErr, cbClose) {
2533
+ try {
2534
+ if (this.ztcp.socket) {
2535
+ try {
2536
+ await this.ztcp.connect();
2537
+ console.log('TCP reconnection successful');
2538
+ this._connectionType = 'tcp';
2539
+ return true;
2540
+ }
2541
+ catch (err) {
2542
+ throw new ZkError(err, 'TCP CONNECT', this.ip);
2543
+ }
2544
+ }
2545
+ else {
2546
+ try {
2547
+ await this.ztcp.createSocket(cbErr, cbClose);
2548
+ await this.ztcp.connect();
2549
+ console.log('TCP connection successful');
2550
+ this._connectionType = 'tcp';
2551
+ return true;
2552
+ }
2553
+ catch (err) {
2554
+ throw new ZkError(err, 'TCP CONNECT', this.ip);
2555
+ }
2556
+ }
2557
+ }
2558
+ catch (err) {
2559
+ try {
2560
+ if (this.ztcp.socket)
2561
+ await this.ztcp.disconnect();
2562
+ }
2563
+ catch (disconnectErr) {
2564
+ console.error('Error disconnecting TCP:', disconnectErr);
2565
+ }
2566
+ if (err.code !== ERROR_TYPES.ECONNREFUSED) {
2567
+ throw new ZkError(err, 'TCP CONNECT', this.ip);
2568
+ }
2569
+ try {
2570
+ if (!this.zudp.socket) {
2571
+ await this.zudp.createSocket(cbErr, cbClose);
2572
+ }
2573
+ await this.zudp.connect();
2574
+ console.log('UDP connection successful');
2575
+ this._connectionType = 'udp';
2576
+ return true;
2577
+ }
2578
+ catch (err) {
2579
+ if (err.message !== 'EADDRINUSE') {
2580
+ this._connectionType = null;
2581
+ try {
2582
+ await this.zudp.disconnect();
2583
+ }
2584
+ catch (disconnectErr) {
2585
+ console.error('Error disconnecting UDP:', disconnectErr);
2586
+ }
2587
+ throw new ZkError(err, 'UDP CONNECT', this.ip);
2588
+ }
2589
+ this._connectionType = 'udp';
2590
+ return true;
2591
+ }
2592
+ }
2593
+ }
2594
+ async getUsers() {
2595
+ return this.functionWrapper(() => this.ztcp.getUsers(), () => this.zudp.getUsers(), 'GET_USERS');
2596
+ }
2597
+ async getTime() {
2598
+ return this.functionWrapper(() => this.ztcp.getTime(), () => this.zudp.getTime(), 'GET_TIME');
2599
+ }
2600
+ async setTime(t) {
2601
+ return this.functionWrapper(() => this.ztcp.setTime(t), () => this.zudp.setTime(t), 'SET_TIME');
2602
+ }
2603
+ async voiceTest() {
2604
+ return this.functionWrapper(() => this.ztcp.voiceTest(), async () => { throw new Error('UDP voice test not supported'); }, 'VOICE_TEST');
2605
+ }
2606
+ async getProductTime() {
2607
+ return this.functionWrapper(() => this.ztcp.getProductTime(), async () => { throw new Error('UDP get product time not supported'); }, 'GET_PRODUCT_TIME');
2608
+ }
2609
+ async getVendor() {
2610
+ return this.functionWrapper(() => this.ztcp.getVendor(), async () => { throw new Error('UDP get vendor not supported'); }, 'GET_VENDOR');
2611
+ }
2612
+ async getMacAddress() {
2613
+ return this.functionWrapper(() => this.ztcp.getMacAddress(), async () => { throw new Error('UDP get MAC address not supported'); }, 'GET_MAC_ADDRESS');
2614
+ }
2615
+ async getSerialNumber() {
2616
+ return this.functionWrapper(() => this.ztcp.getSerialNumber(), async () => { throw new Error('UDP get serial number not supported'); }, 'GET_SERIAL_NUMBER');
2617
+ }
2618
+ async getDeviceVersion() {
2619
+ return this.functionWrapper(() => this.ztcp.getDeviceVersion(), async () => { throw new Error('UDP get device version not supported'); }, 'GET_DEVICE_VERSION');
2620
+ }
2621
+ async getDeviceName() {
2622
+ return this.functionWrapper(() => this.ztcp.getDeviceName(), async () => { throw new Error('UDP get device name not supported'); }, 'GET_DEVICE_NAME');
2623
+ }
2624
+ async getPlatform() {
2625
+ return this.functionWrapper(() => this.ztcp.getPlatform(), async () => { throw new Error('UDP get platform not supported'); }, 'GET_PLATFORM');
2626
+ }
2627
+ async getOS() {
2628
+ return this.functionWrapper(() => this.ztcp.getOS(), async () => { throw new Error('UDP get OS not supported'); }, 'GET_OS');
2629
+ }
2630
+ async getWorkCode() {
2631
+ return this.functionWrapper(() => this.ztcp.getWorkCode(), async () => { throw new Error('UDP get work code not supported'); }, 'GET_WORK_CODE');
2632
+ }
2633
+ async getPIN() {
2634
+ return this.functionWrapper(() => this.ztcp.getPIN(), async () => { throw new Error('UDP get PIN not supported'); }, 'GET_PIN');
2635
+ }
2636
+ async getFaceOn() {
2637
+ return this.functionWrapper(() => this.ztcp.getFaceOn(), async () => { throw new Error('UDP get face on not supported'); }, 'GET_FACE_ON');
2638
+ }
2639
+ async getSSR() {
2640
+ return this.functionWrapper(() => this.ztcp.getSSR(), async () => { throw new Error('UDP get SSR not supported'); }, 'GET_SSR');
2641
+ }
2642
+ async getFirmware() {
2643
+ return this.functionWrapper(() => this.ztcp.getFirmware(), async () => { throw new Error('UDP get firmware not supported'); }, 'GET_FIRMWARE');
2644
+ }
2645
+ async setUser(uid, userid, name, password, role = 0, cardno = 0) {
2646
+ return this.functionWrapper(() => this.ztcp.setUser(uid, userid, name, password, role, cardno), async () => { throw new Error('UDP set user not supported'); }, 'SET_USER');
2647
+ }
2648
+ async deleteUser(uid) {
2649
+ return this.functionWrapper(() => this.ztcp.deleteUser(uid), async () => { throw new Error('UDP delete user not supported'); }, 'DELETE_USER');
2650
+ }
2651
+ async getAttendanceSize() {
2652
+ return this.functionWrapper(() => this.ztcp.getAttendanceSize(), async () => { throw new Error('UDP get attendance size not supported'); }, 'GET_ATTENDANCE_SIZE');
2653
+ }
2654
+ async getAttendances(cb) {
2655
+ return this.functionWrapper(() => this.ztcp.getAttendances(cb), () => this.zudp.getAttendances(cb), 'GET_ATTENDANCES');
2656
+ }
2657
+ async getRealTimeLogs(cb) {
2658
+ return this.functionWrapper(() => this.ztcp.getRealTimeLogs(cb), () => this.zudp.getRealTimeLogs(cb), 'GET_REAL_TIME_LOGS');
2659
+ }
2660
+ async getTemplates() {
2661
+ return this.functionWrapper(() => this.ztcp.getTemplates(), async () => { throw new Error('UDP get templates not supported'); }, 'GET_TEMPLATES');
2662
+ }
2663
+ async saveUserTemplate(user, fingers = []) {
2664
+ return await this.functionWrapper(async () => await this.ztcp.saveUserTemplate(user, fingers), async () => { throw new Error('UDP save user template not supported'); }, 'SAVE_USER_TEMPLATE');
2665
+ }
2666
+ async deleteFinger(uid, fid) {
2667
+ if (fid > 9 || 0 > fid)
2668
+ throw new Error("fid params out of index");
2669
+ if (uid > 3000 || uid < 1)
2670
+ throw new Error("fid params out of index");
2671
+ return this.functionWrapper(() => this.ztcp.deleteFinger(uid, fid), async () => { throw new Error('UDP delete finger not supported'); }, 'DELETE_FINGER');
2672
+ }
2673
+ async enrollUser(uid, temp_id, user_id) {
2674
+ if (temp_id < 0 || temp_id > 9)
2675
+ throw new Error("temp_id out of range 0-9");
2676
+ if (uid < 1 || uid > 3000)
2677
+ throw new Error("uid out of range 1-3000");
2678
+ return this.functionWrapper(() => this.ztcp.enrollUser(uid, temp_id, user_id), async () => { throw new Error('UDP enroll user not supported'); }, 'ENROLL_USER');
2679
+ }
2680
+ async verifyUser(uid) {
2681
+ return this.functionWrapper(() => this.ztcp.verifyUser(uid), async () => { throw new Error('UDP verify user not supported'); }, 'VERIFY_USER');
2682
+ }
2683
+ async restartDevice() {
2684
+ return this.functionWrapper(() => this.ztcp.restartDevice(), async () => { throw new Error('UDP restart device not supported'); }, 'RESTART_DEVICE');
2685
+ }
2686
+ async getSizes() {
2687
+ return this.functionWrapper(() => this.ztcp.getSizes(), () => this.zudp.getInfo(), 'GET_SIZES');
2688
+ }
2689
+ async disconnect() {
2690
+ return this.functionWrapper(() => this.ztcp.disconnect(), () => this.zudp.disconnect(), 'DISCONNECT');
2691
+ }
2692
+ async freeData() {
2693
+ return this.functionWrapper(() => this.ztcp.freeData(), () => this.zudp.freeData(), 'FREE_DATA');
2694
+ }
2695
+ async disableDevice() {
2696
+ return this.functionWrapper(() => this.ztcp.disableDevice(), () => this.zudp.disableDevice(), 'DISABLE_DEVICE');
2697
+ }
2698
+ async enableDevice() {
2699
+ return this.functionWrapper(() => this.ztcp.enableDevice(), () => this.zudp.enableDevice(), 'ENABLE_DEVICE');
2700
+ }
2701
+ async getInfo() {
2702
+ return this.functionWrapper(() => this.ztcp.getInfo(), () => this.zudp.getInfo(), 'GET_INFO');
2703
+ }
2704
+ async clearAttendanceLog() {
2705
+ return this.functionWrapper(() => this.ztcp.clearAttendanceLog(), () => this.zudp.clearAttendanceLog(), 'CLEAR_ATTENDANCE_LOG');
2706
+ }
2707
+ async clearData() {
2708
+ return this.functionWrapper(() => this.ztcp.clearData(), () => this.zudp.clearData(), 'CLEAR_DATA');
2709
+ }
2710
+ async executeCmd(command, data = '') {
2711
+ return this.functionWrapper(() => this.ztcp.executeCmd(command, data), () => this.zudp.executeCmd(command, data), 'EXECUTE_CMD');
2712
+ }
2713
+ }
2714
+
2715
+ module.exports = Zklib;