camstreamerlib 1.9.1 → 2.0.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 CHANGED
@@ -10,28 +10,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.CameraVapix = void 0;
13
- const WebSocket = require("ws");
14
13
  const prettifyXml = require("prettify-xml");
15
14
  const xml2js_1 = require("xml2js");
16
15
  const eventemitter2_1 = require("eventemitter2");
17
- const Digest_1 = require("./Digest");
18
- const RtspClient_1 = require("./RtspClient");
19
- const HTTPRequest_1 = require("./HTTPRequest");
16
+ const WsClient_1 = require("./WsClient");
17
+ const HttpRequest_1 = require("./HttpRequest");
20
18
  class CameraVapix extends eventemitter2_1.EventEmitter2 {
21
19
  constructor(options) {
22
20
  var _a, _b, _c, _d, _e;
23
21
  super();
24
- this.rtsp = null;
25
22
  this.ws = null;
26
23
  this.tls = (_a = options === null || options === void 0 ? void 0 : options.tls) !== null && _a !== void 0 ? _a : false;
27
- if ((options === null || options === void 0 ? void 0 : options.tls) === undefined && (options === null || options === void 0 ? void 0 : options.protocol) !== undefined) {
28
- this.tls = options.protocol === 'https';
29
- }
30
24
  this.tlsInsecure = (_b = options === null || options === void 0 ? void 0 : options.tlsInsecure) !== null && _b !== void 0 ? _b : false;
31
25
  this.ip = (_c = options === null || options === void 0 ? void 0 : options.ip) !== null && _c !== void 0 ? _c : '127.0.0.1';
32
26
  this.port = (_d = options === null || options === void 0 ? void 0 : options.port) !== null && _d !== void 0 ? _d : (this.tls ? 443 : 80);
33
27
  this.auth = (_e = options === null || options === void 0 ? void 0 : options.auth) !== null && _e !== void 0 ? _e : '';
34
28
  }
29
+ vapixGet(path, noWaitForData = false) {
30
+ const options = this.getBaseVapixConnectionParams();
31
+ options.path = encodeURI(path);
32
+ return (0, HttpRequest_1.httpRequest)(options, undefined, noWaitForData);
33
+ }
34
+ vapixPost(path, data, contentType) {
35
+ const options = this.getBaseVapixConnectionParams();
36
+ options.method = 'POST';
37
+ options.path = path;
38
+ if (contentType != null) {
39
+ options.headers = { 'Content-Type': contentType };
40
+ }
41
+ return (0, HttpRequest_1.httpRequest)(options, data);
42
+ }
35
43
  getParameterGroup(groupNames) {
36
44
  return __awaiter(this, void 0, void 0, function* () {
37
45
  const response = (yield this.vapixGet(`/axis-cgi/param.cgi?action=list&group=${encodeURIComponent(groupNames)}`));
@@ -143,6 +151,14 @@ class CameraVapix extends eventemitter2_1.EventEmitter2 {
143
151
  });
144
152
  }));
145
153
  }
