camstreamerlib 3.3.2 → 3.4.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.
@@ -20,6 +20,10 @@ export type TUploadImageResponse = {
20
20
  height: number;
21
21
  call_id: number;
22
22
  };
23
+ export type TErrorResponse = {
24
+ error: string;
25
+ call_id?: number;
26
+ };
23
27
  export type TService = {
24
28
  id: number;
25
29
  enabled: number;
@@ -55,6 +59,7 @@ export declare class CamOverlayDrawingAPI extends EventEmitter {
55
59
  private zIndex;
56
60
  private callId;
57
61
  private sendMessages;
62
+ private timeoutCheckTimer;
58
63
  private wsConnected;
59
64
  private ws;
60
65
  constructor(options?: CamOverlayDrawingOptions);
@@ -69,8 +74,12 @@ export declare class CamOverlayDrawingAPI extends EventEmitter {
69
74
  showCairoImageAbsolute(cairoImage: string, posX: number, posY: number, width: number, height: number): Promise<TCairoResponse>;
70
75
  removeImage(): Promise<TCairoResponse>;
71
76
  private createWsClient;
77
+ private incomingWsMessageHandler;
72
78
  private sendMessage;
73
79
  private sendBinaryMessage;
80
+ private startMsgsTimeoutCheck;
81
+ private stopMsgsTimeoutCheck;
82
+ private reconnectWithError;
74
83
  private reportMessage;
75
84
  private reportError;
76
85
  private reportClose;
@@ -29,9 +29,11 @@ class CamOverlayDrawingAPI extends EventEmitter {
29
29
  }
30
30
  connect() {
31
31
  this.ws.open();
32
+ this.startMsgsTimeoutCheck();
32
33
  }
33
34
  disconnect() {
34
35
  this.ws.close();
36
+ this.stopMsgsTimeoutCheck();
35
37
  }
36
38
  isConnected() {
37
39
  return this.wsConnected;
@@ -88,73 +90,119 @@ class CamOverlayDrawingAPI extends EventEmitter {
88
90
  };
89
91
  this.ws = new WsClient_1.WsClient(options);
90
92
  this.ws.on('open', () => {
93
+ console.log('CamOverlay connection opened');
91
94
  this.wsConnected = true;
92
95
  this.emit('open');
93
96
  });
94
- this.ws.on('message', (data) => {
95
- const dataJSON = JSON.parse(data.toString());
96
- if (Object.hasOwn(dataJSON, 'call_id') && dataJSON['call_id'] in this.sendMessages) {
97
- if (Object.hasOwn(dataJSON, 'error')) {
98
- this.sendMessages[dataJSON.call_id].reject(new Error(dataJSON.error));
99
- }
100
- else {
101
- this.sendMessages[dataJSON.call_id].resolve(dataJSON);
102
- }
103
- delete this.sendMessages[dataJSON['call_id']];
104
- }
105
- if (Object.hasOwn(dataJSON, 'error')) {
106
- this.reportError(new Error(dataJSON.error));
107
- }
108
- else {
109
- this.reportMessage(data.toString());
110
- }
111
- });
97
+ this.ws.on('message', (msgData) => this.incomingWsMessageHandler(msgData));
112
98
  this.ws.on('error', (error) => {
113
99
  this.reportError(error);
114
100
  });
115
101
  this.ws.on('close', () => {
102
+ console.log('CamOverlay connection closed');
116
103
  this.wsConnected = false;
117
104
  this.reportClose();
118
105
  });
119
106
  }
107
+ incomingWsMessageHandler(msgData) {
108
+ const dataJSON = JSON.parse(msgData.toString());
109
+ let errorResponse;
110
+ if ('error' in dataJSON) {
111
+ errorResponse = dataJSON;
112
+ }
113
+ if (dataJSON.call_id !== undefined && this.sendMessages[dataJSON.call_id] !== undefined) {
114
+ if (errorResponse !== undefined) {
115
+ this.sendMessages[dataJSON.call_id].reject(new Error(errorResponse.error));
116
+ }
117
+ else {
118
+ this.sendMessages[dataJSON.call_id].resolve(dataJSON);
119
+ }
120
+ delete this.sendMessages[dataJSON.call_id];
121
+ }
122
+ if (errorResponse !== undefined) {
123
+ this.reconnectWithError(new Error(errorResponse.error));
124
+ }
125
+ else {
126
+ this.reportMessage(msgData.toString());
127
+ }
128
+ }
120
129
  sendMessage(msgJson) {
121
130
  return new Promise((resolve, reject) => {
122
131
  try {
123
- this.sendMessages[this.callId] = { resolve, reject };
124
- msgJson['call_id'] = this.callId++;
125
132
  if (!this.wsConnected) {
126
133
  throw new Error('No CamOverlay connection');
127
134
  }
135
+ msgJson.call_id = ++this.callId;
128
136
  this.ws.send(JSON.stringify(msgJson));
137
+ this.sendMessages[this.callId] = { resolve, reject, sentTimestamp: Date.now() };
129
138
  }
130
139
  catch (err) {
131
- const errorMessage = err instanceof Error ? err.message : err;
132
- this.reportError(new Error(`Send message error: ${errorMessage}`));
140
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
141
+ const error = new Error(`Send message error: ${errorMessage}`);
142
+ reject(error);
143
+ if (!this.wsConnected) {
144
+ this.reportError(error);
145
+ }
146
+ else {
147
+ this.reconnectWithError(error);
148
+ }
133
149
  }
134
150
  });
135
151
  }
136
152
  sendBinaryMessage(msgJson, data) {
137
153
  return new Promise((resolve, reject) => {
138
154
  try {
139
- this.sendMessages[this.callId] = { resolve, reject };
140
- msgJson['call_id'] = this.callId++;
155
+ if (!this.wsConnected) {
156
+ throw new Error('No CamOverlay connection');
157
+ }
158
+ msgJson.call_id = ++this.callId;
141
159
  const jsonBuffer = Buffer.from(JSON.stringify(msgJson));
142
160
  const header = new ArrayBuffer(5);
143
161
  const headerView = new DataView(header);
144
162
  headerView.setInt8(0, 1);
145
163
  headerView.setInt32(1, jsonBuffer.byteLength);
146
164
  const msgBuffer = Buffer.concat([Buffer.from(header), jsonBuffer, data]);
147
- if (!this.wsConnected) {
148
- throw new Error('No CamOverlay connection');
149
- }
150
165
  this.ws.send(msgBuffer);
166
+ this.sendMessages[this.callId] = { resolve, reject, sentTimestamp: Date.now() };
151
167
  }
152
168
  catch (err) {
153
- const errorMessage = err instanceof Error ? err.message : err;
154
- this.reportError(new Error(`Send binary message error: ${errorMessage}`));
169
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
170
+ const error = new Error(`Send binary message error: ${errorMessage}`);
171
+ reject(error);
172
+ if (!this.wsConnected) {
173
+ this.reportError(error);
174
+ }
175
+ else {
176
+ this.reconnectWithError(error);
177
+ }
155
178
  }
156
179
  });
157
180
  }
181
+ startMsgsTimeoutCheck() {
182
+ clearInterval(this.timeoutCheckTimer);
183
+ this.timeoutCheckTimer = setInterval(() => {
184
+ let reconnect = false;
185
+ const now = Date.now();
186
+ for (const callId in this.sendMessages) {
187
+ const msg = this.sendMessages[callId];
188
+ if (now - msg.sentTimestamp > 10000) {
189
+ reconnect = true;
190
+ msg.reject(new Error('Message timeout'));
191
+ delete this.sendMessages[callId];
192
+ }
193
+ }
194
+ if (reconnect) {
195
+ this.reconnectWithError(new Error('Message timeout'));
196
+ }
197
+ }, 5000);
198
+ }
199
+ stopMsgsTimeoutCheck() {
200
+ clearInterval(this.timeoutCheckTimer);
201
+ }
202
+ reconnectWithError(err) {
203
+ this.reportError(err);
204
+ this.ws.reconnect();
205
+ }
158
206
  reportMessage(msg) {
159
207
  this.emit('message', msg);
160
208
  }
@@ -30,7 +30,11 @@ export type TFrameInfo = {
30
30
  };
31
31
  export type TDrawingCallback = (cod: CamOverlayDrawingAPI, cairo: string, info: TFrameInfo) => Promise<void>;
32
32
  export interface Frame {
33
+ on(event: 'open', listener: () => void): this;
34
+ on(event: 'close', listener: () => void): this;
33
35
  on(event: 'layoutChanged', listener: () => void): this;
36
+ emit(event: 'open'): boolean;
37
+ emit(event: 'close'): boolean;
34
38
  emit(event: 'layoutChanged'): boolean;
35
39
  }
36
40
  export declare class Frame extends EventEmitter {
@@ -41,10 +45,12 @@ export declare class Frame extends EventEmitter {
41
45
  protected height: number;
42
46
  private text;
43
47
  private fontColor;
48
+ private fontName?;
44
49
  private font?;
45
50
  private align;
46
51
  private textType;
47
52
  private bgColor?;
53
+ private bgImageName?;
48
54
  private bgImage?;
49
55
  private bgType?;
50
56
  private borderRadius;
@@ -26,9 +26,9 @@ class Frame extends events_1.EventEmitter {
26
26
  this.height = opt.height;
27
27
  this.setText((_b = opt.text) !== null && _b !== void 0 ? _b : '', 'A_LEFT');
28
28
  this.fontColor = (_c = opt.fontColor) !== null && _c !== void 0 ? _c : [1.0, 1.0, 1.0];
29
- this.font = opt.font;
29
+ this.fontName = opt.font;
30
30
  this.bgColor = opt.bgColor;
31
- this.bgImage = opt.bgImage;
31
+ this.bgImageName = opt.bgImage;
32
32
  this.bgType = opt.bgType;
33
33
  this.borderRadius = (_d = opt.borderRadius) !== null && _d !== void 0 ? _d : 0;
34
34
  this.borderWidth = (_e = opt.borderWidth) !== null && _e !== void 0 ? _e : 0;
@@ -62,19 +62,23 @@ class Frame extends events_1.EventEmitter {
62
62
  this.fontColor = fontColor;
63
63
  }
64
64
  setFont(fontName) {
65
- this.font = fontName;
65
+ this.fontName = fontName;
66
+ this.font = undefined;
66
67
  }
67
68
  setFontData(fontData) {
69
+ this.fontName = undefined;
68
70
  this.font = fontData;
69
71
  }
70
72
  setBgColor(color) {
71
73
  this.bgColor = color;
72
74
  }
73
75
  setBgImage(imageName, type = 'fit') {
74
- this.bgImage = imageName;
76
+ this.bgImageName = imageName;
77
+ this.bgImage = undefined;
75
78
  this.bgType = type;
76
79
  }
77
80
  setBgImageData(imageData, type = 'fit') {
81
+ this.bgImageName = undefined;
78
82
  this.bgImage = imageData;
79
83
  this.bgType = type;
80
84
  }
@@ -157,11 +161,11 @@ class Frame extends events_1.EventEmitter {
157
161
  }
158
162
  prepareResources(resourceManager) {
159
163
  return __awaiter(this, void 0, void 0, function* () {
160
- if (typeof this.bgImage === 'string') {
161
- this.bgImage = yield resourceManager.image(this.bgImage);
164
+ if (this.bgImageName !== undefined) {
165
+ this.bgImage = yield resourceManager.image(this.bgImageName);
162
166
  }
163
- if (typeof this.font === 'string') {
164
- this.font = yield resourceManager.font(this.font);
167
+ if (this.fontName !== undefined) {
168
+ this.font = yield resourceManager.font(this.fontName);
165
169
  }
166
170
  });
167
171
  }
@@ -171,7 +175,7 @@ class Frame extends events_1.EventEmitter {
171
175
  return;
172
176
  }
173
177
  const promises = new Array();
174
- if (this.font !== undefined && typeof this.font !== 'string') {
178
+ if (this.font !== undefined) {
175
179
  promises.push(cod.cairo('cairo_set_font_face', cairo, this.font.var));
176
180
  }
177
181
  else {
@@ -230,7 +234,7 @@ class Frame extends events_1.EventEmitter {
230
234
  }
231
235
  drawImage(cod, cairo, scale, ppX, ppY) {
232
236
  return __awaiter(this, void 0, void 0, function* () {
233
- if (this.bgImage === undefined || typeof this.bgImage === 'string') {
237
+ if (this.bgImage === undefined) {
234
238
  return;
235
239
  }
236
240
  const bgImage = this.bgImage.var;
@@ -46,6 +46,12 @@ class Painter extends Frame_1.Frame {
46
46
  connect() {
47
47
  this.cod.on('open', () => {
48
48
  this.rm.clear();
49
+ this.layers = [];
50
+ this.refreshLayers = true;
51
+ this.emit('open');
52
+ });
53
+ this.cod.on('close', () => {
54
+ this.emit('close');
49
55
  });
50
56
  this.cod.on('error', (err) => {
51
57
  console.error('Painter:', err);
@@ -1,14 +1,14 @@
1
1
  import { CamOverlayDrawingAPI, TUploadImageResponse, TCairoCreateResponse } from '../CamOverlayDrawingAPI';
2
2
  export default class ResourceManager {
3
3
  private co;
4
- private imgFiles;
5
- private fontFiles;
4
+ private imgFileNames;
5
+ private fontFileNames;
6
6
  private images;
7
7
  private fonts;
8
8
  constructor(co: CamOverlayDrawingAPI);
9
- image(moniker: string): Promise<TUploadImageResponse>;
10
- font(moniker: string): Promise<TCairoCreateResponse>;
11
9
  registerImage(moniker: string, fileName: string): void;
12
10
  registerFont(moniker: string, fileName: string): void;
11
+ image(moniker: string): Promise<TUploadImageResponse>;
12
+ font(moniker: string): Promise<TCairoCreateResponse>;
13
13
  clear(): void;
14
14
  }
@@ -13,18 +13,24 @@ const fs = require("fs/promises");
13
13
  class ResourceManager {
14
14
  constructor(co) {
15
15
  this.co = co;
16
- this.imgFiles = {};
17
- this.fontFiles = {};
16
+ this.imgFileNames = {};
17
+ this.fontFileNames = {};
18
18
  this.images = {};
19
19
  this.fonts = {};
20
20
  }
21
+ registerImage(moniker, fileName) {
22
+ this.imgFileNames[moniker] = process.env.INSTALL_PATH + '/images/' + fileName;
23
+ }
24
+ registerFont(moniker, fileName) {
25
+ this.fontFileNames[moniker] = process.env.INSTALL_PATH + '/fonts/' + fileName;
26
+ }
21
27
  image(moniker) {
22
28
  return __awaiter(this, void 0, void 0, function* () {
23
29
  if (moniker in this.images) {
24
30
  return this.images[moniker];
25
31
  }
26
- else if (moniker in this.imgFiles) {
27
- const imgData = yield fs.readFile(this.imgFiles[moniker]);
32
+ else if (moniker in this.imgFileNames) {
33
+ const imgData = yield fs.readFile(this.imgFileNames[moniker]);
28
34
  this.images[moniker] = yield this.co.uploadImageData(imgData);
29
35
  return this.images[moniker];
30
36
  }
@@ -38,8 +44,8 @@ class ResourceManager {
38
44
  if (moniker in this.fonts) {
39
45
  return this.fonts[moniker];
40
46
  }
41
- else if (moniker in this.fontFiles) {
42
- const fontData = yield fs.readFile(this.fontFiles[moniker]);
47
+ else if (moniker in this.fontFileNames) {
48
+ const fontData = yield fs.readFile(this.fontFileNames[moniker]);
43
49
  this.fonts[moniker] = yield this.co.uploadFontData(fontData);
44
50
  return this.fonts[moniker];
45
51
  }
@@ -48,12 +54,6 @@ class ResourceManager {
48
54
  }
49
55
  });
50
56
  }
51
- registerImage(moniker, fileName) {
52
- this.imgFiles[moniker] = process.env.INSTALL_PATH + '/images/' + fileName;
53
- }
54
- registerFont(moniker, fileName) {
55
- this.fontFiles[moniker] = process.env.INSTALL_PATH + '/fonts/' + fileName;
56
- }
57
57
  clear() {
58
58
  this.images = {};
59
59
  this.fonts = {};
@@ -35,6 +35,10 @@ export type TResponse = {
35
35
  call_id: number;
36
36
  message: string;
37
37
  };
38
+ export type TErrorResponse = {
39
+ error: string;
40
+ call_id?: number;
41
+ };
38
42
  export interface CamScripterAPICameraEventsGenerator {
39
43
  on(event: 'open', listener: () => void): this;
40
44
  on(event: 'close', listener: () => void): this;
@@ -53,6 +57,7 @@ export declare class CamScripterAPICameraEventsGenerator extends EventEmitter {
53
57
  private camScripterAcapName;
54
58
  private callId;
55
59
  private sendMessages;
60
+ private timeoutCheckTimer;
56
61
  private wsConnected;
57
62
  private ws;
58
63
  constructor(options?: CamScripterOptions);
@@ -62,7 +67,11 @@ export declare class CamScripterAPICameraEventsGenerator extends EventEmitter {
62
67
  undeclareEvent(eventUndeclaration: TEventUndeclaration): Promise<TResponse>;
63
68
  sendEvent(event: TEvent): Promise<TResponse>;
64
69
  private createWsClient;
70
+ private incomingWsMessageHandler;
65
71
  private sendMessage;
72
+ private startMsgsTimeoutCheck;
73
+ private stopMsgsTimeoutCheck;
74
+ private reconnectWithError;
66
75
  private reportErr;
67
76
  private reportClose;
68
77
  }
@@ -22,9 +22,11 @@ class CamScripterAPICameraEventsGenerator extends EventEmitter {
22
22
  }
23
23
  connect() {
24
24
  this.ws.open();
25
+ this.startMsgsTimeoutCheck();
25
26
  }
26
27
  disconnect() {
27
28
  this.ws.close();
29
+ this.stopMsgsTimeoutCheck();
28
30
  }
29
31
  declareEvent(eventDeclaration) {
30
32
  return this.sendMessage({
@@ -63,21 +65,7 @@ class CamScripterAPICameraEventsGenerator extends EventEmitter {
63
65
  this.wsConnected = true;
64
66
  this.emit('open');
65
67
  });
66
- this.ws.on('message', (data) => {
67
- const dataJSON = JSON.parse(data.toString());
68
- if (Object.hasOwn(dataJSON, 'call_id') && dataJSON['call_id'] in this.sendMessages) {
69
- if (Object.hasOwn(dataJSON, 'error')) {
70
- this.sendMessages[dataJSON['call_id']].reject(new Error(dataJSON.error));
71
- }
72
- else {
73
- this.sendMessages[dataJSON['call_id']].resolve(dataJSON);
74
- }
75
- delete this.sendMessages[dataJSON['call_id']];
76
- }
77
- if (Object.hasOwn(dataJSON, 'error')) {
78
- this.reportErr(new Error(dataJSON.error));
79
- }
80
- });
68
+ this.ws.on('message', (msgData) => this.incomingWsMessageHandler(msgData));
81
69
  this.ws.on('error', (error) => {
82
70
  this.reportErr(error);
83
71
  });
@@ -86,21 +74,68 @@ class CamScripterAPICameraEventsGenerator extends EventEmitter {
86
74
  this.reportClose();
87
75
  });
88
76
  }
77
+ incomingWsMessageHandler(msgData) {
78
+ const dataJSON = JSON.parse(msgData.toString());
79
+ let errorResponse;
80
+ if ('error' in dataJSON) {
81
+ errorResponse = dataJSON;
82
+ }
83
+ if (dataJSON.call_id !== undefined && this.sendMessages[dataJSON.call_id] !== undefined) {
84
+ if (errorResponse !== undefined) {
85
+ this.sendMessages[dataJSON.call_id].reject(new Error(errorResponse.error));
86
+ }
87
+ else {
88
+ this.sendMessages[dataJSON.call_id].resolve(dataJSON);
89
+ }
90
+ delete this.sendMessages[dataJSON.call_id];
91
+ }
92
+ if (errorResponse !== undefined) {
93
+ this.reconnectWithError(new Error(errorResponse.error));
94
+ }
95
+ }
89
96
  sendMessage(msgJson) {
90
97
  return new Promise((resolve, reject) => {
91
98
  if (!this.wsConnected) {
92
99
  throw new Error("Websocket hasn't been opened yet.");
93
100
  }
94
101
  try {
95
- this.sendMessages[this.callId] = { resolve, reject };
96
- msgJson.call_id = this.callId++;
102
+ msgJson.call_id = ++this.callId;
97
103
  this.ws.send(JSON.stringify(msgJson));
104
+ this.sendMessages[this.callId] = { resolve, reject, sentTimestamp: Date.now() };
98
105
  }
99
106
  catch (err) {
100
- this.reportErr(new Error(`Send message error: ${err}`));
107
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error';
108
+ const error = new Error(`Send message error: ${errorMessage}`);
109
+ reject(error);
110
+ this.reconnectWithError(error);
101
111
  }
102
112
  });
103
113
  }
114
+ startMsgsTimeoutCheck() {
115
+ clearInterval(this.timeoutCheckTimer);
116
+ this.timeoutCheckTimer = setInterval(() => {
117
+ let reconnect = false;
118
+ const now = Date.now();
119
+ for (const callId in this.sendMessages) {
120
+ const msg = this.sendMessages[callId];
121
+ if (now - msg.sentTimestamp > 10000) {
122
+ reconnect = true;
123
+ msg.reject(new Error('Message timeout'));
124
+ delete this.sendMessages[callId];
125
+ }
126
+ }
127
+ if (reconnect) {
128
+ this.reconnectWithError(new Error('Message timeout'));
129
+ }
130
+ }, 5000);
131
+ }
132
+ stopMsgsTimeoutCheck() {
133
+ clearInterval(this.timeoutCheckTimer);
134
+ }
135
+ reconnectWithError(err) {
136
+ this.reportErr(err);
137
+ this.ws.reconnect();
138
+ }
104
139
  reportErr(err) {
105
140
  this.emit('error', err);
106
141
  }
@@ -34,5 +34,6 @@ export declare class WsClient extends EventEmitter {
34
34
  open(wwwAuthenticateHeader?: string): void;
35
35
  send(data: Buffer | string): void;
36
36
  close(): void;
37
+ reconnect(): void;
37
38
  private closeWsConnection;
38
39
  }
@@ -12,7 +12,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.WsClient = void 0;
13
13
  const EventEmitter = require("events");
14
14
  const WebSocket = require("ws");
15
- const timersPromises = require("timers/promises");
16
15
  const Digest_1 = require("./Digest");
17
16
  class WsClient extends EventEmitter {
18
17
  constructor(options) {
@@ -38,54 +37,63 @@ class WsClient extends EventEmitter {
38
37
  };
39
38
  }
40
39
  open(wwwAuthenticateHeader) {
41
- if (this.ws !== undefined) {
42
- return;
43
- }
44
- this.isClosed = false;
45
- if (this.protocol === undefined) {
46
- this.ws = new WebSocket(this.address, this.wsOptions);
47
- }
48
- else {
49
- this.ws = new WebSocket(this.address, this.protocol, this.wsOptions);
50
- }
51
- this.ws.binaryType = 'arraybuffer';
52
- this.isAlive = true;
53
- this.pingTimer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
54
- var _a;
55
- if ((this.ws && this.ws.readyState !== WebSocket.OPEN) || this.isAlive === false) {
56
- this.emit('error', new Error('Connection timeout'));
57
- yield this.closeWsConnection();
40
+ try {
41
+ if (this.ws !== undefined) {
42
+ return;
43
+ }
44
+ this.isClosed = false;
45
+ if (this.protocol === undefined) {
46
+ this.ws = new WebSocket(this.address, this.wsOptions);
58
47
  }
59
48
  else {
60
- this.isAlive = false;
61
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.ping();
49
+ this.ws = new WebSocket(this.address, this.protocol, this.wsOptions);
62
50
  }
63
- }), this.pingInterval);
64
- this.ws.on('pong', () => {
51
+ this.ws.binaryType = 'arraybuffer';
65
52
  this.isAlive = true;
66
- });
67
- if (wwwAuthenticateHeader !== undefined) {
68
- this.wsOptions.headers['Authorization'] = new Digest_1.Digest().getAuthHeader(this.user, this.pass, 'GET', this.digestAddress, wwwAuthenticateHeader);
69
- }
70
- this.ws.on('unexpected-response', (req, res) => __awaiter(this, void 0, void 0, function* () {
71
- var _b;
72
- if (res.statusCode === 401 && res.headers['www-authenticate'] !== undefined) {
73
- if (this.pingTimer) {
74
- clearInterval(this.pingTimer);
53
+ this.pingTimer = setInterval(() => __awaiter(this, void 0, void 0, function* () {
54
+ var _a;
55
+ if ((this.ws && this.ws.readyState !== WebSocket.OPEN) || this.isAlive === false) {
56
+ this.emit('error', new Error('Connection timeout'));
57
+ yield this.closeWsConnection();
75
58
  }
76
- (_b = this.ws) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
77
- this.ws = undefined;
78
- this.open(res.headers['www-authenticate']);
79
- }
80
- else {
81
- this.emit('error', new Error('Status code: ' + res.statusCode));
82
- yield this.closeWsConnection();
59
+ else {
60
+ this.isAlive = false;
61
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.ping();
62
+ }
63
+ }), this.pingInterval);
64
+ this.ws.on('pong', () => {
65
+ this.isAlive = true;
66
+ });
67
+ if (wwwAuthenticateHeader !== undefined) {
68
+ this.wsOptions.headers['Authorization'] = new Digest_1.Digest().getAuthHeader(this.user, this.pass, 'GET', this.digestAddress, wwwAuthenticateHeader);
83
69
  }
84
- }));
85
- this.ws.on('open', () => this.emit('open'));
86
- this.ws.on('message', (data) => this.emit('message', data));
87
- this.ws.on('error', (error) => this.emit('error', error));
88
- this.ws.on('close', () => this.closeWsConnection());
70
+ this.ws.on('unexpected-response', (req, res) => __awaiter(this, void 0, void 0, function* () {
71
+ var _b;
72
+ if (res.statusCode === 401 && res.headers['www-authenticate'] !== undefined) {
73
+ if (this.pingTimer) {
74
+ clearInterval(this.pingTimer);
75
+ }
76
+ (_b = this.ws) === null || _b === void 0 ? void 0 : _b.removeAllListeners();
77
+ this.ws = undefined;
78
+ this.open(res.headers['www-authenticate']);
79
+ }
80
+ else {
81
+ this.emit('error', new Error('Status code: ' + res.statusCode));
82
+ yield this.closeWsConnection();
83
+ }
84
+ }));
85
+ this.ws.on('open', () => this.emit('open'));
86
+ this.ws.on('message', (data) => this.emit('message', data));
87
+ this.ws.on('error', (error) => {
88
+ this.emit('error', error);
89
+ this.closeWsConnection();
90
+ });
91
+ this.ws.on('close', () => this.closeWsConnection());
92
+ }
93
+ catch (error) {
94
+ this.emit('error', error instanceof Error ? error : new Error('Unknown error'));
95
+ this.closeWsConnection();
96
+ }
89
97
  }
90
98
  send(data) {
91
99
  if (this.ws === undefined) {
@@ -100,41 +108,45 @@ class WsClient extends EventEmitter {
100
108
  return;
101
109
  }
102
110
  this.isClosed = true;
103
- const currentWs = this.ws;
104
- this.closeWsConnection().catch((err) => {
105
- console.error(err);
106
- });
107
- setTimeout(() => {
108
- if (currentWs && currentWs.readyState !== WebSocket.CLOSED) {
109
- currentWs.terminate();
110
- }
111
- }, 5000);
111
+ this.closeWsConnection();
112
+ }
113
+ reconnect() {
114
+ this.closeWsConnection();
112
115
  }
113
116
  closeWsConnection() {
114
- return __awaiter(this, void 0, void 0, function* () {
115
- try {
116
- if (this.ws === undefined) {
117
- return;
118
- }
119
- this.ws.removeAllListeners();
120
- if (this.pingTimer) {
121
- clearInterval(this.pingTimer);
122
- }
123
- if (this.ws.readyState !== WebSocket.CONNECTING &&
124
- this.ws.readyState !== WebSocket.CLOSING &&
125
- this.ws.readyState !== WebSocket.CLOSED) {
126
- this.ws.close();
127
- }
128
- this.ws = undefined;
129
- this.emit('close');
130
- if (!this.isClosed) {
131
- yield timersPromises.setTimeout(10000);
132
- yield this.open();
133
- }
117
+ if (this.ws === undefined) {
118
+ return;
119
+ }
120
+ const wsCopy = this.ws;
121
+ this.ws = undefined;
122
+ try {
123
+ if (this.pingTimer) {
124
+ clearInterval(this.pingTimer);
134
125
  }
135
- catch (err) {
126
+ wsCopy.removeAllListeners();
127
+ wsCopy.on('error', () => { });
128
+ if (wsCopy.readyState !== WebSocket.CLOSING && wsCopy.readyState !== WebSocket.CLOSED) {
129
+ wsCopy.close();
136
130
  }
137
- });
131
+ setTimeout(() => {
132
+ if (wsCopy.readyState !== WebSocket.CLOSED) {
133
+ wsCopy.terminate();
134
+ }
135
+ }, 5000);
136
+ this.emit('close');
137
+ }
138
+ catch (err) {
139
+ console.error(err);
140
+ }
141
+ finally {
142
+ const shouldRestart = !this.isClosed;
143
+ setTimeout(() => {
144
+ wsCopy.removeAllListeners();
145
+ if (shouldRestart && !this.isClosed) {
146
+ this.open();
147
+ }
148
+ }, 10000);
149
+ }
138
150
  }
139
151
  }
140
152
  exports.WsClient = WsClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstreamerlib",
3
- "version": "3.3.2",
3
+ "version": "3.4.0",
4
4
  "description": "Helper library for CamStreamer ACAP applications.",
5
5
  "prettier": "@camstreamer/prettier-config",
6
6
  "dependencies": {