UNPKG

zklib-ts

Version:

Unofficial zkteco library allows Node.js developers to easily interface with ZK BioMetric Fingerprint Attendance Devices

532 lines (524 loc) 19 kB
'use strict'; var utils = require('./helper/utils'); var command = require('./helper/command'); var fs = require('fs'); var dgram = require('node:dgram'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var dgram__namespace = /*#__PURE__*/_interopNamespaceDefault(dgram); const parseCurrentTime = () => { const currentTime = new Date(); return { year: currentTime.getFullYear(), month: currentTime.getMonth() + 1, day: currentTime.getDate(), hour: currentTime.getHours(), second: currentTime.getSeconds() }; }; const log = (text) => { const currentTime = parseCurrentTime(); const fileName = `${currentTime.day}`.padStart(2, '0') + `${currentTime.month}`.padStart(2, '0') + `${currentTime.year}.err.log`; const logMessage = `\n [${currentTime.hour}:${currentTime.second}] ${text}`; fs.appendFile(fileName, logMessage, () => { }); }; /** * * @param {number} time */ const decode = time => { const second = time % 60; time = (time - second) / 60; const minute = time % 60; time = (time - minute) / 60; const hour = time % 24; time = (time - hour) / 24; const day = time % 31 + 1; time = (time - (day - 1)) / 31; const month = time % 12; time = (time - month) / 12; const year = time + 2000; return new Date(year, month, day, hour, minute, second); }; /** * * @param {Date} date */ const encode = date => { return (((date.getFullYear() % 100) * 12 * 31 + date.getMonth() * 31 + date.getDate() - 1) * (24 * 60 * 60) + (date.getHours() * 60 + date.getMinutes()) * 60 + date.getSeconds()); }; var timeParser = { encode, decode }; class ZUDP { ip; port; timeout; socket; sessionId; replyId; inport; comm_key; constructor(ip, port, timeout, inport, comm_key = 0) { this.ip = ip; this.port = port; this.timeout = timeout; this.socket = null; this.sessionId = null; this.replyId = 0; this.inport = inport; this.comm_key = comm_key; } createSocket(cbError, cbClose) { return new Promise((resolve, reject) => { this.socket = dgram__namespace.createSocket('udp4'); this.socket.setMaxListeners(Infinity); this.socket.once('error', (err) => { this.socket = null; reject(err); if (cbError) cbError(err); }); this.socket.once('close', () => { this.socket = null; if (cbClose) cbClose('udp'); }); this.socket.once('listening', () => { resolve(this.socket); }); try { this.socket.bind(this.inport); } catch (err) { this.socket = null; reject(err); if (cbError) cbError(err); } }); } async connect() { try { let reply = await this.executeCmd(command.COMMANDS.CMD_CONNECT, ''); if (reply.readUInt16LE(0) === command.COMMANDS.CMD_ACK_OK) { return true; } if (reply.readUInt16LE(0) === command.COMMANDS.CMD_ACK_UNAUTH) { const hashedCommkey = utils.authKey(this.comm_key, this.sessionId); reply = await this.executeCmd(command.COMMANDS.CMD_AUTH, hashedCommkey); if (reply.readUInt16LE(0) === command.COMMANDS.CMD_ACK_OK) { return true; } else { throw new Error('Authentication error'); } } else { throw new Error('NO_REPLY_ON_CMD_CONNECT'); } } catch (err) { console.error('Error in connect method:', err); throw err; } } async closeSocket() { return new Promise((resolve, reject) => { if (!this.socket) { resolve(true); return; } const timeout = 2000; const timer = setTimeout(() => { console.warn('Socket close timeout'); resolve(true); }, timeout); this.socket.removeAllListeners('message'); // @ts-ignore this.socket.close((err) => { clearTimeout(timer); if (err) { console.error('Error closing socket:', err); reject(err); } else { resolve(true); } this.socket = null; }); }); } writeMessage(msg, connect) { return new Promise((resolve, reject) => { if (!this.socket) { reject(new Error('Socket not initialized')); return; } let sendTimeoutId; const onMessage = (data) => { clearTimeout(sendTimeoutId); this.socket.removeListener('message', onMessage); resolve(data); }; this.socket.once('message', onMessage); this.socket.send(msg, 0, msg.length, this.port, this.ip, (err) => { if (err) { this.socket.removeListener('message', onMessage); reject(err); return; } if (this.timeout) { sendTimeoutId = setTimeout(() => { this.socket.removeListener('message', onMessage); reject(new Error('TIMEOUT_ON_WRITING_MESSAGE')); }, connect ? 2000 : this.timeout); } }); }); } requestData(msg) { return new Promise((resolve, reject) => { if (!this.socket) { reject(new Error('Socket not initialized')); return; } let sendTimeoutId; let responseTimeoutId; const handleOnData = (data) => { if (utils.checkNotEventUDP(data)) return; clearTimeout(sendTimeoutId); clearTimeout(responseTimeoutId); this.socket.removeListener('message', handleOnData); resolve(data); }; const onReceiveTimeout = () => { this.socket.removeListener('message', handleOnData); reject(new Error('TIMEOUT_ON_RECEIVING_REQUEST_DATA')); }; this.socket.on('message', handleOnData); this.socket.send(msg, 0, msg.length, this.port, this.ip, (err) => { if (err) { this.socket.removeListener('message', handleOnData); reject(err); return; } responseTimeoutId = setTimeout(onReceiveTimeout, this.timeout); }); sendTimeoutId = setTimeout(() => { this.socket.removeListener('message', handleOnData); reject(new Error('TIMEOUT_IN_RECEIVING_RESPONSE_AFTER_REQUESTING_DATA')); }, this.timeout); }); } async executeCmd(command$1, data) { try { if (command$1 === command.COMMANDS.CMD_CONNECT) { this.sessionId = 0; this.replyId = 0; } else { this.replyId++; } const buf = utils.createUDPHeader(command$1, this.sessionId, this.replyId, data); const reply = await this.writeMessage(buf, command$1 === command.COMMANDS.CMD_CONNECT || command$1 === command.COMMANDS.CMD_EXIT); if (reply && reply.length > 0) { if (command$1 === command.COMMANDS.CMD_CONNECT) { this.sessionId = reply.readUInt16LE(4); } } return reply; } catch (err) { console.error(`Error executing command ${command$1}:`, err); throw err; } } async sendChunkRequest(start, size) { this.replyId++; const reqData = Buffer.alloc(8); reqData.writeUInt32LE(start, 0); reqData.writeUInt32LE(size, 4); const buf = utils.createUDPHeader(command.COMMANDS.CMD_DATA_RDY, this.sessionId, this.replyId, reqData); try { await new Promise((resolve, reject) => { this.socket.send(buf, 0, buf.length, this.port, this.ip, (err) => { if (err) { log(`[UDP][SEND_CHUNK_REQUEST] Error sending chunk request: ${err.message}`); reject(err); } else { resolve(); } }); }); } catch (error) { log(`[UDP][SEND_CHUNK_REQUEST] Exception: ${error.message}`); throw error; } } async readWithBuffer(reqData, cb = null) { this.replyId++; const buf = utils.createUDPHeader(command.COMMANDS.CMD_DATA_WRRQ, this.sessionId, this.replyId, reqData); try { const reply = await this.requestData(buf); const header = utils.decodeUDPHeader(reply.subarray(0, 8)); switch (header.commandId) { case command.COMMANDS.CMD_DATA: return { data: reply.subarray(8), err: null }; case command.COMMANDS.CMD_ACK_OK: case command.COMMANDS.CMD_PREPARE_DATA: return await this.handleChunkedData(reply, header.commandId, cb); default: throw new Error('ERROR_IN_UNHANDLE_CMD ' + utils.exportErrorMessage(header.commandId)); } } catch (err) { return { data: null, err: err }; } } async handleChunkedData(reply, commandId, cb) { return new Promise((resolve) => { const recvData = reply.subarray(8); const size = recvData.readUIntLE(1, 4); let totalBuffer = Buffer.from([]); const timeout = 3000; let timer = setTimeout(() => { this.socket.removeListener('message', handleOnData); resolve({ data: null, err: new Error('TIMEOUT WHEN RECEIVING PACKET') }); }, timeout); const internalCallback = (replyData, err = null) => { this.socket.removeListener('message', handleOnData); clearTimeout(timer); resolve({ data: err ? null : replyData, err }); }; const handleOnData = (reply) => { if (utils.checkNotEventUDP(reply)) return; clearTimeout(timer); timer = setTimeout(() => { internalCallback(totalBuffer, new Error(`TIMEOUT !! ${(size - totalBuffer.length) / size} % REMAIN !`)); }, timeout); const header = utils.decodeUDPHeader(reply); switch (header.commandId) { case command.COMMANDS.CMD_PREPARE_DATA: break; case command.COMMANDS.CMD_DATA: totalBuffer = Buffer.concat([totalBuffer, reply.subarray(8)]); cb && cb(totalBuffer.length, size); break; case command.COMMANDS.CMD_ACK_OK: if (totalBuffer.length === size) { internalCallback(totalBuffer); } break; default: internalCallback(Buffer.from([]), new Error('ERROR_IN_UNHANDLE_CMD ' + utils.exportErrorMessage(header.commandId))); } }; this.socket.on('message', handleOnData); const chunkCount = Math.ceil(size / command.Constants.MAX_CHUNK); for (let i = 0; i < chunkCount; i++) { const start = i * command.Constants.MAX_CHUNK; const chunkSize = (i === chunkCount - 1) ? size % command.Constants.MAX_CHUNK : command.Constants.MAX_CHUNK; this.sendChunkRequest(start, chunkSize).catch(err => { internalCallback(Buffer.from([]), err); }); } }); } async getUsers() { try { if (this.socket) { await this.freeData(); } const data = await this.readWithBuffer(command.REQUEST_DATA.GET_USERS); if (this.socket) { await this.freeData(); } const USER_PACKET_SIZE = 28; let userData = data.data?.subarray(4) || Buffer.from([]); const users = []; while (userData.length >= USER_PACKET_SIZE) { const user = utils.decodeUserData28(userData.subarray(0, USER_PACKET_SIZE)); users.push(user); userData = userData.subarray(USER_PACKET_SIZE); } return { data: users }; } catch (err) { throw new Error(err.message); } } async getAttendances(callbackInProcess) { try { if (this.socket) { await this.freeData(); } const data = await this.readWithBuffer(command.REQUEST_DATA.GET_ATTENDANCE_LOGS); if (this.socket) { await this.freeData(); } const RECORD_PACKET_SIZE = 16; let recordData = data.data?.subarray(4) || Buffer.from([]); const records = []; while (recordData.length >= RECORD_PACKET_SIZE) { const record = utils.decodeRecordData16(recordData.subarray(0, RECORD_PACKET_SIZE)); records.push({ ...record, ip: this.ip }); recordData = recordData.subarray(RECORD_PACKET_SIZE); } return { data: records, err: data.err }; } catch (err) { return { data: [], err: err }; } } async freeData() { try { const resp = await this.executeCmd(command.COMMANDS.CMD_FREE_DATA, Buffer.alloc(0)); return !!resp; } catch (err) { console.error('Error freeing data:', err); throw err; } } async getInfo() { try { const data = await this.executeCmd(command.COMMANDS.CMD_GET_FREE_SIZES, Buffer.alloc(0)); return { userCounts: data.readUIntLE(24, 4), logCounts: data.readUIntLE(40, 4), logCapacity: data.readUIntLE(72, 4) }; } catch (err) { console.error('Error retrieving info:', err); throw err; } } async getTime() { try { const response = await this.executeCmd(command.COMMANDS.CMD_GET_TIME, Buffer.alloc(0)); const timeValue = response.readUInt32LE(8); return timeParser.decode(timeValue); } catch (err) { console.error('Error retrieving time:', err); throw err; } } async setTime(tm) { try { const commandBuffer = Buffer.alloc(32); commandBuffer.writeUInt32LE(timeParser.encode(new Date(tm)), 0); await this.executeCmd(command.COMMANDS.CMD_SET_TIME, commandBuffer); return true; } catch (err) { console.error('Error setting time:', err); throw err; } } async clearAttendanceLog() { try { return !!await this.executeCmd(command.COMMANDS.CMD_CLEAR_ATTLOG, Buffer.alloc(0)); } catch (err) { console.error('Error clearing attendance log:', err); throw err; } } async clearData() { try { return !!await this.executeCmd(command.COMMANDS.CMD_CLEAR_DATA, Buffer.alloc(0)); } catch (err) { console.error('Error clearing data:', err); throw err; } } async disableDevice() { try { const resp = await this.executeCmd(command.COMMANDS.CMD_DISABLEDEVICE, command.REQUEST_DATA.DISABLE_DEVICE); return !!resp; } catch (err) { console.error('Error disabling device:', err); throw err; } } async enableDevice() { try { const resp = await this.executeCmd(command.COMMANDS.CMD_ENABLEDEVICE, Buffer.alloc(0)); return !!resp; } catch (err) { console.error('Error enabling device:', err); throw err; } } async disconnect() { try { await this.executeCmd(command.COMMANDS.CMD_EXIT, Buffer.alloc(0)); } catch (err) { console.error('Error executing disconnect command:', err); } try { await this.closeSocket(); } catch (err) { console.error('Error closing the socket:', err); } } async getRealTimeLogs(cb = () => { }) { this.replyId++; const buf = utils.createUDPHeader(command.COMMANDS.CMD_REG_EVENT, this.sessionId, this.replyId, command.REQUEST_DATA.GET_REAL_TIME_EVENT); try { this.socket.send(buf, 0, buf.length, this.port, this.ip, (err) => { if (err) { console.error('Error sending UDP message:', err); return; } console.log('UDP message sent successfully'); }); } catch (err) { console.error('Error during send operation:', err); return; } const handleMessage = (data) => { if (!utils.checkNotEventUDP(data)) return; if (data.length === 18) { cb(utils.decodeRecordRealTimeLog18(data)); } }; if (this.socket.listenerCount('message') === 0) { this.socket.on('message', handleMessage); } else { console.warn('Multiple message listeners detected. Ensure only one listener is attached.'); } } } exports.ZUDP = ZUDP;