154
+ getCameraImage(camera, compression, resolution, outputStream) {
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ const path = `/axis-cgi/jpg/image.cgi?resolution=${resolution}&compression=${compression}&camera=${camera}`;
157
+ const res = (yield this.vapixGet(path, true));
158
+ res.pipe(outputStream);
159
+ return outputStream;
160
+ });
161
+ }
146
162
  getEventDeclarations() {
147
163
  return __awaiter(this, void 0, void 0, function* () {
148
164
  const data = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' +
@@ -155,173 +171,73 @@ class CameraVapix extends eventemitter2_1.EventEmitter2 {
155
171
  return prettifyXml(declarations);
156
172
  });
157
173
  }
158
- isReservedEventName(eventName) {
159
- return eventName == 'eventsConnect' || eventName == 'eventsDisconnect';
160
- }
161
- eventsConnect(channel = 'RTSP') {
174
+ eventsConnect() {
162
175
  if (this.ws != null) {
163
176
  throw new Error('Websocket is already opened.');
164
177
  }
165
- if (this.rtsp != null) {
166
- throw new Error('RTSP is already opened.');
167
- }
168
- if (channel == 'RTSP') {
169
- this.rtspConnect();
170
- }
171
- else if (channel == 'websocket') {
172
- this.websocketConnect();
173
- }
174
- else {
175
- throw new Error('Unknown channel.');
176
- }
177
- }
178
- eventsDisconnect() {
179
- if (this.rtsp != null) {
180
- this.rtsp.disconnect();
181
- }
182
- if (this.ws != null) {
183
- this.ws.close();
184
- }
185
- }
186
- rtspConnect() {
187
- this.rtsp = new RtspClient_1.RtspClient({
178
+ const options = {
179
+ tls: this.tls,
180
+ tlsInsecure: this.tlsInsecure,
181
+ auth: this.auth,
188
182
  ip: this.ip,
189
183
  port: this.port,
190
- auth: this.auth,
191
- });
192
- this.rtsp.on('connect', () => {
193
- this.emit('eventsConnect');
194
- });
195
- this.rtsp.on('disconnect', (err) => {
196
- this.emit('eventsDisconnect', new Error(err));
197
- this.rtsp = null;
198
- });
199
- this.rtsp.on('event', (event) => {
184
+ address: '/vapix/ws-data-stream?sources=events',
185
+ };
186
+ this.ws = new WsClient_1.WsClient(options);
187
+ this.ws.on('open', () => {
188
+ const topics = [];
200
189
  const eventNames = this.eventNames();
201
190
  for (let i = 0; i < eventNames.length; i++) {
202
191
  if (!this.isReservedEventName(eventNames[i])) {
203
- let name = eventNames[i];
204
- while (name[name.length - 1] == '.' || name[name.length - 1] == '/') {
205
- name = name.substring(0, name.length - 1);
206
- }
207
- if (event.indexOf(name) != -1) {
208
- (0, xml2js_1.parseString)(event, (err, eventJson) => {
209
- if (err != null) {
210
- this.eventsDisconnect();
211
- return;
212
- }
213
- this.emit(eventNames[i], eventJson);
214
- });
215
- break;
216
- }
192
+ const topic = {
193
+ topicFilter: eventNames[i],
194
+ };
195
+ topics.push(topic);
217
196
  }
218
197
  }
198
+ const topicFilter = {
199
+ apiVersion: '1.0',
200
+ method: 'events:configure',
201
+ params: {
202
+ eventFilterList: topics,
203
+ },
204
+ };
205
+ this.ws.send(JSON.stringify(topicFilter));
219
206
  });
220
- let eventTopicFilter = '';
221
- const eventNames = this.eventNames();
222
- for (let i = 0; i < eventNames.length; i++) {
223
- if (!this.isReservedEventName(eventNames[i])) {
224
- if (eventTopicFilter.length != 0) {
225
- eventTopicFilter += '|';
207
+ this.ws.on('message', (data) => {
208
+ const dataJSON = JSON.parse(data.toString());
209
+ if (dataJSON.method === 'events:configure') {
210
+ if (dataJSON.error === undefined) {
211
+ this.emit('eventsConnect');
226
212
  }
227
- let topic = eventNames[i].replace(/tns1/g, 'onvif');
228
- topic = topic.replace(/tnsaxis/g, 'axis');
229
- eventTopicFilter += topic;
230
- }
231
- }
232
- this.rtsp.connect(eventTopicFilter);
233
- }
234
- websocketConnect(digestHeader) {
235
- var _a;
236
- const protocol = this.tls ? 'wss' : 'ws';
237
- const address = `${protocol}://${this.ip}:${this.port}/vapix/ws-data-stream?sources=events`;
238
- const options = {
239
- auth: this.auth,
240
- rejectUnauthorized: !this.tlsInsecure,
241
- headers: {},
242
- };
243
- if (digestHeader != undefined) {
244
- const userPass = this.auth.split(':');
245
- (_a = options['headers']) !== null && _a !== void 0 ? _a : (options['headers'] = {});
246
- options['headers']['Authorization'] = Digest_1.Digest.getAuthHeader(userPass[0], userPass[1], 'GET', '/vapix/ws-data-stream?sources=events', digestHeader);
247
- }
248
- return new Promise((resolve, reject) => {
249
- this.ws = new WebSocket(address, options);
250
- this.ws.on('open', () => {
251
- const topics = [];
252
- const eventNames = this.eventNames();
253
- for (let i = 0; i < eventNames.length; i++) {
254
- if (!this.isReservedEventName(eventNames[i])) {
255
- const topic = {
256
- topicFilter: eventNames[i],
257
- };
258
- topics.push(topic);
259
- }
260
- }
261
- const topicFilter = {
262
- apiVersion: '1.0',
263
- method: 'events:configure',
264
- params: {
265
- eventFilterList: topics,
266
- },
267
- };
268
- this.ws.send(JSON.stringify(topicFilter));
269
- });
270
- this.ws.on('unexpected-response', (req, res) => __awaiter(this, void 0, void 0, function* () {
271
- if (res.statusCode == 401 && res.headers['www-authenticate'] != undefined)
272
- this.websocketConnect(res.headers['www-authenticate']).then(resolve, reject);
273
213
  else {
274
- reject('Error: status code: ' + res.statusCode + ', ' + res.data);
214
+ this.emit('eventsDisconnect', dataJSON.error);
215
+ this.eventsDisconnect();
275
216
  }
276
- }));
277
- this.ws.on('message', (data) => {
278
- const dataJSON = JSON.parse(data);
279
- if (dataJSON.method === 'events:configure') {
280
- if (dataJSON.error === undefined) {
281
- this.emit('eventsConnect');
282
- }
283
- else {
284
- this.emit('eventsDisconnect', dataJSON.error);
285
- this.eventsDisconnect();
286
- }
287
- return;
288
- }
289
- const eventName = dataJSON.params.notification.topic;
290
- this.emit(eventName, dataJSON);
291
- });
292
- this.ws.on('error', (error) => {
293
- this.emit('eventsDisconnect', error);
294
- this.ws = null;
295
- });
296
- this.ws.on('close', () => {
297
- if (this.ws !== null) {
298
- this.emit('websocketDisconnect');
299
- }
300
- this.ws = null;
301
- });
217
+ return;
218
+ }
219
+ const eventName = dataJSON.params.notification.topic;
220
+ this.emit(eventName, dataJSON);
302
221
  });
303
- }
304
- vapixGet(path, noWaitForData = false) {
305
- const options = this.getBaseVapixConnectionParams();
306
- options.path = encodeURI(path);
307
- return (0, HTTPRequest_1.httpRequest)(options, undefined, noWaitForData);
308
- }
309
- getCameraImage(camera, compression, resolution, outputStream) {
310
- return __awaiter(this, void 0, void 0, function* () {
311
- const path = `/axis-cgi/jpg/image.cgi?resolution=${resolution}&compression=${compression}&camera=${camera}`;
312
- const res = (yield this.vapixGet(path, true));
313
- res.pipe(outputStream);
314
- return outputStream;
222
+ this.ws.on('error', (error) => {
223
+ this.emit('eventsDisconnect', error);
224
+ this.ws = null;
225
+ });
226
+ this.ws.on('close', () => {
227
+ if (this.ws !== null) {
228
+ this.emit('eventsClose');
229
+ }
230
+ this.ws = null;
315
231
  });
232
+ this.ws.open();
316
233
  }
317
- vapixPost(path, data, contentType) {
318
- const options = this.getBaseVapixConnectionParams();
319
- options.method = 'POST';
320
- options.path = path;
321
- if (contentType != null) {
322
- options.headers = { 'Content-Type': contentType };
234
+ eventsDisconnect() {
235
+ if (this.ws != null) {
236
+ this.ws.close();
323
237
  }
324
- return (0, HTTPRequest_1.httpRequest)(options, data);
238
+ }
239
+ isReservedEventName(eventName) {
240
+ return eventName == 'eventsConnect' || eventName == 'eventsDisconnect' || eventName == 'websocketDisconnect';
325
241
  }
326
242
  getBaseVapixConnectionParams() {
327
243
  return {
package/README.md CHANGED
@@ -16,7 +16,8 @@ npm install camstreamerlib
16
16
  - [HttpServer](doc/HttpServer.md) is a module for processing HTTP requests in your scripts. It also automatically serves up the content from html directory or you can register paths which you can process by your own (e.g. http://$CAMERA_IP/local/camscripter/proxy/$MY_PACKAGE_NAME/control.cgi).
17
17
  - [CameraVapix](doc/CameraVapix.md) is a module to access Axis camera VAPIX interface.
18
18
  - [CamStreamerAPI](doc/CamStreamerAPI.md) is a module for easy control of video streaming in the CamStreamer ACAP application (RTMP, HLS, SRT and MPEG-TS protocols).
19
- - [CamOverlayAPI](doc/CamOverlayAPI.md) is a module for easy control of CamOverlay drawing API. For more details on supported video overlay drawing functions see https://camstreamer.com/camoverlay-api1
19
+ - [CamOverlayAPI](doc/CamOverlayAPI.md) is a module to access CamOverlay HTTP interface.
20
+ - [CamOverlayDrawingAPI](doc/CamOverlayDrawingAPI.md) is a module for easy control of CamOverlay drawing API. For more details on supported video overlay drawing functions see https://camstreamer.com/camoverlay-api1
20
21
  - [CamScripterAPICameraEventsGenerator](doc/CamScripterAPICameraEventsGenerator.md) is a module which allows generating events on an Axis camera. These events can be used for triggers in the Axis camera rule engine (events/actions). It is also an easy way how to integrate events and metadata in VMS systems which support Axis camera events.
21
22
 
22
23
  ## For Developers
@@ -54,3 +55,15 @@ If you need to exclude a file or directory add `-exlude` or `-e` parameter with
54
55
  "create-package": "node node_modules/camstreamerlib/CreatePackage.js -i -e=react"
55
56
  }
56
57
  ```
58
+
59
+ ### Breaking changes when moving from version 1.\*.* to 2.\*.*
60
+
61
+ - Renamed file HTTPRequest.ts to HttpRequest.ts
62
+ - Removed deprecated protocol attribute from all options objects (use tls instead).
63
+ - Removed RTSP
64
+ > Previously CameraVapix.ts supported both WebSocket and RTSP.
65
+ > Starting with version 2.0.0, it supports WebSocket only.
66
+ - ServiceID shouldn't be passed to CamOverlayAPI by the options object. Pass it as a parameter.
67
+ - Renamed CamOverlayDrawingAPI event msg to message.
68
+ - Drawing services extracted from CamOverlayAPI.ts to a separate file.
69
+ > Please read [CamOverlayAPI](doc/CamOverlayAPI.md) and [CamOverlayDrawingAPI](doc/CamOverlayDrawingAPI.md) for more information.
package/WsClient.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ import * as EventEmitter from 'events';
4
+ import { Options } from './common';
5
+ export type WsClientOptions = Options & {
6
+ address: string;
7
+ headers?: object;
8
+ pingInterval?: number;
9
+ protocol?: string;
10
+ };
11
+ export declare class WsClient extends EventEmitter {
12
+ private userPass;
13
+ private address;
14
+ private protocol;
15
+ private pingInterval;
16
+ private wsOptions;
17
+ private digestAddress;
18
+ private isAlive;
19
+ private pingTimer;
20
+ private ws;
21
+ constructor(options?: WsClientOptions);
22
+ open(digestHeader?: string): void;
23
+ send(data: Buffer | string): void;
24
+ close(): void;
25
+ private handleCloseEvent;
26
+ }
package/WsClient.js ADDED
@@ -0,0 +1,104 @@
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.WsClient = void 0;
13
+ const EventEmitter = require("events");
14
+ const WebSocket = require("ws");
15
+ const Digest_1 = require("./Digest");
16
+ class WsClient extends EventEmitter {
17
+ constructor(options) {
18
+ var _a, _b, _c, _d, _e, _f, _g;
19
+ super();
20
+ this.isAlive = true;
21
+ this.pingTimer = null;
22
+ this.ws = null;
23
+ const tls = (_a = options === null || options === void 0 ? void 0 : options.tls) !== null && _a !== void 0 ? _a : false;
24
+ const tlsInsecure = (_b = options === null || options === void 0 ? void 0 : options.tlsInsecure) !== null && _b !== void 0 ? _b : false;
25
+ const ip = (_c = options === null || options === void 0 ? void 0 : options.ip) !== null && _c !== void 0 ? _c : '127.0.0.1';
26
+ const port = (_d = options === null || options === void 0 ? void 0 : options.port) !== null && _d !== void 0 ? _d : (tls ? 443 : 80);
27
+ const auth = (_e = options === null || options === void 0 ? void 0 : options.auth) !== null && _e !== void 0 ? _e : '';
28
+ const protocol = tls ? 'wss' : 'ws';
29
+ this.address = `${protocol}://${ip}:${port}${options.address}`;
30
+ this.digestAddress = options.address;
31
+ this.pingInterval = (_f = options.pingInterval) !== null && _f !== void 0 ? _f : 30000;
32
+ this.protocol = options.protocol;
33
+ this.userPass = auth.split(':');
34
+ this.wsOptions = {
35
+ auth: options.auth,
36
+ rejectUnauthorized: !tlsInsecure,
37
+ headers: (_g = options.headers) !== null && _g !== void 0 ? _g : {},
38
+ };
39
+ }
40
+ open(digestHeader) {
41
+ if (this.protocol == undefined) {
42
+ this.ws = new WebSocket(this.address, this.wsOptions);
43
+ }
44
+ else {
45
+ this.ws = new WebSocket(this.address, this.protocol, this.wsOptions);
46
+ }
47
+ this.ws.binaryType = 'arraybuffer';
48
+ this.isAlive = true;
49
+ this.pingTimer = setInterval(() => {
50
+ if (this.ws.readyState !== this.ws.OPEN || this.isAlive === false) {
51
+ this.emit('error', new Error('Connection timeout'));
52
+ this.close();
53
+ }
54
+ else {
55
+ this.isAlive = false;
56
+ this.ws.ping();
57
+ }
58
+ }, this.pingInterval);
59
+ this.ws.on('pong', () => {
60
+ this.isAlive = true;
61
+ });
62
+ if (digestHeader !== undefined) {
63
+ this.wsOptions.headers['Authorization'] = Digest_1.Digest.getAuthHeader(this.userPass[0], this.userPass[1], 'GET', this.digestAddress, digestHeader);
64
+ }
65
+ this.ws.on('unexpected-response', (req, res) => __awaiter(this, void 0, void 0, function* () {
66
+ if (res.statusCode === 401 && res.headers['www-authenticate'] !== undefined) {
67
+ this.open(res.headers['www-authenticate']);
68
+ }
69
+ else {
70
+ const e = new Error('Error: status code: ' + res.statusCode + ', ' + res.data);
71
+ this.emit('error', e);
72
+ }
73
+ }));
74
+ this.ws.on('open', () => this.emit('open'));
75
+ this.ws.on('message', (data) => this.emit('message', data));
76
+ this.ws.on('error', (error) => this.emit('error', error));
77
+ this.ws.on('close', () => this.emit('close'));
78
+ }
79
+ send(data) {
80
+ if (this.ws.readyState === this.ws.OPEN) {
81
+ this.ws.send(data);
82
+ }
83
+ }
84
+ close() {
85
+ try {
86
+ this.handleCloseEvent();
87
+ this.ws.close();
88
+ }
89
+ catch (err) {
90
+ this.emit('error', err);
91
+ }
92
+ setTimeout(() => {
93
+ if (this.ws.readyState == this.ws.OPEN || this.ws.readyState == this.ws.CLOSING) {
94
+ this.ws.terminate();
95
+ }
96
+ }, 5000);
97
+ }
98
+ handleCloseEvent() {
99
+ this.ws.removeAllListeners();
100
+ clearInterval(this.pingTimer);
101
+ this.emit('close');
102
+ }
103
+ }
104
+ exports.WsClient = WsClient;
package/common.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export type Options = {
2
+ ip?: string;
3
+ port?: number;
4
+ auth?: string;
5
+ tls?: boolean;
6
+ tlsInsecure?: boolean;
7
+ };
package/common.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstreamerlib",
3
- "version": "1.9.1",
3
+ "version": "2.0.0",
4
4
  "description": "Helper library for CamStreamer ACAP applications.",
5
5
  "prettier": "@camstreamer/prettier-config",
6
6
  "dependencies": {
@@ -28,8 +28,8 @@
28
28
  "build": "npm-run-all clean tsc copyPackage",
29
29
  "tsc": "tsc",
30
30
  "copyPackage": "cp -f LICENSE dist/ && cp -f README.md dist/ && cp -f package.json dist/",
31
- "pretty": "prettier --write \"./src/*.{ts,tsx}\"",
32
- "pretty:check": "prettier --check \"./src/*.{ts,tsx}\"",
31
+ "pretty": "prettier --write \"./{src,doc}/*.{ts,tsx,md}\"",
32
+ "pretty:check": "prettier --check \"./{src,doc}/*.{ts,tsx,md}\"",
33
33
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
34
34
  },
35
35
  "files": [
package/RtspClient.d.ts DELETED
@@ -1,48 +0,0 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import * as EventEmitter from 'events';
4
- export type RtspClientOptions = {
5
- ip?: string;
6
- port?: number;
7
- auth?: string;
8
- };
9
- export declare class RtspClient extends EventEmitter {
10
- private ip;
11
- private port;
12
- private auth;
13
- private authorizationType;
14
- private wwwAuthenticateHeader;
15
- private sessioncookie;
16
- private clientGet;
17
- private clientPost;
18
- private inputBuffer;
19
- private state;
20
- private rtspPath;
21
- private rtspCSeq;
22
- private control;
23
- private session;
24
- private authorizationSent;
25
- private keepAliveTimer;
26
- private disconnected;
27
- private rtpMsgBuffer;
28
- constructor(options?: RtspClientOptions);
29
- connect(eventTopicFilter: string): void;
30
- disconnect(): void;
31
- closeConnection(reason: string): void;
32
- processGetMessage(data: any): void;
33
- parseRtspMessage(): {
34
- statusLine: Buffer;
35
- headers: {};
36
- headersRaw: Buffer;
37
- body: Buffer;
38
- };
39
- parseRtpMessage(): {
40
- channel: number;
41
- body: Buffer;
42
- };
43
- getInitializationMessageGet(): string;
44
- getInitializationMessagePost(): string;
45
- getRtspMessage(): string;
46
- getAuthHeader(method: string, path: string): string;
47
- sendRtspMessage(message: string): void;
48
- }