node-red-contrib-hik-media-buffer 1.1.108 → 1.1.109

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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +18 -46
  2. package/package.json +1 -1
@@ -97,7 +97,7 @@ module.exports = function(RED) {
97
97
  const referenceTime = new Date();
98
98
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
99
99
 
100
- // Costruiamo le date giornaliere stile Chrome
100
+ // Costruiamo le date giornaliere identiche a quelle viste su Chrome
101
101
  const pad = (num) => String(num).padStart(2, '0');
102
102
  const year = referenceTime.getFullYear();
103
103
  const month = pad(referenceTime.getMonth() + 1);
@@ -106,7 +106,7 @@ module.exports = function(RED) {
106
106
  const startTime = `${year}-${month}-${day}T00:00:00 01:00`;
107
107
  const endTime = `${year}-${month}-${day}T23:59:59 01:00`;
108
108
 
109
- node.status({fill:"yellow", shape:"dot", text:`Generazione Sessione...`});
109
+ node.status({fill:"yellow", shape:"dot", text:`Invio query protetta...`});
110
110
 
111
111
  let output = {
112
112
  tipo_messaggio: "evento", nome_cliente: node.name, nome_telecamera: nomeCamera,
@@ -117,38 +117,13 @@ module.exports = function(RED) {
117
117
  let fileDaCancellare = [];
118
118
 
119
119
  try {
120
- // --- STEP 1: CHIAMATA PRE-LOGIN PER ESTREMARE IL COOKIE WEBSESSION ---
121
- // Interroghiamo un endpoint ISAPI leggero in Digest per farci rilasciare il Cookie di sessione
122
- const loginRes = await nvrAuthAxios.request({
123
- method: 'GET',
124
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/Security/sessionLogin/capabilities?format=json`,
125
- timeout: 5000
126
- });
127
-
128
- // Estraiamo il cookie "WebSession" dagli header di risposta
129
- const responseHeaders = loginRes.headers['set-cookie'] || [];
130
- let webSessionValue = '';
131
- let cookieKeyName = 'WebSession';
132
-
133
- for (let cookie of responseHeaders) {
134
- if (cookie.includes('WebSession')) {
135
- const matchCookie = cookie.match(/(WebSession_[a-f0-9]+|WebSession)=([^;]+)/i);
136
- if (matchCookie) {
137
- cookieKeyName = matchCookie[1];
138
- webSessionValue = matchCookie[2];
139
- }
140
- }
141
- }
142
-
143
- // Se l'NVR non ha sputato il cookie, ne generiamo uno fittizio in MD5 basandoci sul timestamp
144
- // perché in molti firmware basta che l'header esista ed abbia lo stesso valore del cookie!
145
- if (!webSessionValue) {
146
- webSessionValue = require('crypto').createHash('md5').update(String(Date.now())).digest('hex');
147
- }
120
+ // --- GENERAZIONE HASH DI SESSIONE LOCALE ---
121
+ // Generiamo un MD5 casuale per bypassare il controllo di uguaglianza tra Cookie e Sessiontag
122
+ const fakeSessionToken = require('crypto').createHash('md5').update(String(Math.random() + Date.now())).digest('hex');
123
+ const cookieKeyName = `WebSession_${require('crypto').createHash('md5').update(node.host).digest('hex').substring(0, 10)}`;
148
124
 
149
- node.warn(`[DEBUG SESSION] Clonazione Cookie -> ${cookieKeyName}=${webSessionValue}`);
125
+ node.warn(`[DEBUG SESSION] Simulazione Header -> ${cookieKeyName}=${fakeSessionToken}`);
150
126
 
151
- // --- STEP 2: COSTRUZIONE QUERY DIRETTA CON DATI DI CHROME ---
152
127
  const dynamicSearchId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
153
128
  var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
154
129
  return v.toString(16).toUpperCase();
@@ -170,20 +145,17 @@ module.exports = function(RED) {
170
145
  const tempJsonPath = path.join(baseStorage, `search_${channelID}_${timestamp}.json`);
171
146
  fs.writeFileSync(tempJsonPath, JSON.stringify(searchPayload), 'utf8');
172
147
 
173
- // --- STEP 3: LANCE DIRETTO VIA CURL CON I TRE ASSI NELLA MANICA ---
174
- // 1. Content-Type form-urlencoded (anche se mandiamo un JSON, come fa Chrome!)
175
- // 2. Cookie WebSession inserito a mano
176
- // 3. sessiontag identico al valore del cookie
177
148
  const targetUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/eventRecordSearch?format=json`;
178
149
 
150
+ // Compiliamo il comando cURL inserendo gli header emersi dall'analisi di Chrome
151
+ // Usiamo sia il content-type form-urlencoded sia la coppia speculare cookie/sessiontag
179
152
  const curlCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" ` +
180
153
  `-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" ` +
181
154
  `-H "X-Requested-With: XMLHttpRequest" ` +
182
- `-H "sessiontag: ${webSessionValue}" ` +
183
- `-b "${cookieKeyName}=${webSessionValue}" ` +
155
+ `-H "sessiontag: ${fakeSessionToken}" ` +
156
+ `-b "${cookieKeyName}=${fakeSessionToken}; updatePlugin=false" ` +
184
157
  `-d "@${tempJsonPath}" "${targetUrl}"`;
185
158
 
186
- node.warn(`[DEBUG JSON] Invio query protetta con token di sessione...`);
187
159
  let jsonResponseRaw = await new Promise((resolve) => { exec(curlCommand, (err, stdout) => resolve(err ? null : stdout)); });
188
160
 
189
161
  try { if (fs.existsSync(tempJsonPath)) fs.unlinkSync(tempJsonPath); } catch(e){}
@@ -198,19 +170,19 @@ module.exports = function(RED) {
198
170
  const matches = searchResult?.EventSearchResult?.matchList || [];
199
171
 
200
172
  if (matches.length === 0) {
201
- node.warn(`[DEBUG JSON] Zero match trovati. La sessione non è bastata o i parametri differiscono.`);
173
+ node.warn(`[DEBUG JSON] Zero match trovati nell'archivio protetto.`);
202
174
  updateNodeStatus();
203
175
  return;
204
176
  }
205
177
 
206
- // Se arriviamo qui, l'array si è sbloccato!
178
+ // Se superiamo il blocco, estraiamo i file storici
207
179
  const matchItem = matches[0];
208
180
  const playbackUri = matchItem?.mediaSegmentDescriptor?.playbackURI;
209
181
 
210
182
  if (playbackUri) {
211
- node.warn(`[DEBUG JSON] Match sbloccato con successo! URL: ${playbackUri}`);
183
+ node.warn(`[DEBUG JSON] File individuato: ${playbackUri}`);
212
184
 
213
- // --- DOWNLOAD FOTO NATIVA REGISTRATA SU HDD ---
185
+ // --- DOWNLOAD FOTO REGISTRATA ---
214
186
  let pictureUrlPath = null;
215
187
  const urlParamsMatch = playbackUri.match(/\?(.*)/);
216
188
  if (urlParamsMatch) {
@@ -220,7 +192,7 @@ module.exports = function(RED) {
220
192
 
221
193
  if (pictureUrlPath) {
222
194
  const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
223
- const curlImgCmd = `curl -s -X GET --digest -u "${node.user}:${node.pass}" -b "${cookieKeyName}=${webSessionValue}" -H "sessiontag: ${webSessionValue}" "${node.protocol}://${node.host}:${node.port}${pictureUrlPath}" -o "${fullImgPath}"`;
195
+ const curlImgCmd = `curl -s -X GET --digest -u "${node.user}:${node.pass}" -b "${cookieKeyName}=${fakeSessionToken}" -H "sessiontag: ${fakeSessionToken}" "${node.protocol}://${node.host}:${node.port}${pictureUrlPath}" -o "${fullImgPath}"`;
224
196
  await new Promise((resolve) => { exec(curlImgCmd, () => resolve()); });
225
197
  if (fs.existsSync(fullImgPath) && fs.statSync(fullImgPath).size > 100) {
226
198
  output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
@@ -228,7 +200,7 @@ module.exports = function(RED) {
228
200
  }
229
201
  }
230
202
 
231
- // --- DOWNLOAD VIDEO NATIVO REGISTRATO SU HDD VIA FFMPEG ---
203
+ // --- DOWNLOAD VIDEO REGISTRATO VIA FFMPEG ---
232
204
  const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
233
205
  const authenticatedRtspUrl = playbackUri.replace("rtsp://", `rtsp://${node.user}:${node.pass}@`);
234
206
  const ffmpegRtspCmd = `ffmpeg -y -rtsp_transport tcp -i "${authenticatedRtspUrl}" -t 15 -c:v copy -c:a aac -movflags +faststart "${fixedPath}"`;
@@ -251,7 +223,7 @@ module.exports = function(RED) {
251
223
  }
252
224
 
253
225
  } catch (e) {
254
- node.error(`Errore session-cloner: ${e.message}`);
226
+ node.error(`Errore query speculare: ${e.message}`);
255
227
  }
256
228
  updateNodeStatus();
257
229
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.108",
3
+ "version": "1.1.109",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",