camstreamerlib 1.6.0 → 1.7.0

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.
package/CameraVapix.js ADDED
@@ -0,0 +1,328 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.CameraVapix = void 0;
13
+ const WebSocket = require("ws");
14
+ const prettifyXml = require("prettify-xml");
15
+ const xml2js_1 = require("xml2js");
16
+ const eventemitter2_1 = require("eventemitter2");
17
+ const Digest_1 = require("./Digest");
18
+ const RtspClient_1 = require("./RtspClient");
19
+ const HTTPRequest_1 = require("./HTTPRequest");
20
+ class CameraVapix extends eventemitter2_1.EventEmitter2 {
21
+ constructor(options) {
22
+ var _a, _b, _c, _d;
23
+ super();
24
+ this.rtsp = null;
25
+ this.ws = null;
26
+ this.protocol = (_a = options === null || options === void 0 ? void 0 : options.protocol) !== null && _a !== void 0 ? _a : 'html';
27
+ this.ip = (_b = options === null || options === void 0 ? void 0 : options.ip) !== null && _b !== void 0 ? _b : '127.0.0.1';
28
+ this.port = (_c = options === null || options === void 0 ? void 0 : options.port) !== null && _c !== void 0 ? _c : (this.protocol == 'http' ? 80 : 443);
29
+ this.auth = (_d = options === null || options === void 0 ? void 0 : options.auth) !== null && _d !== void 0 ? _d : '';
30
+ }
31
+ getParameterGroup(groupNames) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const response = (yield this.vapixGet(`/axis-cgi/param.cgi?action=list&group=${encodeURIComponent(groupNames)}`));
34
+ const params = {};
35
+ const lines = response.split(/[\r\n]/);
36
+ for (let i = 0; i < lines.length; i++) {
37
+ if (lines[i].length) {
38
+ const p = lines[i].split('=');
39
+ if (p.length >= 2) {
40
+ params[p[0]] = p[1];
41
+ }
42
+ }
43
+ }
44
+ return params;
45
+ });
46
+ }
47
+ setParameter(params) {
48
+ let postData = 'action=update&';
49
+ for (let key in params) {
50
+ postData += key + '=' + params[key] + '&';
51
+ }
52
+ postData = postData.slice(0, postData.length - 1);
53
+ return this.vapixPost('/axis-cgi/param.cgi', postData);
54
+ }
55
+ getPTZPresetList(channel) {
56
+ return __awaiter(this, void 0, void 0, function* () {
57
+ const response = (yield this.vapixGet(`/axis-cgi/com/ptz.cgi?query=presetposcam&camera=${encodeURIComponent(channel)}`));
58
+ const positions = [];
59
+ const lines = response.split(/[\r\n]/);
60
+ for (let line of lines) {
61
+ if (line.length > 0 && line.indexOf('presetposno') != -1) {
62
+ const p = line.split('=');
63
+ if (p.length >= 2) {
64
+ positions.push(p[1]);
65
+ }
66
+ }
67
+ }
68
+ return positions;
69
+ });
70
+ }
71
+ goToPreset(channel, presetName) {
72
+ return this.vapixPost('/axis-cgi/com/ptz.cgi', `camera=${encodeURIComponent(channel)}&gotoserverpresetname=${encodeURIComponent(presetName)}`);
73
+ }
74
+ getGuardTourList() {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ const gTourList = new Array();
77
+ const response = yield this.getParameterGroup('GuardTour');
78
+ for (let i = 0; i < 20; i++) {
79
+ const gTourBaseName = 'root.GuardTour.G' + i;
80
+ if (gTourBaseName + '.CamNbr' in response) {
81
+ const gTour = {
82
+ id: gTourBaseName,
83
+ camNbr: response[gTourBaseName + '.CamNbr'],
84
+ name: response[gTourBaseName + '.Name'],
85
+ randomEnabled: response[gTourBaseName + '.RandomEnabled'],
86
+ running: response[gTourBaseName + '.Running'],
87
+ timeBetweenSequences: response[gTourBaseName + '.TimeBetweenSequences'],
88
+ tour: [],
89
+ };
90
+ for (let j = 0; j < 100; j++) {
91
+ const tourBaseName = 'root.GuardTour.G' + i + '.Tour.T' + j;
92
+ if (tourBaseName + '.MoveSpeed' in response) {
93
+ const tour = {
94
+ moveSpeed: response[tourBaseName + '.MoveSpeed'],
95
+ position: response[tourBaseName + '.Position'],
96
+ presetNbr: response[tourBaseName + '.PresetNbr'],
97
+ waitTime: response[tourBaseName + '.WaitTime'],
98
+ waitTimeViewType: response[tourBaseName + '.WaitTimeViewType'],
99
+ };
100
+ gTour.tour.push(tour);
101
+ }
102
+ }
103
+ gTourList.push(gTour);
104
+ }
105
+ else {
106
+ break;
107
+ }
108
+ }
109
+ return gTourList;
110
+ });
111
+ }
112
+ setGuardTourEnabled(gourTourID, enable) {
113
+ const options = {};
114
+ options[gourTourID + '.Running'] = enable ? 'yes' : 'no';
115
+ return this.setParameter(options);
116
+ }
117
+ getInputState(port) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ const response = yield this.vapixPost('/axis-cgi/io/port.cgi', `checkactive=${encodeURIComponent(port)}`);
120
+ return response.split('=')[1].indexOf('active') == 0;
121
+ });
122
+ }
123
+ setOutputState(port, active) {
124
+ return this.vapixPost('/axis-cgi/io/port.cgi', `action=${encodeURIComponent(port)}:${active ? '/' : '\\'}`);
125
+ }
126
+ getApplicationList() {
127
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
128
+ const xml = (yield this.vapixGet('/axis-cgi/applications/list.cgi'));
129
+ (0, xml2js_1.parseString)(xml, (err, result) => {
130
+ if (err) {
131
+ reject(err);
132
+ return;
133
+ }
134
+ const apps = [];
135
+ for (let i = 0; i < result.reply.application.length; i++) {
136
+ apps.push(result.reply.application[i].$);
137
+ }
138
+ resolve(apps);
139
+ });
140
+ }));
141
+ }
142
+ getEventDeclarations() {
143
+ return __awaiter(this, void 0, void 0, function* () {
144
+ const data = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' +
145
+ '<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
146
+ 'xmlns:xsd="http://www.w3.org/2001/XMLSchema">' +
147
+ '<GetEventInstances xmlns="http://www.axis.com/vapix/ws/event1"/>' +
148
+ '</s:Body>' +
149
+ '</s:Envelope>';
150
+ const declarations = yield this.vapixPost('/vapix/services', data, 'application/soap+xml');
151
+ return prettifyXml(declarations);
152
+ });
153
+ }
154
+ isReservedEventName(eventName) {
155
+ return eventName == 'eventsConnect' || eventName == 'eventsDisconnect';
156
+ }
157
+ eventsConnect(channel = 'RTSP') {
158
+ if (this.ws != null) {
159
+ throw new Error('Websocket is already opened.');
160
+ }
161
+ if (this.rtsp != null) {
162
+ throw new Error('RTSP is already opened.');
163
+ }
164
+ if (channel == 'RTSP') {
165
+ this.rtspConnect();
166
+ }
167
+ else if (channel == 'websocket') {
168
+ this.websocketConnect();
169
+ }
170
+ else {
171
+ throw new Error('Unknown channel.');
172
+ }
173
+ }
174
+ eventsDisconnect() {
175
+ if (this.rtsp != null) {
176
+ this.rtsp.disconnect();
177
+ }
178
+ if (this.ws != null) {
179
+ this.ws.close();
180
+ }
181
+ }
182
+ rtspConnect() {
183
+ this.rtsp = new RtspClient_1.RtspClient({
184
+ ip: this.ip,
185
+ port: this.port,
186
+ auth: this.auth,
187
+ });
188
+ this.rtsp.on('connect', () => {
189
+ this.emit('eventsConnect');
190
+ });
191
+ this.rtsp.on('disconnect', (err) => {
192
+ this.emit('eventsDisconnect', new Error(err));
193
+ this.rtsp = null;
194
+ });
195
+ this.rtsp.on('event', (event) => {
196
+ const eventNames = this.eventNames();
197
+ for (let i = 0; i < eventNames.length; i++) {
198
+ if (!this.isReservedEventName(eventNames[i])) {
199
+ let name = eventNames[i];
200
+ while (name[name.length - 1] == '.' || name[name.length - 1] == '/') {
201
+ name = name.substring(0, name.length - 1);
202
+ }
203
+ if (event.indexOf(name) != -1) {
204
+ (0, xml2js_1.parseString)(event, (err, eventJson) => {
205
+ if (err != null) {
206
+ this.eventsDisconnect();
207
+ return;
208
+ }
209
+ this.emit(eventNames[i], eventJson);
210
+ });
211
+ break;
212
+ }
213
+ }
214
+ }
215
+ });
216
+ let eventTopicFilter = '';
217
+ const eventNames = this.eventNames();
218
+ for (let i = 0; i < eventNames.length; i++) {
219
+ if (!this.isReservedEventName(eventNames[i])) {
220
+ if (eventTopicFilter.length != 0) {
221
+ eventTopicFilter += '|';
222
+ }
223
+ let topic = eventNames[i].replace(/tns1/g, 'onvif');
224
+ topic = topic.replace(/tnsaxis/g, 'axis');
225
+ eventTopicFilter += topic;
226
+ }
227
+ }
228
+ this.rtsp.connect(eventTopicFilter);
229
+ }
230
+ websocketConnect(digestHeader) {
231
+ var _a;
232
+ const address = `ws://${this.ip}:${this.port}/vapix/ws-data-stream?sources=events`;
233
+ const options = {
234
+ auth: this.auth,
235
+ };
236
+ if (digestHeader != undefined) {
237
+ const userPass = this.auth.split(':');
238
+ (_a = options['headers']) !== null && _a !== void 0 ? _a : (options['headers'] = {});
239
+ options['headers']['Authorization'] = Digest_1.Digest.getAuthHeader(userPass[0], userPass[1], 'GET', '/vapix/ws-data-stream?sources=events', digestHeader);
240
+ }
241
+ return new Promise((resolve, reject) => {
242
+ this.ws = new WebSocket(address, options);
243
+ this.ws.on('open', () => {
244
+ const topics = [];
245
+ const eventNames = this.eventNames();
246
+ for (let i = 0; i < eventNames.length; i++) {
247
+ if (!this.isReservedEventName(eventNames[i])) {
248
+ const topic = {
249
+ topicFilter: eventNames[i],
250
+ };
251
+ topics.push(topic);
252
+ }
253
+ }
254
+ const topicFilter = {
255
+ apiVersion: '1.0',
256
+ method: 'events:configure',
257
+ params: {
258
+ eventFilterList: topics,
259
+ },
260
+ };
261
+ this.ws.send(JSON.stringify(topicFilter));
262
+ });
263
+ this.ws.on('unexpected-response', (req, res) => __awaiter(this, void 0, void 0, function* () {
264
+ if (res.statusCode == 401 && res.headers['www-authenticate'] != undefined)
265
+ this.websocketConnect(res.headers['www-authenticate']).then(resolve, reject);
266
+ else {
267
+ reject('Error: status code: ' + res.statusCode + ', ' + res.data);
268
+ }
269
+ }));
270
+ this.ws.on('message', (data) => {
271
+ const dataJSON = JSON.parse(data);
272
+ if (dataJSON.method === 'events:configure') {
273
+ if (dataJSON.error === undefined) {
274
+ this.emit('eventsConnect');
275
+ }
276
+ else {
277
+ this.emit('eventsDisconnect', dataJSON.error);
278
+ this.eventsDisconnect();
279
+ }
280
+ return;
281
+ }
282
+ const eventName = dataJSON.params.notification.topic;
283
+ this.emit(eventName, dataJSON);
284
+ });
285
+ this.ws.on('error', (error) => {
286
+ this.emit('eventsDisconnect', error);
287
+ this.ws = null;
288
+ });
289
+ this.ws.on('close', () => {
290
+ if (this.ws !== null) {
291
+ this.emit('websocketDisconnect');
292
+ }
293
+ this.ws = null;
294
+ });
295
+ });
296
+ }
297
+ vapixGet(path, noWaitForData = false) {
298
+ const options = this.getBaseVapixConnectionParams();
299
+ options.path = encodeURI(path);
300
+ return (0, HTTPRequest_1.httpRequest)(options, undefined, noWaitForData);
301
+ }
302
+ getCameraImage(camera, compression, resolution, outputStream) {
303
+ return __awaiter(this, void 0, void 0, function* () {
304
+ const path = `/axis-cgi/jpg/image.cgi?resolution=${resolution}&compression=${compression}&camera=${camera}`;
305
+ const res = (yield this.vapixGet(path, true));
306
+ res.pipe(outputStream);
307
+ return outputStream;
308
+ });
309
+ }
310
+ vapixPost(path, data, contentType) {
311
+ const options = this.getBaseVapixConnectionParams();
312
+ options.method = 'POST';
313
+ options.path = path;
314
+ if (contentType != null) {
315
+ options.headers = { 'Content-Type': contentType };
316
+ }
317
+ return (0, HTTPRequest_1.httpRequest)(options, data);
318
+ }
319
+ getBaseVapixConnectionParams() {
320
+ return {
321
+ protocol: this.protocol + ':',
322
+ host: this.ip,
323
+ port: this.port,
324
+ auth: this.auth,
325
+ };
326
+ }
327
+ }
328
+ exports.CameraVapix = CameraVapix;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const fs = require("fs");
4
+ const Path = require("path");
5
+ const AdmZip = require("adm-zip");
6
+ function isDirectory(path) {
7
+ const stat = fs.statSync(path);
8
+ return stat.isDirectory();
9
+ }
10
+ function createZipArchive(zip, folder, options) {
11
+ const files = fs.readdirSync(folder);
12
+ for (let file of files) {
13
+ const path = Path.join(folder, file);
14
+ const isDir = isDirectory(path);
15
+ if (file[0] == '.' ||
16
+ (file == 'node_modules' && !options.includeNodeModules) ||
17
+ (file == 'src' && options.typeScriptPackage)) {
18
+ continue;
19
+ }
20
+ else if (file == 'dist' && options.typeScriptPackage) {
21
+ zip.addLocalFolder(path);
22
+ }
23
+ else if (isDir) {
24
+ zip.addLocalFolder(path, file);
25
+ }
26
+ else {
27
+ zip.addLocalFile(path);
28
+ }
29
+ }
30
+ }
31
+ function main(args) {
32
+ const folder = Path.resolve('.');
33
+ const zipFile = Path.basename(folder) + '.zip';
34
+ const options = {
35
+ includeNodeModules: false,
36
+ typeScriptPackage: false,
37
+ };
38
+ for (let arg of args) {
39
+ if (arg == '-i' || arg == '-includeNodeModules') {
40
+ options.includeNodeModules = true;
41
+ }
42
+ }
43
+ if (fs.existsSync('dist')) {
44
+ options.typeScriptPackage = true;
45
+ }
46
+ if (fs.existsSync(zipFile)) {
47
+ try {
48
+ fs.unlinkSync(zipFile);
49
+ }
50
+ catch (error) {
51
+ console.log('An error occured: ', error);
52
+ process.exit(1);
53
+ }
54
+ }
55
+ const zip = new AdmZip();
56
+ createZipArchive(zip, folder, options);
57
+ zip.writeZip(zipFile);
58
+ }
59
+ main(process.argv.slice(2));
package/Digest.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare class Digest {
2
+ static getAuthHeader(user: string, pass: string, method: string, uri: string, wwwAuthenticateHeader: string): string;
3
+ }
@@ -1,41 +1,39 @@
1
- import * as crypto from 'crypto';
2
-
3
- export class Digest {
4
- static getAuthHeader(user: string, pass: string, method: string, uri: string, wwwAuthenticateHeader: string) {
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Digest = void 0;
4
+ const crypto = require("crypto");
5
+ class Digest {
6
+ static getAuthHeader(user, pass, method, uri, wwwAuthenticateHeader) {
5
7
  let digestItems = {};
6
8
  let digestArr = wwwAuthenticateHeader.substring(wwwAuthenticateHeader.indexOf('Digest') + 6).split(',');
7
-
8
9
  for (let i = 0; i < digestArr.length; i++) {
9
10
  let pos = digestArr[i].indexOf('=');
10
11
  let key = digestArr[i].substring(0, pos).trim();
11
12
  let value = digestArr[i].substring(pos + 1).trim();
12
13
  digestItems[key] = value.replace(/"/g, '');
13
14
  }
14
-
15
15
  let HA1 = crypto.createHash('md5').update(`${user}:${digestItems['realm']}:${pass}`).digest('hex');
16
16
  let HA2 = crypto.createHash('md5').update(`${method}:${uri}`).digest('hex');
17
- let response: string;
17
+ let response;
18
18
  if (digestItems['qop'] != undefined) {
19
19
  response = crypto
20
20
  .createHash('md5')
21
21
  .update(`${HA1}:${digestItems['nonce']}:00000001:162d50aa594e9648:auth:${HA2}`)
22
22
  .digest('hex');
23
- } else {
23
+ }
24
+ else {
24
25
  response = crypto.createHash('md5').update(`${HA1}:${digestItems['nonce']}:${HA2}`).digest('hex');
25
26
  }
26
-
27
- let header =
28
- 'Digest ' +
27
+ let header = 'Digest ' +
29
28
  `username="${user}",` +
30
29
  `realm="${digestItems['realm']}",` +
31
30
  `nonce="${digestItems['nonce']}",` +
32
31
  `uri="${uri}",` +
33
32
  `response="${response}"`;
34
-
35
33
  if (digestItems['qop'] != undefined) {
36
34
  header += ',qop=auth,nc=00000001,cnonce="162d50aa594e9648"';
37
35
  }
38
-
39
36
  return header;
40
37
  }
41
38
  }
39
+ exports.Digest = Digest;
@@ -0,0 +1,15 @@
1
+ /// <reference types="node" />
2
+ import * as http from 'http';
3
+ export declare type HttpRequestOptions = {
4
+ method?: string;
5
+ protocol?: string;
6
+ host: string;
7
+ port: number;
8
+ path?: string;
9
+ auth: string;
10
+ timeout?: number;
11
+ headers?: {
12
+ 'Content-Type'?: string;
13
+ };
14
+ };
15
+ export declare function httpRequest(options: HttpRequestOptions, postData?: string, noWaitForData?: boolean): Promise<string | http.IncomingMessage>;
package/HTTPRequest.js ADDED
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.httpRequest = void 0;
13
+ const http = require("http");
14
+ const https = require("https");
15
+ const Digest_1 = require("./Digest");
16
+ function httpRequest(options, postData, noWaitForData = false) {
17
+ var _a, _b;
18
+ var _c;
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ if (postData !== undefined) {
21
+ (_a = options.headers) !== null && _a !== void 0 ? _a : (options.headers = {});
22
+ (_b = (_c = options.headers)['Content-Type']) !== null && _b !== void 0 ? _b : (_c['Content-Type'] = 'application/x-www-form-urlencoded');
23
+ options.headers['Content-Length'] = Buffer.byteLength(postData);
24
+ }
25
+ let response = yield request(options, postData, undefined, noWaitForData);
26
+ if (response.resp.statusCode == 200) {
27
+ return noWaitForData ? response.resp : response.data;
28
+ }
29
+ else if (response.resp.statusCode == 401) {
30
+ if (response.resp.headers['www-authenticate'] != undefined &&
31
+ response.resp.headers['www-authenticate'].indexOf('Digest') != -1) {
32
+ response = yield request(options, postData, response.resp.headers['www-authenticate'], noWaitForData);
33
+ if (response.resp.statusCode == 200) {
34
+ return noWaitForData ? response.resp : response.data;
35
+ }
36
+ }
37
+ }
38
+ if (noWaitForData) {
39
+ throw new Error(`Error: status code: ${response.resp.statusCode}`);
40
+ }
41
+ else {
42
+ throw new Error(`Error: status code: ${response.resp.statusCode}, ${response.data}`);
43
+ }
44
+ });
45
+ }
46
+ exports.httpRequest = httpRequest;
47
+ function request(options, postData, digestHeader, noWaitForData) {
48
+ return new Promise((resolve, reject) => {
49
+ var _a, _b;
50
+ if (digestHeader != undefined) {
51
+ if (options.auth == undefined) {
52
+ reject('No credentials found');
53
+ }
54
+ const auth = options.auth.split(':');
55
+ delete options.auth;
56
+ (_a = options.method) !== null && _a !== void 0 ? _a : (options.method = 'GET');
57
+ (_b = options.headers) !== null && _b !== void 0 ? _b : (options.headers = {});
58
+ options['headers']['Authorization'] = Digest_1.Digest.getAuthHeader(auth[0], auth[1], options.method, options.path, digestHeader);
59
+ }
60
+ let client = options.protocol === 'https:' ? https : http;
61
+ let req = client
62
+ .request(options, (resp) => {
63
+ if (!noWaitForData) {
64
+ let data = '';
65
+ resp.on('data', (chunk) => {
66
+ data += chunk;
67
+ });
68
+ resp.on('end', () => {
69
+ resolve({ resp: resp, data: data });
70
+ });
71
+ }
72
+ else {
73
+ resolve({ resp: resp });
74
+ }
75
+ })
76
+ .on('error', (err) => {
77
+ reject(err);
78
+ });
79
+ if (postData != undefined) {
80
+ req.write(postData);
81
+ }
82
+ req.end();
83
+ });
84
+ }
@@ -0,0 +1,18 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import * as http from 'http';
4
+ import * as EventEmitter from 'events';
5
+ export declare type HttpServerOptions = {
6
+ port?: number;
7
+ };
8
+ declare type OnRequestCallback = (req: http.IncomingMessage, res: http.ServerResponse) => void;
9
+ export declare class HttpServer extends EventEmitter {
10
+ private port;
11
+ private registeredPaths;
12
+ private server;
13
+ private sockets;
14
+ constructor(options?: HttpServerOptions);
15
+ onRequest(path: string, callback: OnRequestCallback): void;
16
+ close(): void;
17
+ }
18
+ export {};