Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

node-red-contrib-hikvision-ultimate

Package Overview
Dependencies
Maintainers
0
Versions
121
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-red-contrib-hikvision-ultimate - npm Package Compare versions

Comparing version 1.2.0 to 1.2.1

4

CHANGELOG.md

@@ -7,2 +7,6 @@ <p align="center"><img src='https://raw.githubusercontent.com/Supergiovane/node-red-contrib-hikvision-ultimate/master/img/logo.png' width="40%"></p>

<p>
<b>Version 1.2.1</b> September 2024<br/>
- AX Pro: fixed an issue where occasionally, when malformed multipart data is sent from the AX Pro, there would be no NAK.<br/>
</p>
<p>
<b>Version 1.2.0</b> September 2024<br/>

@@ -9,0 +13,0 @@ - RAW Event Node: on the third pin, outputs the IMAGE related to the event (if any). The node will now filter the heartbeat signals.<br/>

263

nodes/AXPro-config.js
const { default: fetch } = require('node-fetch')
const sha256 = require('./utils/Sha256').sha256
const { XMLParser, XMLBuilder } = require("fast-xml-parser");
const Dicer = require('dicer');

@@ -9,5 +10,5 @@ module.exports = (RED) => {

const AbortController = require('abort-controller');
const readableStr = require('stream').Readable;
const https = require('https');
function Hikvisionconfig(config) {

@@ -34,3 +35,2 @@ RED.nodes.createNode(this, config)

var controller = null; // AbortController
var oReadable = new readableStr();
node.setAllClientsStatus = ({ fill, shape, text }) => {

@@ -67,3 +67,3 @@ function nextStatus(oClient) {

if (node.isConnected) {
if (node.errorDescription === "") node.errorDescription = "Timeout waiting heartbeat"; // In case of timeout of a stream, there is no error throwed.
if (node.errorDescription === "") node.errorDescription = "Timeout waiting for heartbeat"; // In case of timeout of a stream, there is no error throwed.
node.nodeClients.forEach(oClient => {

@@ -109,2 +109,7 @@ oClient.sendPayload({ topic: oClient.topic || "", errorDescription: node.errorDescription, payload: true });

//#region ALARMSTREAM
// Funzione per estrarre il boundary dal Content-Type
function extractBoundary(contentType) {
const match = contentType.match(/boundary=(.*)$/);
return match ? match[1] : null;
}
async function startAlarmStream() {

@@ -148,3 +153,3 @@

follow: 20, // maximum redirect count. 0 to not follow redirect
timeout: 0, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
timeout: 20000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead.
compress: false, // support gzip/deflate content encoding. false to disable

@@ -158,3 +163,2 @@ size: 0, // maximum response body size in bytes. 0 to disable

const responseAuth = await node.clientAlarmStream.fetch(node.protocol + "://" + node.host + "/ISAPI/Security/sessionLogin/capabilities?username=" + node.credentials.user, node.optionsAlarmStream)

@@ -236,80 +240,3 @@ if (responseAuth.status >= 200 && responseAuth.status <= 300) {

try {
//#region "HANDLE STREAM MESSAGE"
// Handle the complete stream message, enclosed into the --boundary stream string
// If there is more boundary, process each one separately
// ###################################
async function handleChunk(result) {
try {
// 05/12/2020 process the data
var aResults = result.split("--boundary");
if (node.debug) RED.log.info("SPLITTATO RESULT COUNT: ####### " + aResults.length + " ###################### FINE SPLITTATO RESULT");
aResults.forEach(async sRet => {
if (sRet.trim() !== "") {
if (node.debug) RED.log.error("BANANA PROCESSING" + sRet);
try {
//sRet = sRet.replace(/--boundary/g, '');
var i = sRet.indexOf("<"); // Get only the XML, starting with "<"
if (i > -1) {
sRet = sRet.substring(i);
try {
const parser = new XMLParser();
let result = parser.parse(sRet);
if (node.debug) RED.log.error("BANANA SBANANATO XML -> JSON " + JSON.stringify(result));
if (result !== null && result !== undefined && result.hasOwnProperty("EventNotificationAlert")) {
node.nodeClients.forEach(oClient => {
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result.EventNotificationAlert });
});
}
} catch (error) {
sRet = "";
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
} else {
i = sRet.indexOf("{") // It's a Json
if (i > -1) {
if (node.debug) RED.log.error("BANANA SBANANATO JSON " + sRet);
sRet = sRet.substring(i);
try {
sRet = JSON.parse(sRet);
// if (node.debug) RED.log.error("BANANA JSONATO: " + sRet);
if (sRet !== null && sRet !== undefined) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: sRet });
})
}
} catch (error) {
sRet = "";
}
} else {
// Invalid body
if (node.debug) RED.log.info("AXPro-config: DecodingBody Info only: Invalid Json " + sRet);
}
}
// All is fine. Reset and restart the hearbeat timer
// Hikvision sends an heartbeat alarm (videoloss), depending on firmware, every 300ms or more.
// If this HeartBeat isn't received, abort the stream request and restart.
node.resetHeartBeatTimer();
} catch (error) {
// if (node.debug) RED.log.error("BANANA startAlarmStream decodifica body: " + error);
if (node.debug) RED.log.error("AXPro-config: DecodingBody error: " + (error.message || " unknown error"));
throw (error);
}
} else {
if (node.debug) RED.log.info("SPLITTATO RESULT EMPTY: ####### " + sRet + " ###################### FINE SPLITTATO RESULT");
}
});
} catch (error) {
if (node.debug) RED.log.info("AXPro-config: readStream error: " + (error.message || " unknown error"));
node.errorDescription = "readStream error " + (error.message || " unknown error");
throw (error);
}
}
// ###################################
//#endregion
node.optionsAlarmStream.method = 'GET'

@@ -320,12 +247,12 @@ delete (node.optionsAlarmStream.Authorization)

const responseFromAxProAlarmStream = await node.clientAlarmStream.fetch(node.protocol + "://" + node.host + "/ISAPI/Event/notification/alertStream", node.optionsAlarmStream);
if (responseFromAxProAlarmStream.status >= 200 && responseFromAxProAlarmStream.status <= 300) {
const res = await node.clientAlarmStream.fetch(node.protocol + "://" + node.host + "/ISAPI/Event/notification/alertStream", node.optionsAlarmStream);
if (res.status >= 200 && res.status <= 300) {
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for event." });
} else {
node.setAllClientsStatus({ fill: "red", shape: "ring", text: responseFromAxProAlarmStream.statusText || " unknown response code" });
node.setAllClientsStatus({ fill: "red", shape: "ring", text: res.statusText || " unknown response code" });
// if (node.debug) RED.log.error("BANANA Error response " + response.statusText);
node.errorDescription = "StatusResponse problem " + (responseFromAxProAlarmStream.statusText || " unknown status response code");
throw new Error("StatusResponse " + (responseFromAxProAlarmStream.statusText || " unknown response code"));
node.errorDescription = "StatusResponse problem " + (res.statusText || " unknown status response code");
throw new Error("StatusResponse " + (res.statusText || " unknown response code"));
}
if (responseFromAxProAlarmStream.ok) {
if (res.ok) {
if (!node.isConnected) {

@@ -339,46 +266,100 @@ node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." });

node.isConnected = true;
node.resetHeartBeatTimer();
try {
if (node.debug) RED.log.info("AXPro-config: before Pipelining...");
if (oReadable !== null) oReadable.removeAllListeners() // 09/01/2023
oReadable = readableStr.from(responseFromAxProAlarmStream.body, { encoding: 'utf8' });
var result = "";
oReadable.on('data', async (chunk) => {
result += chunk;
if (result.indexOf("--boundary") > -1) {
const contentType = res.headers.get('content-type');
if (!contentType) {
if (node.debug) RED.log.error("Hikvision-config: No Content-Type in response");
// 11/01/2022 let's do some other checks on the event stream text
let bMessageCanBeHandled = false;
if (result.includes("</EventNotificationAlert>")) {
// Is the XML
bMessageCanBeHandled = true;
} else if (result.includes("}")) {
// Should be the JSON
bMessageCanBeHandled = true;
}
}
if (bMessageCanBeHandled) {
if (contentType.includes('multipart')) {
const boundary = extractBoundary(contentType);
if (!boundary) {
if (node.debug) RED.log.error("Hikvision-config: Failed to extract boundary from multipart stream");
}
//console.log(`Receiving multipart stream with boundary: ${boundary}`);
// Inizializza Dicer per il parsing del multipart
const dicer = new Dicer({ boundary });
dicer.on('part', (part) => {
let partData = [];
let extension = 'bin'; // Default estensione per parti non riconosciute
part.on('header', (header) => {
try {
await handleChunk(result);
node.resetHeartBeatTimer();
// Verifica il tipo di parte
if (header['content-type'] && (header['content-type'][0].includes('image/jpeg') || header['content-type'][0].includes('image/jpg'))) {
extension = 'jpg'; // Estensione corretta per immagini JPEG
} else if (header['content-type'] && header['content-type'][0].includes('image/png')) {
extension = 'png'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/xml')) {
extension = 'xml'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/json')) {
extension = 'json'; // Estensione corretta per immagini PNG
}
} catch (error) {
if (node.debug) RED.log.info("AXPro-config: Error handleChunk " + error.message || "");
}
result = "";
}
}
});
oReadable.on('end', function () {
// For some reason, some NVRs do end the stream. I must restart it.
if (node.debug) RED.log.info("AXPro-config: streamPipeline: STREAMING HAS ENDED.");
startAlarmStream();
});
});
oReadable.on('error', function (error) {
if (node.debug) RED.log.error("AXPro-config: streamPipeline: " + (error.message || " unknown error"));
});
part.on('data', (data) => {
try {
node.resetHeartBeatTimer();
partData.push(data); // Aggiungi i chunk di dati alla parte
} catch (error) {
}
});
//await streamPipeline(response.body, readStream);
part.on('end', () => {
try {
node.resetHeartBeatTimer();
const fullData = Buffer.concat(partData); // Unisci i chunk di dati
switch (extension) {
case 'xml':
handleXML(fullData);
break;
case 'json':
handleJSON(fullData);
break;
case 'jpg' || 'png':
//const filename = generateFilename(extension);
//saveFile(fullData, filename); // Salva l'immagine su disco
handleIMG(fullData, extension);
break;
default:
break;
}
} catch (error) {
}
});
part.on('error', (err) => {
//console.error('Error in part:', err);
});
});
dicer.on('finish', () => {
//console.log('Finished parsing multipart stream.');
node.resetHeartBeatTimer();
});
dicer.on('error', (err) => {
console.error('Error in Dicer:', err);
});
// Pipa lo stream multipart in Dicer
res.body.pipe(dicer);
} else {
//throw new Error('Unsupported Content-Type');
}
} catch (error) {
if (node.debug) RED.log.error("AXPro-config: streamPipeline: Please be sure to have the latest Node.JS version installed: " + (error.message || " unknown error"));
if (node.debug) RED.log.error("Hikvision-config: streamPipeline: Please be sure to have the latest Node.JS version installed: " + (error.message || " unknown error"));
}
}

@@ -401,3 +382,43 @@

//#endregion
//#region "HANDLE STREAM MESSAGE"
// Handle the complete stream message
// ###################################
async function handleIMG(result, extension) {
try {
if (node.debug) RED.log.error("BANANA SBANANATO IMG -> JSON " + JSON.stringify(oXML));
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: result, type: 'img', extension: extension });
});
} catch (error) {
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
async function handleXML(result) {
try {
const parser = new XMLParser();
const oXML = parser.parse(result);
if (node.debug) RED.log.error("BANANA SBANANATO XML -> JSON " + JSON.stringify(oXML));
node.nodeClients.forEach(oClient => {
if (oXML !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: oXML.EventNotificationAlert, type: 'event' });
});
} catch (error) {
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
async function handleJSON(result) {
try {
const oJSON = JSON.parse(result);
if (oJSON !== null && oJSON !== undefined) {
node.nodeClients.forEach(oClient => {
oClient.sendPayload({ topic: oClient.topic || "", payload: oJSON, type: 'event' });
})
}
} catch (error) {
if (node.debug) RED.log.error("BANANA ERRORE fast-xml-parser(sRet, function (err, result) " + error.message || "");
}
}
// ###################################
//#endregion
// Read zones status and outputs only changed ones

@@ -404,0 +425,0 @@ // Wrapping the async function, for peace of mind

@@ -234,3 +234,2 @@

}
if (contentType.includes('multipart')) {

@@ -252,13 +251,16 @@ const boundary = extractBoundary(contentType);

part.on('header', (header) => {
//console.log('Part headers:', header);
node.resetHeartBeatTimer();
// Verifica il tipo di parte
if (header['content-type'] && (header['content-type'][0].includes('image/jpeg') || header['content-type'][0].includes('image/jpg'))) {
extension = 'jpg'; // Estensione corretta per immagini JPEG
} else if (header['content-type'] && header['content-type'][0].includes('image/png')) {
extension = 'png'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/xml')) {
extension = 'xml'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/json')) {
extension = 'json'; // Estensione corretta per immagini PNG
try {
//console.log('Part headers:', header);
node.resetHeartBeatTimer();
// Verifica il tipo di parte
if (header['content-type'] && (header['content-type'][0].includes('image/jpeg') || header['content-type'][0].includes('image/jpg'))) {
extension = 'jpg'; // Estensione corretta per immagini JPEG
} else if (header['content-type'] && header['content-type'][0].includes('image/png')) {
extension = 'png'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/xml')) {
extension = 'xml'; // Estensione corretta per immagini PNG
} else if (header['content-type'] && header['content-type'][0].includes('application/json')) {
extension = 'json'; // Estensione corretta per immagini PNG
}
} catch (error) {
}

@@ -268,29 +270,40 @@ });

part.on('data', (data) => {
node.resetHeartBeatTimer();
partData.push(data); // Aggiungi i chunk di dati alla parte
try {
node.resetHeartBeatTimer();
partData.push(data); // Aggiungi i chunk di dati alla parte
} catch (error) {
}
});
part.on('end', () => {
node.resetHeartBeatTimer();
const fullData = Buffer.concat(partData); // Unisci i chunk di dati
switch (extension) {
case 'xml':
handleXML(fullData);
break;
case 'json':
handleJSON(fullData);
break;
case 'jpg' || 'png':
//const filename = generateFilename(extension);
//saveFile(fullData, filename); // Salva l'immagine su disco
handleIMG(fullData, extension);
break;
default:
break;
try {
node.resetHeartBeatTimer();
const fullData = Buffer.concat(partData); // Unisci i chunk di dati
switch (extension) {
case 'xml':
handleXML(fullData);
break;
case 'json':
handleJSON(fullData);
break;
case 'jpg' || 'png':
//const filename = generateFilename(extension);
//saveFile(fullData, filename); // Salva l'immagine su disco
handleIMG(fullData, extension);
break;
default:
break;
}
} catch (error) {
}
});
part.on('error', (err) => {
//console.error('Error in part:', err);
});
});
dicer.on('finish', () => {
console.log('Finished parsing multipart stream.');
//console.log('Finished parsing multipart stream.');
});

@@ -297,0 +310,0 @@

{
"name": "node-red-contrib-hikvision-ultimate",
"version": "1.2.0",
"version": "1.2.1",
"description": "A native set of nodes for Hikvision (and compatible) Cameras, Alarms, Radars, NVR, Doorbells, etc.",

@@ -5,0 +5,0 @@ "author": "Supergiovane (https://github.com/Supergiovane)",

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc