camstreamerlib
Version:
Helper library for CamStreamer ACAP applications.
159 lines (158 loc) • 4.93 kB
JavaScript
import EventEmitter from 'events';
import { WsClient } from './WsClient';
export class CamScripterAPICameraEventsGenerator extends EventEmitter {
tls;
tlsInsecure;
ip;
port;
user;
pass;
callId;
sendMessages;
timeoutCheckTimer;
wsConnected;
ws;
constructor(options) {
super();
this.tls = options?.tls ?? false;
this.tlsInsecure = options?.tlsInsecure ?? false;
this.ip = options?.ip ?? '127.0.0.1';
this.port = options?.port ?? (this.tls ? 443 : 80);
this.user = options?.user ?? '';
this.pass = options?.pass ?? '';
this.callId = 0;
this.sendMessages = {};
this.wsConnected = false;
this.createWsClient();
EventEmitter.call(this);
}
connect() {
this.ws.open();
this.startMsgsTimeoutCheck();
}
disconnect() {
this.ws.destroy();
this.stopMsgsTimeoutCheck();
}
declareEvent(eventDeclaration) {
return this.sendMessage({
call_id: 0,
command: 'declare_event',
data: eventDeclaration,
});
}
undeclareEvent(eventUndeclaration) {
return this.sendMessage({
call_id: 0,
command: 'undeclare_event',
data: eventUndeclaration,
});
}
sendEvent(event) {
return this.sendMessage({
call_id: 0,
command: 'send_event',
data: event,
});
}
createWsClient() {
const options = {
user: this.user,
pass: this.pass,
tlsInsecure: this.tlsInsecure,
tls: this.tls,
ip: this.ip,
port: this.port,
address: `/local/camscripter/ws`,
protocol: 'camera-events',
};
this.ws = new WsClient(options);
this.ws.onOpen = () => {
this.wsConnected = true;
this.emit('open');
};
this.ws.onMessage = (data) => this.incomingWsMessageHandler(data.toString());
this.ws.onError = (error) => {
this.reportErr(error);
};
this.ws.onClose = () => {
this.wsConnected = false;
this.reportClose();
};
}
incomingWsMessageHandler(msgData) {
const dataJSON = JSON.parse(msgData);
let errorResponse;
if ('error' in dataJSON) {
errorResponse = dataJSON;
}
if (dataJSON.call_id !== undefined) {
if (errorResponse !== undefined) {
this.sendMessages[dataJSON.call_id]?.reject(new Error(errorResponse.error));
}
else {
this.sendMessages[dataJSON.call_id]?.resolve(dataJSON);
}
delete this.sendMessages[dataJSON.call_id];
}
if (errorResponse !== undefined) {
this.reconnectWithError(new Error(errorResponse.error));
}
}
sendMessage(msgJson) {
return new Promise((resolve, reject) => {
if (!this.wsConnected) {
throw new Error("Websocket hasn't been opened yet.");
}
try {
msgJson.call_id = ++this.callId;
this.ws.send(JSON.stringify(msgJson));
this.sendMessages[this.callId] = { resolve, reject, sentTimestamp: Date.now() };
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
const error = new Error(`Send message error: ${errorMessage}`);
reject(error);
this.reconnectWithError(error);
}
});
}
startMsgsTimeoutCheck() {
clearInterval(this.timeoutCheckTimer);
this.timeoutCheckTimer = setInterval(() => {
let reconnect = false;
const now = Date.now();
for (const callId in this.sendMessages) {
const msg = this.sendMessages[callId];
if (!msg) {
continue;
}
if (now - msg.sentTimestamp > 10000) {
reconnect = true;
msg.reject(new Error('Message timeout'));
delete this.sendMessages[callId];
}
}
if (reconnect) {
this.reconnectWithError(new Error('Message timeout'));
}
}, 5000);
}
stopMsgsTimeoutCheck() {
clearInterval(this.timeoutCheckTimer);
}
reconnectWithError(err) {
this.reportErr(err);
this.ws.reconnect();
}
reportErr(err) {
this.emit('error', err);
}
reportClose() {
for (const callId in this.sendMessages) {
this.sendMessages[callId]?.reject(new Error('Connection lost'));
}
this.sendMessages = {};
this.emit('close');
}
}