node-red-contrib-hikvision-ultimate
Advanced tools
Comparing version 0.0.8 to 0.0.9
@@ -5,2 +5,7 @@ # node-red-contrib-hikvision-ultimate | ||
<p> | ||
<b>Version 0.0.9 BETA</b> November 2020<br/> | ||
- Replaced the node urllib with node-fetch. | ||
- Alert node now filters heartbeath without sending unuseful payloads. | ||
</p> | ||
<p> | ||
<b>Version 0.0.8 BETA</b> November 2020<br/> | ||
@@ -7,0 +12,0 @@ - ANPR node now emits connect and disconnect messages. |
@@ -5,7 +5,6 @@ | ||
const urllib = require('urllib'); | ||
var Agent = require('agentkeepalive'); | ||
const xml2js = require('xml2js').parseString; | ||
const DigestFetch = require('digest-fetch') | ||
const AbortController = require('abort-controller'); | ||
const xml2js = require('xml2js').Parser({explicitArray: false}).parseString; | ||
function ANPRconfig(config) { | ||
@@ -32,90 +31,122 @@ RED.nodes.createNode(this, config) | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Connecting..." }); | ||
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client. | ||
var controller = new AbortController(); // For aborting the stream request | ||
var options = { | ||
"digestAuth": node.credentials.user + ":" + node.credentials.password, | ||
"streaming": false, | ||
"timeout": 5000, | ||
"method": "POST", | ||
'followRedirect': true, | ||
'followAllRedirects': true, | ||
"data": "<AfterTime><picTime>202001010101010000</picTime></AfterTime>", | ||
"headers": { | ||
'content-type': 'application/xml', | ||
} | ||
}; | ||
// These properties are part of the Fetch Standard | ||
method: 'POST', | ||
headers: { 'content-type': 'application/xml' }, // request headers. format is the identical to that accepted by the Headers constructor (see below) | ||
body: "<AfterTime><picTime>202001010101010000</picTime></AfterTime>", // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream | ||
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect | ||
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests | ||
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options, function (err, data, res) { | ||
if (err) { | ||
//console.log("MAIN ERROR: " + JSON.stringify(err)); | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}) | ||
// The following properties are node-fetch extensions | ||
follow: 20, // maximum redirect count. 0 to not follow redirect | ||
timeout: 5000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead. | ||
compress: true, // support gzip/deflate content encoding. false to disable | ||
size: 0, // maximum response body size in bytes. 0 to disable | ||
agent: null // http(s).Agent instance or function that returns an instance (see below) | ||
} | ||
client.fetch("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options) | ||
.then(response => { | ||
//RED.log.error(response.status + " " + response.statusText); | ||
if (response.statusText === "Unauthorized") { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText }); | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 10000); // Reconnect | ||
return; | ||
} | ||
if (data) { | ||
node.lastPicName = ""; | ||
var oPlates = null; | ||
try { | ||
var sRet = data.toString(); | ||
var i = sRet.indexOf("<"); // Get only the XML, starting with "<" | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
// By xml2js | ||
xml2js(sRet, function (err, result) { | ||
oPlates = result; | ||
}); | ||
} else { | ||
i = sRet.indexOf("{") // It's a Json | ||
if (response.statusText === "Ok") { | ||
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." }); | ||
} | ||
return response.body; | ||
}).then(body => { | ||
body.on('readable', () => { | ||
let chunk; | ||
var sRet = ""; | ||
node.lastPicName = ""; | ||
while (null !== (chunk = body.read())) { | ||
sRet = chunk.toString(); | ||
} | ||
//console.log ("BANANA " + sRet) | ||
var oPlates = null; | ||
try { | ||
var i = sRet.indexOf("<"); // Get only the XML, starting with "<" | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
oPlates = JSON.parse(result); | ||
// By xml2js | ||
xml2js(sRet, function (err, result) { | ||
oPlates = result; | ||
}); | ||
} else { | ||
i = sRet.indexOf("{") // It's a Json | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
oPlates = JSON.parse(result); | ||
} else { | ||
// Invalid body | ||
RED.log.error("ANPR-config: DecodingBody:" + error); | ||
node.lastPicName = ""; // This raises an error, below. | ||
oPlates = null; // Set null | ||
} | ||
} | ||
} catch (error) { | ||
RED.log.error("ANPR-config: ERRORE CATCHATO initPlateReader:" + error); | ||
node.lastPicName = ""; // This raises an error, below. | ||
} | ||
} catch (error) { console.log("ERRORE CATCHATO initPlateReader" + error); } | ||
// Working the plates. Must be sure, that no error occurs, before acknolwedging the plate last picName | ||
try { | ||
if (oPlates !== null) { | ||
if (oPlates.Plates !== null) { | ||
if (oPlates.Plates.hasOwnProperty("Plate") && oPlates.Plates.Plate.length > 0) { | ||
node.lastPicName = oPlates.Plates.Plate[oPlates.Plates.Plate.length - 1].picName; | ||
//node.lastPicName = "202001010101010000"; // BANANA SIMULAZIONE CON LETTURA DI TUTTE LE TARGHE | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Found " + oPlates.Plates.Plate.length + " ignored plates. Last was " + node.lastPicName }); | ||
} else { | ||
// No previously plates found, set a default datetime | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "No previously plates found." }); | ||
node.lastPicName = "202001010101010000"; | ||
} | ||
} | ||
} | ||
} catch (error) { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Ahi lasso Error: " + error }); | ||
node.lastPicName = ""; // This raises an error, below. | ||
} | ||
}; | ||
// Must be sure, that no error occurs, before acknolwedging the plate last picName | ||
try { | ||
if (oPlates !== null) { | ||
if (oPlates.Plates !== null && oPlates.Plates.hasOwnProperty("Plate")) { | ||
if (oPlates.Plates.Plate.length > 0) { | ||
node.lastPicName = oPlates.Plates.Plate[oPlates.Plates.Plate.length - 1].picName; | ||
//node.lastPicName = "202001010101010000"; // BANANA | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Found " + oPlates.Plates.Plate.length + " ignored plates. Last was " + node.lastPicName }); | ||
} else { | ||
// No previously plates found, set a default datetime | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "No previously plates found." }); | ||
node.lastPicName = "202001010101010000"; | ||
if (node.lastPicName === "") { | ||
// Some errors occurred | ||
// Abort request | ||
try { | ||
if (controller !== null) controller.abort(); | ||
} catch (error) { } | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Error occurred in init plates list." }); | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 5000); // Reconnect | ||
} else { | ||
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for vehicle..." }); | ||
if (!node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: true }); | ||
}) | ||
} | ||
node.isConnected = true; | ||
setTimeout(node.queryForPlates, 2000); // Start main polling thread | ||
} | ||
} | ||
} catch (error) { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Ahi lasso: " + error }); | ||
} | ||
if (node.lastPicName === "") { | ||
// Some errors occurred | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Error occurred in init plates list." }); | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 10000); // Reconnect | ||
} else { | ||
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Waiting for vehicle..." }); | ||
if (!node.isConnected) { | ||
}); | ||
}) | ||
.catch(err => { | ||
// Abort request | ||
try { | ||
if (controller !== null) controller.abort(); | ||
} catch (error) { } | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable: " + err + " Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: true }); | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}) | ||
} | ||
node.isConnected = true; | ||
setTimeout(node.queryForPlates, 2000); // Start main polling thread | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 5000); // Reconnect | ||
return; | ||
}); | ||
}); | ||
}; | ||
@@ -138,37 +169,38 @@ | ||
} | ||
var controller = new AbortController(); // For aborting the stream request | ||
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client. | ||
var options = { | ||
"digestAuth": node.credentials.user + ":" + node.credentials.password, | ||
"streaming": false, | ||
"timeout": 5000, | ||
"method": "POST", | ||
'followRedirect': true, | ||
'followAllRedirects': true, | ||
"data": "<AfterTime><picTime>" + node.lastPicName + "</picTime></AfterTime>", | ||
"headers": { | ||
'content-type': 'application/xml', | ||
} | ||
}; | ||
// These properties are part of the Fetch Standard | ||
method: 'POST', | ||
headers: { 'content-type': 'application/xml' }, // request headers. format is the identical to that accepted by the Headers constructor (see below) | ||
body: "<AfterTime><picTime>" + node.lastPicName + "</picTime></AfterTime>", // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream | ||
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect | ||
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests | ||
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options, function (err, data, res) { | ||
// The following properties are node-fetch extensions | ||
follow: 20, // maximum redirect count. 0 to not follow redirect | ||
timeout: 5000, // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies). Signal is recommended instead. | ||
compress: true, // support gzip/deflate content encoding. false to disable | ||
size: 0, // maximum response body size in bytes. 0 to disable | ||
agent: null // http(s).Agent instance or function that returns an instance (see below) | ||
} | ||
if (err) { | ||
//console.log("MAIN ERROR: " + JSON.stringify(err)); | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}) | ||
client.fetch("http://" + node.host + "/ISAPI/Traffic/channels/1/vehicleDetect/plates", options) | ||
.then(response => { | ||
//RED.log.error(response.status + " " + response.statusText); | ||
if (response.statusText === "Unauthorized") { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText }); | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 10000); // Restart whole process. | ||
return; | ||
} | ||
if (data) { | ||
if (response.statusText === "Ok") { | ||
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected." }); | ||
} | ||
return response.body; | ||
}).then(body => { | ||
let chunk; | ||
var oRet = ""; | ||
while (null !== (chunk = body.read())) { | ||
sRet = chunk.toString(); | ||
} | ||
node.isConnected = true; | ||
var oRet = null; | ||
try { | ||
var sRet = data.toString(); | ||
var i = sRet.indexOf("<"); // Get only the XML, starting with "<" | ||
@@ -190,3 +222,3 @@ if (i > -1) { | ||
} catch (error) { console.log("ERRORE CATCHATO " + error); } | ||
} catch (error) { RED.log.error("ANPR-config: ERRORE CATCHATO vehicleDetect/plates:" + error); } | ||
@@ -203,7 +235,16 @@ // Send the message to the child nodes | ||
} | ||
}; | ||
setTimeout(node.queryForPlates, 1000); // Call the cunction again. | ||
}); | ||
setTimeout(node.queryForPlates, 1000); // Call the cunction again. | ||
}) | ||
.catch(err => { | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}) | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.initPlateReader, 10000); // Restart whole process. | ||
return; | ||
}); | ||
}; | ||
@@ -210,0 +251,0 @@ |
@@ -5,7 +5,6 @@ | ||
const urllib = require('urllib'); | ||
var Agent = require('agentkeepalive'); | ||
const xml2js = require('xml2js').parseString; | ||
const DigestFetch = require('digest-fetch') | ||
const AbortController = require('abort-controller'); | ||
const xml2js = require('xml2js').Parser({explicitArray: false}).parseString; | ||
function Hikvisionconfig(config) { | ||
@@ -18,81 +17,116 @@ RED.nodes.createNode(this, config) | ||
node.isConnected = false; | ||
node.timerCheckHeartBeat = null; | ||
var controller = null; // AbortController | ||
node.setAllClientsStatus = ({ fill, shape, text }) => { | ||
function nextStatus(oClient) { | ||
oClient.setNodeStatus({ fill: fill, shape: shape, text: text }) | ||
} | ||
node.nodeClients.map(nextStatus); | ||
} | ||
// This function starts the heartbeat timer, to detect the disconnection from the server | ||
node.startHeartBeatTimer = () => { | ||
// Reset node.timerCheckHeartBeat | ||
if (node.timerCheckHeartBeat !== null) clearTimeout(node.timerCheckHeartBeat); | ||
node.timerCheckHeartBeat = setTimeout(() => { | ||
try { | ||
if (controller !== null) controller.abort(); | ||
} catch (error) { } | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}); | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: "Lost connection...Retry..." }); | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.startAlarmStream, 5000); // Reconnect | ||
}, 25000); | ||
} | ||
node.startAlarmStream = () => { | ||
var controller = new AbortController(); // For aborting the stream request | ||
var client = new DigestFetch(node.credentials.user, node.credentials.password); // Instantiate the fetch client. | ||
var options = { | ||
// These properties are part of the Fetch Standard | ||
method: 'GET', | ||
headers: {}, // request headers. format is the identical to that accepted by the Headers constructor (see below) | ||
body: null, // request body. can be null, a string, a Buffer, a Blob, or a Node.js Readable stream | ||
redirect: 'follow', // set to `manual` to extract redirect headers, `error` to reject redirect | ||
signal: controller.signal, // pass an instance of AbortSignal to optionally abort requests | ||
// The following properties are node-fetch extensions | ||
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. | ||
compress: true, // support gzip/deflate content encoding. false to disable | ||
size: 0, // maximum response body size in bytes. 0 to disable | ||
agent: null // http(s).Agent instance or function that returns an instance (see below) | ||
} | ||
node.startHeartBeatTimer(); // First thing, start the heartbeat timer. | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Connecting..." }); | ||
var keepaliveAgent = new Agent({ | ||
keepAlive: true | ||
}); | ||
var options = { | ||
"digestAuth": node.credentials.user + ":" + node.credentials.password, | ||
"streaming": true, | ||
"agent": keepaliveAgent | ||
}; | ||
node.urllibRequest = urllib.request("http://" + node.host + "/ISAPI/Event/notification/alertStream", options, function (err, data, res) { | ||
if (err) { | ||
//console.log("MAIN ERROR: " + err); | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable. Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}) | ||
client.fetch("http://" + node.host + "/ISAPI/Event/notification/alertStream", options) | ||
.then(response => { | ||
//console.log(response.status + " " + response.statusText); | ||
if (response.statusText === "Unauthorized") { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: response.statusText }); | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.startAlarmStream, 10000); // Reconnect | ||
return; | ||
} | ||
res.on('data', function (chunk) { | ||
node.isConnected = true; | ||
try { | ||
var sRet = chunk.toString(); | ||
sRet = sRet.replace(/--boundary/g, ''); | ||
var i = sRet.indexOf("<"); // Get only the XML, starting with "<" | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
// By xml2js | ||
xml2js(sRet, function (err, result) { | ||
node.nodeClients.forEach(oClient => { | ||
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result, connected: true }); | ||
}) | ||
}); | ||
} else { | ||
i = sRet.indexOf("{") // It's a Json | ||
if (response.statusText === "Ok") { | ||
node.setAllClientsStatus({ fill: "green", shape: "ring", text: "Connected. Waiting for Alarm." }); | ||
} | ||
return response.body; | ||
}) | ||
.then(body => { | ||
body.on('readable', () => { | ||
node.isConnected = true; | ||
try { | ||
let chunk; | ||
var sRet = "" | ||
while (null !== (chunk = body.read())) { | ||
sRet = chunk.toString(); | ||
} | ||
sRet = sRet.replace(/--boundary/g, ''); | ||
var i = sRet.indexOf("<"); // Get only the XML, starting with "<" | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
//sRet = sRet.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); // Fix numbers and chars invalid in JSON | ||
//console.log("BANANA : " + sRet); | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: JSON.parse(sRet), connected: true }); | ||
}) | ||
// By xml2js | ||
xml2js(sRet, function (err, result) { | ||
node.nodeClients.forEach(oClient => { | ||
if (result !== undefined) oClient.sendPayload({ topic: oClient.topic || "", payload: result, connected: true }); | ||
}) | ||
}); | ||
} else { | ||
i = sRet.indexOf("{") // It's a Json | ||
if (i > -1) { | ||
sRet = sRet.substring(i); | ||
//sRet = sRet.replace(/(['"])?([a-z0-9A-Z_]+)(['"])?:/g, '"$2": '); // Fix numbers and chars invalid in JSON | ||
//console.log("BANANA : " + sRet); | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: JSON.parse(sRet), connected: true }); | ||
}) | ||
} | ||
} | ||
} | ||
} catch (error) { console.log("ERRORE CATCHATO " + error); } | ||
} catch (error) { RED.log.error("Hikvision-config: ERRORE CATCHATO " + error); } | ||
// 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.startHeartBeatTimer(); | ||
}); | ||
}); | ||
res.on('end', function () { | ||
//console.log("Hikvision-Config: END " + res.statusMessage); | ||
if (res.statusMessage === "Unauthorized") { | ||
node.setAllClientsStatus({ fill: "red", shape: "ring", text: res.statusMessage }); | ||
} | ||
}); | ||
res.on('close', function () { | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Disconnected. Retry..." }); | ||
}) | ||
.catch(err => { | ||
node.setAllClientsStatus({ fill: "grey", shape: "ring", text: "Server unreachable: " + err + " Retry..." }); | ||
if (node.isConnected) { | ||
node.nodeClients.forEach(oClient => { | ||
oClient.sendPayload({ topic: oClient.topic || "", payload: null, connected: false }); | ||
}); | ||
}) | ||
} | ||
node.isConnected = false; | ||
setTimeout(node.startAlarmStream, 10000); // Reconnect | ||
return; | ||
}); | ||
res.on('error', function (err) { | ||
//("ERROR: " + err); | ||
}); | ||
}); | ||
}; | ||
@@ -102,14 +136,12 @@ | ||
//#region "FUNCTIONS" | ||
node.on('close', function (removed, done) { | ||
try { | ||
controller.abort(); | ||
} catch (error) { } | ||
if (node.timerCheckHeartBeat !== null) clearTimeout(node.timerCheckHeartBeat); | ||
done(); | ||
}); | ||
node.setAllClientsStatus = ({ fill, shape, text }) => { | ||
function nextStatus(oClient) { | ||
oClient.setNodeStatus({ fill: fill, shape: shape, text: text }) | ||
} | ||
node.nodeClients.map(nextStatus); | ||
} | ||
@@ -116,0 +148,0 @@ node.addClient = (_Node) => { |
@@ -17,2 +17,10 @@ | ||
if (_msg.payload === null) { node.send(_msg); return; }; // If null, then it's disconnected. Avoid processing the event | ||
if (_msg.payload.hasOwnProperty("EventNotificationAlert") | ||
&& _msg.payload.EventNotificationAlert.eventType.toString().toLowerCase() === "videoloss" | ||
&& _msg.payload.EventNotificationAlert.eventState.toString().toLowerCase() === "inactive") { | ||
// It's a HertBeat, exit. | ||
node.setNodeStatus({ fill: "green", shape: "ring", text: "Waiting for alert..." }); | ||
return ; | ||
}; | ||
node.setNodeStatus({ fill: "green", shape: "dot", text: "Alert received" }); | ||
@@ -19,0 +27,0 @@ node.send(_msg); |
{ | ||
"name": "node-red-contrib-hikvision-ultimate", | ||
"version": "0.0.8", | ||
"version": "0.0.9", | ||
"description": "A native set of node for Hikvision Cameras, Alarms, Radars etc.", | ||
"author": "Supergiovane (https://github.com/Supergiovane)", | ||
"dependencies": { | ||
"urllib": "2.36.1", | ||
"xml2js": "0.4.23", | ||
"agentkeepalive": "4.1.3" | ||
"node-fetch": "2.6.1", | ||
"digest-fetch": "1.1.5", | ||
"abort-controller": ">=3.0.0" | ||
}, | ||
@@ -15,3 +16,6 @@ "keywords": [ | ||
"alarm", | ||
"camera" | ||
"camera", | ||
"radar", | ||
"ANPR", | ||
"license plate" | ||
], | ||
@@ -18,0 +22,0 @@ "license": "MIT", |
441060
484
4
+ Addedabort-controller@>=3.0.0
+ Addeddigest-fetch@1.1.5
+ Addednode-fetch@2.6.1
+ Addedabort-controller@3.0.0(transitive)
+ Addedbase-64@0.1.0(transitive)
+ Addedcrypto-js@3.3.0(transitive)
+ Addeddigest-fetch@1.1.5(transitive)
+ Addedevent-target-shim@5.0.1(transitive)
+ Addednode-fetch@2.6.1(transitive)
- Removedagentkeepalive@4.1.3
- Removedurllib@2.36.1
- Removedaddress@2.0.3(transitive)
- Removedagent-base@4.2.14.3.0(transitive)
- Removedagentkeepalive@4.1.3(transitive)
- Removedany-promise@1.3.0(transitive)
- Removedast-types@0.16.1(transitive)
- Removedbytes@3.1.2(transitive)
- Removedcall-bind-apply-helpers@1.0.1(transitive)
- Removedcall-bound@1.0.3(transitive)
- Removedco@4.6.0(transitive)
- Removedcontent-type@1.0.5(transitive)
- Removedcopy-to@2.0.1(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddata-uri-to-buffer@1.2.0(transitive)
- Removeddebug@2.6.93.1.03.2.74.4.0(transitive)
- Removeddeep-is@0.1.4(transitive)
- Removeddefault-user-agent@1.0.0(transitive)
- Removeddegenerator@1.0.4(transitive)
- Removeddepd@1.1.22.0.0(transitive)
- Removeddestroy@1.2.0(transitive)
- Removeddigest-header@0.0.1(transitive)
- Removeddunder-proto@1.0.1(transitive)
- Removedee-first@1.1.1(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removedes-define-property@1.0.1(transitive)
- Removedes-errors@1.3.0(transitive)
- Removedes-object-atoms@1.1.1(transitive)
- Removedes6-promise@4.2.8(transitive)
- Removedes6-promisify@5.0.0(transitive)
- Removedescape-html@1.0.3(transitive)
- Removedescodegen@1.14.3(transitive)
- Removedesprima@3.1.34.0.1(transitive)
- Removedestraverse@4.3.0(transitive)
- Removedesutils@2.0.3(transitive)
- Removedextend@3.0.2(transitive)
- Removedextend-shallow@2.0.1(transitive)
- Removedfast-levenshtein@2.0.6(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedformstream@1.5.1(transitive)
- Removedftp@0.3.10(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-intrinsic@1.2.7(transitive)
- Removedget-proto@1.0.1(transitive)
- Removedget-uri@2.0.4(transitive)
- Removedgopd@1.2.0(transitive)
- Removedhas-symbols@1.1.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedhttp-errors@2.0.0(transitive)
- Removedhttp-proxy-agent@2.1.0(transitive)
- Removedhttps-proxy-agent@3.0.1(transitive)
- Removedhumanize-ms@1.2.1(transitive)
- Removediconv-lite@0.4.24(transitive)
- Removedinherits@2.0.4(transitive)
- Removedip@1.1.51.1.9(transitive)
- Removedis-extendable@0.1.1(transitive)
- Removedisarray@0.0.11.0.0(transitive)
- Removedlevn@0.3.0(transitive)
- Removedlru-cache@5.1.1(transitive)
- Removedmath-intrinsics@1.1.0(transitive)
- Removedmime@2.6.0(transitive)
- Removedminimist@1.2.8(transitive)
- Removedmkdirp@0.5.6(transitive)
- Removedms@2.0.02.1.3(transitive)
- Removedmz@2.7.0(transitive)
- Removednetmask@1.0.6(transitive)
- Removednode-hex@1.0.1(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedobject-inspect@1.13.3(transitive)
- Removedonce@1.4.0(transitive)
- Removedoptionator@0.8.3(transitive)
- Removedos-name@1.0.3(transitive)
- Removedosx-release@1.1.0(transitive)
- Removedpac-proxy-agent@3.0.1(transitive)
- Removedpac-resolver@3.0.0(transitive)
- Removedpause-stream@0.0.11(transitive)
- Removedprelude-ls@1.1.2(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedproxy-agent@3.1.1(transitive)
- Removedproxy-from-env@1.1.0(transitive)
- Removedpump@3.0.2(transitive)
- Removedqs@6.14.0(transitive)
- Removedraw-body@2.5.2(transitive)
- Removedreadable-stream@1.1.142.3.8(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsemver@5.7.2(transitive)
- Removedsetprototypeof@1.2.0(transitive)
- Removedside-channel@1.1.0(transitive)
- Removedside-channel-list@1.0.0(transitive)
- Removedside-channel-map@1.0.1(transitive)
- Removedside-channel-weakmap@1.0.2(transitive)
- Removedsmart-buffer@4.2.0(transitive)
- Removedsocks@2.3.3(transitive)
- Removedsocks-proxy-agent@4.0.2(transitive)
- Removedsource-map@0.6.1(transitive)
- Removedstatuses@1.5.02.0.1(transitive)
- Removedstring_decoder@0.10.311.1.1(transitive)
- Removedthenify@3.3.1(transitive)
- Removedthenify-all@1.6.0(transitive)
- Removedthrough@2.3.8(transitive)
- Removedthunkify@2.1.2(transitive)
- Removedtoidentifier@1.0.1(transitive)
- Removedtslib@2.8.1(transitive)
- Removedtype-check@0.3.2(transitive)
- Removedunescape@1.0.1(transitive)
- Removedunpipe@1.0.0(transitive)
- Removedurllib@2.36.1(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedutility@0.1.111.18.0(transitive)
- Removedwin-release@1.1.1(transitive)
- Removedword-wrap@1.2.5(transitive)
- Removedwrappy@1.0.2(transitive)
- Removedxregexp@2.0.0(transitive)
- Removedyallist@3.1.1(transitive)