Initial commit

This commit is contained in:
2025-10-22 22:08:30 +02:00
commit db61d35c44
33 changed files with 1548 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/node_modules/

20
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}\\dist\\Main.js",
"args": [
// "--no-output-log"
]
}
]
}

39
README.md Normal file
View File

@@ -0,0 +1,39 @@
# NTSH Camera Runner
A process monitoring service for the Nothing to See Here installation, designed to work with the main [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control) system.
## Purpose
Camera Runner monitors and manages the camera executable process, providing:
- Automatic process startup and crash recovery
- WebSocket communication on port `6301` for integration with [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control)
- Real-time process status and output logging
- Remote restart and system reboot capabilities
## Configuration
Configuration is stored in `~/MorphixProductions/NTSHControl/CameraRunner/config.json`:
```json
{
"socketServer": {
"port": 6301
},
"executable": {
"path": "",
"arguments": []
}
}
```
## Integration
This service is controlled by the main [NTSH-Control](https://git.morphix.nl/Nothing-to-See-Here/NTSH-Control) system via WebSocket communication. The main control system connects to `127.0.0.1:6301` to monitor status and send restart commands.
## Process States
- `STARTING` - Process launching
- `RUNNING` - Process active and healthy
- `STOPPED` - Process terminated or crashed
- `PROBLEM` - Error condition detected

View File

@@ -0,0 +1,138 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigurationManager = void 0;
var fs_extra_1 = require("fs-extra");
var path_1 = require("path");
var DefaultConfiguration_1 = require("./DefaultConfiguration");
var PREFIX = '[ConfigurationManager]';
var ConfigurationManager = /** @class */ (function () {
function ConfigurationManager(Main) {
this._Main = Main;
}
ConfigurationManager.prototype.load = function () {
return __awaiter(this, void 0, void 0, function () {
var configPath, configExists, config, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
console.log(PREFIX, 'Loading');
return [4 /*yield*/, (0, fs_extra_1.ensureDir)((0, path_1.join)(this._Main.dataPath))];
case 1:
_b.sent();
configPath = (0, path_1.join)(this._Main.dataPath, 'config.json');
return [4 /*yield*/, (0, fs_extra_1.pathExists)(configPath)];
case 2:
configExists = _b.sent();
if (!!configExists) return [3 /*break*/, 4];
return [4 /*yield*/, (0, fs_extra_1.writeFile)(configPath, JSON.stringify(DefaultConfiguration_1.DefaultConfiguration, null, 4))];
case 3:
_b.sent();
this._Main.Config = DefaultConfiguration_1.DefaultConfiguration;
console.log(PREFIX, 'Written default configuration');
return [3 /*break*/, 7];
case 4: return [4 /*yield*/, (0, fs_extra_1.readJSON)(configPath)];
case 5:
config = _b.sent();
_a = this._Main;
return [4 /*yield*/, this.validateConfig(config)];
case 6:
_a.Config = _b.sent();
console.log(PREFIX, 'Loaded configuration');
_b.label = 7;
case 7: return [2 /*return*/];
}
});
});
};
ConfigurationManager.prototype.validateConfig = function (config) {
return __awaiter(this, void 0, void 0, function () {
var normalizedConfig, hasChanges, configPath;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
normalizedConfig = this.normalizeConfig(config, DefaultConfiguration_1.DefaultConfiguration);
hasChanges = JSON.stringify(config) !== JSON.stringify(normalizedConfig);
if (!hasChanges) return [3 /*break*/, 2];
this._Main.Config = normalizedConfig;
configPath = (0, path_1.join)(this._Main.dataPath, 'config.json');
return [4 /*yield*/, (0, fs_extra_1.writeFile)(configPath, JSON.stringify(normalizedConfig, null, 4))];
case 1:
_a.sent();
console.log(PREFIX, 'Configuration updated and saved');
_a.label = 2;
case 2: return [2 /*return*/, normalizedConfig];
}
});
});
};
ConfigurationManager.prototype.normalizeConfig = function (current, template) {
var _this = this;
if (template === null || template === undefined) {
return template;
}
if (typeof template !== 'object' || Array.isArray(template)) {
if (current !== null &&
current !== undefined &&
typeof current === typeof template) {
if (Array.isArray(template) && Array.isArray(current)) {
if (template.length > 0 &&
typeof template[0] === 'object' &&
!Array.isArray(template[0])) {
return current.map(function (item) {
return _this.normalizeConfig(item, template[0]);
});
}
return current;
}
return current;
}
return template;
}
var result = {};
for (var key in template) {
if (template.hasOwnProperty(key)) {
result[key] = this.normalizeConfig(current === null || current === void 0 ? void 0 : current[key], template[key]);
}
}
return result;
};
return ConfigurationManager;
}());
exports.ConfigurationManager = ConfigurationManager;
//# sourceMappingURL=ConfigurationManager.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ConfigurationManager.js","sourceRoot":"","sources":["../../src/Configuration/ConfigurationManager.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultConfiguration = void 0;
exports.DefaultConfiguration = {
socketServer: {
port: 6301,
},
executable: {
path: '',
arguments: [],
},
};
//# sourceMappingURL=DefaultConfiguration.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"DefaultConfiguration.js","sourceRoot":"","sources":["../../src/Configuration/DefaultConfiguration.ts"],"names":[],"mappings":";;;AAEa,QAAA,oBAAoB,GAAW;IAC3C,YAAY,EAAE;QACb,IAAI,EAAE,IAAI;KACV;IACD,UAAU,EAAE;QACX,IAAI,EAAE,EAAE;QACR,SAAS,EAAE,EAAE;KACb;CACD,CAAC"}

79
dist/Main.js vendored Normal file
View File

@@ -0,0 +1,79 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Main = void 0;
var path_1 = require("path");
var ConfigurationManager_1 = require("./Configuration/ConfigurationManager");
var os_1 = require("os");
var SocketServer_1 = require("./SocketServer");
var processWatcher_1 = require("./processWatcher");
var Utils_1 = require("./Utils");
var Main = /** @class */ (function () {
function Main() {
this.dataPath = (0, path_1.join)((0, os_1.homedir)(), 'MorphixProductions', 'NTSHControl', 'CameraRunner');
this.ConfigurationManager = new ConfigurationManager_1.ConfigurationManager(this);
this.SocketServer = new SocketServer_1.SocketServer(this);
this.ProcessWatcher = new processWatcher_1.ProcessWatcher(this);
}
Main.prototype.start = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.ConfigurationManager.load()];
case 1:
_a.sent();
return [4 /*yield*/, this.SocketServer.listen()];
case 2:
_a.sent();
return [4 /*yield*/, (0, Utils_1.delay)(5000)];
case 3:
_a.sent();
return [4 /*yield*/, this.ProcessWatcher.start()];
case 4:
_a.sent();
return [2 /*return*/];
}
});
});
};
return Main;
}());
exports.Main = Main;
var _Main = new Main();
_Main.start();
//# sourceMappingURL=Main.js.map

1
dist/Main.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"Main.js","sourceRoot":"","sources":["../src/Main.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6BAA4B;AAC5B,6EAG8C;AAC9C,yBAA6B;AAC7B,+CAA8C;AAC9C,mDAAkD;AAClD,iCAAgC;AAEhC;IAAA;QACC,aAAQ,GAAG,IAAA,WAAI,EACd,IAAA,YAAO,GAAE,EACT,oBAAoB,EACpB,aAAa,EACb,cAAc,CACd,CAAC;QAEF,yBAAoB,GAAG,IAAI,2CAAoB,CAAC,IAAI,CAAC,CAAC;QACtD,iBAAY,GAAG,IAAI,2BAAY,CAAC,IAAI,CAAC,CAAC;QACtC,mBAAc,GAAG,IAAI,+BAAc,CAAC,IAAI,CAAC,CAAC;IAY3C,CAAC;IARM,oBAAK,GAAX;;;;4BACC,qBAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,EAAA;;wBAAtC,SAAsC,CAAC;wBACvC,qBAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAA;;wBAAhC,SAAgC,CAAC;wBAEjC,qBAAM,IAAA,aAAK,EAAC,IAAI,CAAC,EAAA;;wBAAjB,SAAiB,CAAC;wBAElB,qBAAM,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,EAAA;;wBAAjC,SAAiC,CAAC;;;;;KAClC;IACF,WAAC;AAAD,CAAC,AAtBD,IAsBC;AAtBY,oBAAI;AAwBjB,IAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;AACzB,KAAK,CAAC,KAAK,EAAE,CAAC"}

49
dist/Reboot.js vendored Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.reboot = reboot;
var child_process_1 = require("child_process");
function reboot() {
if (process.platform === 'win32') {
return rebootWindows();
}
else if (process.platform === 'linux') {
return rebootLinux();
}
return Promise.resolve({
succeed: false,
message: 'Platform not supported',
});
}
function rebootWindows() {
return new Promise(function (resolve, reject) {
(0, child_process_1.exec)('shutdown /r /t 3', function (error, stdout, stderr) {
if (error) {
console.error("Error shutting down Windows: ".concat(error.message));
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error("Error shutting down Windows: ".concat(stderr));
return resolve({ succeed: false, message: stderr });
}
console.log("Windows shutdown command executed: ".concat(stdout));
resolve({ succeed: true });
});
});
}
function rebootLinux() {
return new Promise(function (resolve, reject) {
(0, child_process_1.exec)('shutdown -r now', function (error, stdout, stderr) {
if (error) {
console.error("Error shutting down Linux: ".concat(error.message));
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error("Error shutting down Linux: ".concat(stderr));
return resolve({ succeed: false, message: stderr });
}
console.log("Linux shutdown command executed: ".concat(stdout));
resolve({ succeed: true });
});
});
}
//# sourceMappingURL=Reboot.js.map

1
dist/Reboot.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"Reboot.js","sourceRoot":"","sources":["../src/Reboot.ts"],"names":[],"mappings":";;AAEA,wBAWC;AAbD,+CAAqC;AAErC,SAAgB,MAAM;IACrB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,aAAa,EAAE,CAAC;IACxB,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzC,OAAO,WAAW,EAAE,CAAC;IACtB,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,wBAAwB;KACjC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,aAAa;IACrB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,kBAAkB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC9C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,uCAAgC,KAAK,CAAC,OAAO,CAAE,CAC/C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,uCAAgC,MAAM,CAAE,CAAC,CAAC;gBACxD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,6CAAsC,MAAM,CAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IACnB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,iBAAiB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC7C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,qCAA8B,KAAK,CAAC,OAAO,CAAE,CAC7C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,qCAA8B,MAAM,CAAE,CAAC,CAAC;gBACtD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2CAAoC,MAAM,CAAE,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC"}

49
dist/Shutdown.js vendored Normal file
View File

@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.shutdown = shutdown;
var child_process_1 = require("child_process");
function shutdown() {
if (process.platform === 'win32') {
return shutdownWindows();
}
else if (process.platform === 'linux') {
return shutdownLinux();
}
return Promise.resolve({
succeed: false,
message: 'Platform not supported',
});
}
function shutdownWindows() {
return new Promise(function (resolve, reject) {
(0, child_process_1.exec)('shutdown /s /t 3', function (error, stdout, stderr) {
if (error) {
console.error("Error shutting down Windows: ".concat(error.message));
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error("Error shutting down Windows: ".concat(stderr));
return resolve({ succeed: false, message: stderr });
}
console.log("Windows shutdown command executed: ".concat(stdout));
resolve({ succeed: true });
});
});
}
function shutdownLinux() {
return new Promise(function (resolve, reject) {
(0, child_process_1.exec)('shutdown now', function (error, stdout, stderr) {
if (error) {
console.error("Error shutting down Linux: ".concat(error.message));
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error("Error shutting down Linux: ".concat(stderr));
return resolve({ succeed: false, message: stderr });
}
console.log("Linux shutdown command executed: ".concat(stdout));
resolve({ succeed: true });
});
});
}
//# sourceMappingURL=Shutdown.js.map

1
dist/Shutdown.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"Shutdown.js","sourceRoot":"","sources":["../src/Shutdown.ts"],"names":[],"mappings":";;AAEA,4BAWC;AAbD,+CAAqC;AAErC,SAAgB,QAAQ;IACvB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAClC,OAAO,eAAe,EAAE,CAAC;IAC1B,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzC,OAAO,aAAa,EAAE,CAAC;IACxB,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;QACtB,OAAO,EAAE,KAAK;QACd,OAAO,EAAE,wBAAwB;KACjC,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe;IACvB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,kBAAkB,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC9C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,uCAAgC,KAAK,CAAC,OAAO,CAAE,CAC/C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,uCAAgC,MAAM,CAAE,CAAC,CAAC;gBACxD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,6CAAsC,MAAM,CAAE,CAAC,CAAC;YAC5D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC;AAED,SAAS,aAAa;IACrB,OAAO,IAAI,OAAO,CACjB,UAAC,OAAO,EAAE,MAAM;QACf,IAAA,oBAAI,EAAC,cAAc,EAAE,UAAC,KAAK,EAAE,MAAM,EAAE,MAAM;YAC1C,IAAI,KAAK,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CACZ,qCAA8B,KAAK,CAAC,OAAO,CAAE,CAC7C,CAAC;gBACF,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,IAAI,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,qCAA8B,MAAM,CAAE,CAAC,CAAC;gBACtD,OAAO,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,2CAAoC,MAAM,CAAE,CAAC,CAAC;YAC1D,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;IACJ,CAAC,CACD,CAAC;AACH,CAAC"}

87
dist/SocketServer.js vendored Normal file
View File

@@ -0,0 +1,87 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketServer = void 0;
var socket_io_1 = require("socket.io");
var Reboot_1 = require("./Reboot");
var PREFIX = '[SocketServer]';
var SocketServer = /** @class */ (function () {
function SocketServer(Main) {
this._Main = Main;
this.prepare();
}
SocketServer.prototype.prepare = function () {
var _this = this;
this.socket = new socket_io_1.Server();
this.socket.on('connection', function (socket) {
socket.on('getStatus', function (callback) {
if (typeof callback !== 'function')
return;
callback(_this._Main.ProcessWatcher.getStatus());
});
socket.on('restart', function (callback) {
if (typeof callback !== 'function')
return;
callback(_this._Main.ProcessWatcher.requestRestart());
});
socket.on('reboot', function (callback) { return __awaiter(_this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (typeof callback !== 'function')
return [2 /*return*/];
_a = callback;
return [4 /*yield*/, (0, Reboot_1.reboot)()];
case 1:
_a.apply(void 0, [_b.sent()]);
return [2 /*return*/];
}
});
}); });
});
};
SocketServer.prototype.listen = function () {
var port = this._Main.Config.socketServer.port;
this.socket.listen(port);
console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port));
};
return SocketServer;
}());
exports.SocketServer = SocketServer;
//# sourceMappingURL=SocketServer.js.map

1
dist/SocketServer.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"SocketServer.js","sourceRoot":"","sources":["../src/SocketServer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAA6D;AAE7D,mCAAkC;AAElC,IAAM,MAAM,GAAG,gBAAgB,CAAC;AAChC;IAKC,sBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,8BAAO,GAAf;QAAA,iBAsCC;QArCA,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;QAEnC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,YAAY,EAAE,UAAC,MAAc;YAC3C,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,UAAC,QAAkB;gBACzC,IAAI,OAAO,QAAQ,KAAK,UAAU;oBAAE,OAAO;gBAE3C,QAAQ,CAAC,KAAI,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,EAAE,CAAC,CAAC;YACjD,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CACR,SAAS,EACT,UACC,QAGU;gBAEV,IAAI,OAAO,QAAQ,KAAK,UAAU;oBAAE,OAAO;gBAE3C,QAAQ,CAAC,KAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,EAAE,CAAC,CAAC;YACtD,CAAC,CACD,CAAC;YAEF,MAAM,CAAC,EAAE,CACR,QAAQ,EACR,UACC,QAGU;;;;;4BAEV,IAAI,OAAO,QAAQ,KAAK,UAAU;gCAAE,sBAAO;4BAE3C,KAAA,QAAQ,CAAA;4BAAC,qBAAM,IAAA,eAAM,GAAE,EAAA;;4BAAvB,kBAAS,SAAc,EAAC,CAAC;;;;iBACzB,CACD,CAAC;QACH,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,6BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC;QACjD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,mBAAC;AAAD,CAAC,AAxDD,IAwDC;AAxDY,oCAAY"}

22
dist/SocketWerver.js vendored Normal file
View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebServer = void 0;
var socket_io_1 = require("socket.io");
var PREFIX = '[WebServer]';
var WebServer = /** @class */ (function () {
function WebServer(Main) {
this._Main = Main;
this.prepare();
}
WebServer.prototype.prepare = function () {
this.socket = new socket_io_1.Server();
};
WebServer.prototype.listen = function () {
var port = this._Main.Config.webServer.port;
this.socket.listen(port);
console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port));
};
return WebServer;
}());
exports.WebServer = WebServer;
//# sourceMappingURL=SocketWerver.js.map

1
dist/SocketWerver.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"SocketWerver.js","sourceRoot":"","sources":["../src/SocketWerver.ts"],"names":[],"mappings":";;;AAAA,uCAA6D;AAG7D,IAAM,MAAM,GAAG,aAAa,CAAC;AAC7B;IAKC,mBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,2BAAO,GAAf;QACC,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;IACpC,CAAC;IAED,0BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,gBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,8BAAS"}

7
dist/Utils.js vendored Normal file
View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.delay = delay;
function delay(duration) {
return new Promise(function (resolve) { return setTimeout(resolve, duration); });
}
//# sourceMappingURL=Utils.js.map

1
dist/Utils.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"Utils.js","sourceRoot":"","sources":["../src/Utils.ts"],"names":[],"mappings":";;AAAA,sBAEC;AAFD,SAAgB,KAAK,CAAC,QAAgB;IACrC,OAAO,IAAI,OAAO,CAAC,UAAC,OAAO,IAAK,OAAA,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,EAA7B,CAA6B,CAAC,CAAC;AAChE,CAAC"}

22
dist/WebServer.js vendored Normal file
View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebServer = void 0;
var socket_io_1 = require("socket.io");
var PREFIX = '[WebServer]';
var WebServer = /** @class */ (function () {
function WebServer(Main) {
this._Main = Main;
this.prepare();
}
WebServer.prototype.prepare = function () {
this.socket = new socket_io_1.Server();
};
WebServer.prototype.listen = function () {
var port = this._Main.Config.webServer.port;
this.socket.listen(port);
console.log(PREFIX, "Listening on port http://127.0.0.1:".concat(port));
};
return WebServer;
}());
exports.WebServer = WebServer;
//# sourceMappingURL=WebServer.js.map

1
dist/WebServer.js.map vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"file":"WebServer.js","sourceRoot":"","sources":["../src/WebServer.ts"],"names":[],"mappings":";;;AAAA,uCAA6D;AAG7D,IAAM,MAAM,GAAG,aAAa,CAAC;AAC7B;IAKC,mBAAY,IAAU;QACrB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAElB,IAAI,CAAC,OAAO,EAAE,CAAC;IAChB,CAAC;IAEO,2BAAO,GAAf;QACC,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAc,EAAE,CAAC;IACpC,CAAC;IAED,0BAAM,GAAN;QACC,IAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QAC9C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,6CAAsC,IAAI,CAAE,CAAC,CAAC;IACnE,CAAC;IACF,gBAAC;AAAD,CAAC,AApBD,IAoBC;AApBY,8BAAS"}

205
dist/processWatcher.js vendored Normal file
View File

@@ -0,0 +1,205 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProcessWatcher = void 0;
var fs_extra_1 = require("fs-extra");
var child_process_1 = require("child_process");
var Utils_1 = require("./Utils");
var PREFIX = '[ProcessWatcher]';
var LOG_OUTPUT = !process.argv.includes('--no-output-log');
var ProcessWatcher = /** @class */ (function () {
function ProcessWatcher(Main) {
this.state = 'STOPPED';
this.startTime = null;
this.output = { current: [], last: [] };
this.restartTriggered = false;
this._Main = Main;
}
ProcessWatcher.prototype.requestRestart = function () {
if (this.state !== 'RUNNING')
return {
succeed: false,
message: 'Cannot restart when process is not running. It is probably restarting already.',
};
this.restart(true);
return { succeed: true };
};
ProcessWatcher.prototype.broadcastState = function () {
this._Main.SocketServer.socket.emit('simpleStatus', {
state: this.state,
message: this.message,
error: this.error,
});
};
ProcessWatcher.prototype.setInfo = function (message, error, state) {
if (state === void 0) { state = 'PROBLEM'; }
this.message = message;
this.error = error;
this.state = state;
this.broadcastState();
this.output.current.push("[".concat(new Date().toLocaleTimeString('nl-NL'), "] [System] [").concat(state, "] ").concat(message !== null && message !== void 0 ? message : error));
if (state == 'PROBLEM' || state == 'STOPPED')
console.warn(PREFIX, message !== null && message !== void 0 ? message : error);
else
console.log(PREFIX, message !== null && message !== void 0 ? message : error);
};
ProcessWatcher.prototype.restart = function () {
return __awaiter(this, arguments, void 0, function (instant) {
if (instant === void 0) { instant = false; }
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.restartTriggered)
return [2 /*return*/];
this.restartTriggered = true;
this.startTime = -1;
this.broadcastState();
if (instant)
this.setInfo('Process will restart shortly...', null, 'STOPPED');
if (!(this.process != null)) return [3 /*break*/, 2];
this.process.kill('SIGTERM');
return [4 /*yield*/, (0, Utils_1.delay)(3000)];
case 1:
_a.sent();
if (!this.process.killed && this.process.exitCode === null) {
this.process.kill('SIGKILL');
console.log(PREFIX, 'Sent SIGKILL to process.');
}
_a.label = 2;
case 2:
this.startTime = -1;
if (!!instant) return [3 /*break*/, 4];
this.setInfo("Restarting process in 5 seconds...", null, 'STOPPED');
return [4 /*yield*/, (0, Utils_1.delay)(5000)];
case 3:
_a.sent();
_a.label = 4;
case 4: return [4 /*yield*/, this.start()];
case 5:
_a.sent();
return [2 /*return*/];
}
});
});
};
ProcessWatcher.prototype.start = function () {
var _this = this;
var _a, _b;
this.output.last = __spreadArray([], this.output.current, true);
this.output.current = [];
this.startTime = Date.now();
this.restartTriggered = false;
this.broadcastState();
var path = this._Main.Config.executable.path;
if (path == null || !(0, fs_extra_1.pathExistsSync)(path)) {
this.setInfo('Executable problem', "Executable path is not set or does not exist: ".concat(path));
return;
}
var fileName = path.split(/(\/|\\)/).pop();
this.setInfo("Starting executable: ".concat(fileName), null, 'STARTING');
this.process = (0, child_process_1.spawn)(path, this._Main.Config.executable.arguments, {
stdio: 'pipe',
});
this.process.on('exit', function (code, signal) {
if (_this.restartTriggered)
return;
_this.setInfo('Process exited', "Process exited with code ".concat(code, " and signal ").concat(signal), 'STOPPED');
_this.restart();
});
this.process.on('error', function (err) {
_this.setInfo('Process error', err.message);
_this.restart();
});
(_a = this.process.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) {
var lines = data
.toString()
.trim()
.split('\n')
.filter(function (line) { return line.length > 0; });
lines.forEach(function (line) {
var formattedLine = "[".concat(new Date().toLocaleTimeString('nl-NL'), "] [").concat(fileName, "] ").concat(line);
if (LOG_OUTPUT)
console.log(PREFIX, formattedLine);
_this.output.current.push(formattedLine);
});
});
(_b = this.process.stderr) === null || _b === void 0 ? void 0 : _b.on('data', function (data) {
var lines = data
.toString()
.trim()
.split('\n')
.filter(function (line) { return line.length > 0; });
lines.forEach(function (line) {
var formattedLine = "[".concat(new Date().toLocaleTimeString('nl-NL'), "] [").concat(fileName, "] [ERROR] ").concat(line);
if (LOG_OUTPUT)
console.error(PREFIX, formattedLine);
_this.output.current.push(formattedLine);
});
});
setTimeout(function () {
var _a, _b;
if (_this.process == null ||
((_a = _this.process) === null || _a === void 0 ? void 0 : _a.killed) ||
((_b = _this.process) === null || _b === void 0 ? void 0 : _b.exitCode) != null ||
_this.restartTriggered)
return;
_this.setInfo('Running', '', 'RUNNING');
}, 5000);
};
ProcessWatcher.prototype.getStatus = function () {
return {
state: this.state,
message: this.message,
error: this.error,
startTime: this.startTime,
output: this.output,
};
};
return ProcessWatcher;
}());
exports.ProcessWatcher = ProcessWatcher;
//# sourceMappingURL=processWatcher.js.map

1
dist/processWatcher.js.map vendored Normal file

File diff suppressed because one or more lines are too long

315
package-lock.json generated Normal file
View File

@@ -0,0 +1,315 @@
{
"name": "ntshcamerarunner",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ntshcamerarunner",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^24.9.1",
"fs-extra": "^11.3.2",
"socket.io": "^4.8.1"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/fs-extra": {
"version": "11.0.4",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
"integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==",
"license": "MIT",
"dependencies": {
"@types/jsonfile": "*",
"@types/node": "*"
}
},
"node_modules/@types/jsonfile": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz",
"integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "24.9.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/fs-extra": {
"version": "11.3.2",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz",
"integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/jsonfile": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
"license": "MIT",
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"license": "MIT",
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

17
package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"name": "ntshcamerarunner",
"version": "1.0.0",
"main": "dist/Main.js",
"scripts": {
"start": "node ."
},
"author": "Mees van der Wijk - Morphix Productions",
"license": "ISC",
"description": "",
"dependencies": {
"@types/fs-extra": "^11.0.4",
"@types/node": "^24.9.1",
"fs-extra": "^11.3.2",
"socket.io": "^4.8.1"
}
}

View File

@@ -0,0 +1,113 @@
import { ensureDir, pathExists, readJSON, writeFile } from 'fs-extra';
import { Main } from '../Main';
import { join } from 'path';
import { DefaultConfiguration } from './DefaultConfiguration';
const PREFIX = '[ConfigurationManager]';
export class ConfigurationManager {
private _Main: Main;
constructor(Main: Main) {
this._Main = Main;
}
async load() {
console.log(PREFIX, 'Loading');
await ensureDir(join(this._Main.dataPath));
let configPath = join(this._Main.dataPath, 'config.json');
let configExists = await pathExists(configPath);
if (!configExists) {
await writeFile(
configPath,
JSON.stringify(DefaultConfiguration, null, 4)
);
this._Main.Config = DefaultConfiguration;
console.log(PREFIX, 'Written default configuration');
} else {
var config: Config = await readJSON(configPath);
this._Main.Config = await this.validateConfig(config);
console.log(PREFIX, 'Loaded configuration');
}
}
async validateConfig(config: Config): Promise<Config> {
const normalizedConfig: Config = this.normalizeConfig(
config,
DefaultConfiguration
);
const hasChanges =
JSON.stringify(config) !== JSON.stringify(normalizedConfig);
if (hasChanges) {
this._Main.Config = normalizedConfig;
const configPath = join(this._Main.dataPath, 'config.json');
await writeFile(
configPath,
JSON.stringify(normalizedConfig, null, 4)
);
console.log(PREFIX, 'Configuration updated and saved');
}
return normalizedConfig;
}
private normalizeConfig(current: any, template: any): any {
if (template === null || template === undefined) {
return template;
}
if (typeof template !== 'object' || Array.isArray(template)) {
if (
current !== null &&
current !== undefined &&
typeof current === typeof template
) {
if (Array.isArray(template) && Array.isArray(current)) {
if (
template.length > 0 &&
typeof template[0] === 'object' &&
!Array.isArray(template[0])
) {
return current.map((item) =>
this.normalizeConfig(item, template[0])
);
}
return current;
}
return current;
}
return template;
}
const result: any = {};
for (const key in template) {
if (template.hasOwnProperty(key)) {
result[key] = this.normalizeConfig(
current?.[key],
template[key]
);
}
}
return result;
}
}
export interface Config {
socketServer: ConfigSocketServer;
executable: ConfigExecutable;
}
interface ConfigSocketServer {
port: number;
}
interface ConfigExecutable {
path: string;
arguments: string[];
}

View File

@@ -0,0 +1,11 @@
import { Config } from './ConfigurationManager';
export const DefaultConfiguration: Config = {
socketServer: {
port: 6301,
},
executable: {
path: '',
arguments: [],
},
};

36
src/Main.ts Normal file
View File

@@ -0,0 +1,36 @@
import { join } from 'path';
import {
Config,
ConfigurationManager,
} from './Configuration/ConfigurationManager';
import { homedir } from 'os';
import { SocketServer } from './SocketServer';
import { ProcessWatcher } from './processWatcher';
import { delay } from './Utils';
export class Main {
dataPath = join(
homedir(),
'MorphixProductions',
'NTSHControl',
'CameraRunner'
);
ConfigurationManager = new ConfigurationManager(this);
SocketServer = new SocketServer(this);
ProcessWatcher = new ProcessWatcher(this);
Config: Config;
async start() {
await this.ConfigurationManager.load();
await this.SocketServer.listen();
await delay(5000);
await this.ProcessWatcher.start();
}
}
const _Main = new Main();
_Main.start();

56
src/Reboot.ts Normal file
View File

@@ -0,0 +1,56 @@
import { exec } from 'child_process';
export function reboot(): Promise<{ succeed: boolean; message?: string }> {
if (process.platform === 'win32') {
return rebootWindows();
} else if (process.platform === 'linux') {
return rebootLinux();
}
return Promise.resolve({
succeed: false,
message: 'Platform not supported',
});
}
function rebootWindows(): Promise<{ succeed: boolean; message?: string }> {
return new Promise<{ succeed: boolean; message?: string }>(
(resolve, reject) => {
exec('shutdown /r /t 3', (error, stdout, stderr) => {
if (error) {
console.error(
`Error shutting down Windows: ${error.message}`
);
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error(`Error shutting down Windows: ${stderr}`);
return resolve({ succeed: false, message: stderr });
}
console.log(`Windows shutdown command executed: ${stdout}`);
resolve({ succeed: true });
});
}
);
}
function rebootLinux(): Promise<{ succeed: boolean; message?: string }> {
return new Promise<{ succeed: boolean; message?: string }>(
(resolve, reject) => {
exec('shutdown -r now', (error, stdout, stderr) => {
if (error) {
console.error(
`Error shutting down Linux: ${error.message}`
);
return resolve({ succeed: false, message: error.message });
}
if (stderr) {
console.error(`Error shutting down Linux: ${stderr}`);
return resolve({ succeed: false, message: stderr });
}
console.log(`Linux shutdown command executed: ${stdout}`);
resolve({ succeed: true });
});
}
);
}

62
src/SocketServer.ts Normal file
View File

@@ -0,0 +1,62 @@
import { Socket, Server as SocketIOServer } from 'socket.io';
import { Main } from './Main';
import { reboot } from './Reboot';
const PREFIX = '[SocketServer]';
export class SocketServer {
private _Main: Main;
socket: SocketIOServer;
constructor(Main: Main) {
this._Main = Main;
this.prepare();
}
private prepare() {
this.socket = new SocketIOServer();
this.socket.on('connection', (socket: Socket) => {
socket.on('getStatus', (callback: Function) => {
if (typeof callback !== 'function') return;
callback(this._Main.ProcessWatcher.getStatus());
});
socket.on(
'restart',
(
callback: (response: {
succeed: boolean;
message?: string;
}) => void
) => {
if (typeof callback !== 'function') return;
callback(this._Main.ProcessWatcher.requestRestart());
}
);
socket.on(
'reboot',
async (
callback: (response: {
succeed: boolean;
message?: string;
}) => void
) => {
if (typeof callback !== 'function') return;
callback(await reboot());
}
);
});
}
listen() {
const port = this._Main.Config.socketServer.port;
this.socket.listen(port);
console.log(PREFIX, `Listening on port http://127.0.0.1:${port}`);
}
}

3
src/Utils.ts Normal file
View File

@@ -0,0 +1,3 @@
export function delay(duration: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, duration));
}

185
src/processWatcher.ts Normal file
View File

@@ -0,0 +1,185 @@
import { pathExistsSync } from 'fs-extra';
import { Main } from './Main';
import { ChildProcess, exec, spawn } from 'child_process';
import { delay } from './Utils';
const PREFIX = '[ProcessWatcher]';
const LOG_OUTPUT = !process.argv.includes('--no-output-log');
export class ProcessWatcher {
private _Main: Main;
state: CameraRunnerState = 'STOPPED';
message?: string;
error?: string;
startTime: number = null;
output: { current: string[]; last: string[] } = { current: [], last: [] };
process: ChildProcess;
constructor(Main: Main) {
this._Main = Main;
}
requestRestart(): { succeed: boolean; message?: string } {
if (this.state !== 'RUNNING')
return {
succeed: false,
message:
'Cannot restart when process is not running. It is probably restarting already.',
};
this.restart(true);
return { succeed: true };
}
broadcastState() {
this._Main.SocketServer.socket.emit('simpleStatus', {
state: this.state,
message: this.message,
error: this.error,
});
}
setInfo(
message: string,
error: string,
state: CameraRunnerState = 'PROBLEM'
) {
this.message = message;
this.error = error;
this.state = state;
this.broadcastState();
this.output.current.push(
`[${new Date().toLocaleTimeString('nl-NL')}] [System] [${state}] ${
message ?? error
}`
);
if (state == 'PROBLEM' || state == 'STOPPED')
console.warn(PREFIX, message ?? error);
else console.log(PREFIX, message ?? error);
}
private restartTriggered: boolean = false;
async restart(instant: boolean = false) {
if (this.restartTriggered) return;
this.restartTriggered = true;
this.startTime = -1;
this.broadcastState();
if (instant)
this.setInfo('Process will restart shortly...', null, 'STOPPED');
if (this.process != null) {
this.process.kill('SIGTERM');
await delay(3000);
if (!this.process.killed && this.process.exitCode === null) {
this.process.kill('SIGKILL');
console.log(PREFIX, 'Sent SIGKILL to process.');
}
}
this.startTime = -1;
if (!instant) {
this.setInfo(`Restarting process in 5 seconds...`, null, 'STOPPED');
await delay(5000);
}
await this.start();
}
start() {
this.output.last = [...this.output.current];
this.output.current = [];
this.startTime = Date.now();
this.restartTriggered = false;
this.broadcastState();
const path = this._Main.Config.executable.path;
if (path == null || !pathExistsSync(path)) {
this.setInfo(
'Executable problem',
`Executable path is not set or does not exist: ${path}`
);
return;
}
const fileName = path.split(/(\/|\\)/).pop();
this.setInfo(`Starting executable: ${fileName}`, null, 'STARTING');
this.process = spawn(path, this._Main.Config.executable.arguments, {
stdio: 'pipe',
});
this.process.on('exit', (code, signal) => {
if (this.restartTriggered) return;
this.setInfo(
'Process exited',
`Process exited with code ${code} and signal ${signal}`,
'STOPPED'
);
this.restart();
});
this.process.on('error', (err) => {
this.setInfo('Process error', err.message);
this.restart();
});
this.process.stdout?.on('data', (data) => {
const lines = data
.toString()
.trim()
.split('\n')
.filter((line) => line.length > 0);
lines.forEach((line) => {
const formattedLine = `[${new Date().toLocaleTimeString(
'nl-NL'
)}] [${fileName}] ${line}`;
if (LOG_OUTPUT) console.log(PREFIX, formattedLine);
this.output.current.push(formattedLine);
});
});
this.process.stderr?.on('data', (data) => {
const lines = data
.toString()
.trim()
.split('\n')
.filter((line) => line.length > 0);
lines.forEach((line) => {
const formattedLine = `[${new Date().toLocaleTimeString(
'nl-NL'
)}] [${fileName}] [ERROR] ${line}`;
if (LOG_OUTPUT) console.error(PREFIX, formattedLine);
this.output.current.push(formattedLine);
});
});
setTimeout(() => {
if (
this.process == null ||
this.process?.killed ||
this.process?.exitCode != null ||
this.restartTriggered
)
return;
this.setInfo('Running', '', 'RUNNING');
}, 5000);
}
getStatus() {
return {
state: this.state,
message: this.message,
error: this.error,
startTime: this.startTime,
output: this.output,
};
}
}
export type CameraRunnerState = 'RUNNING' | 'STOPPED' | 'STARTING' | 'PROBLEM';

9
tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"outDir": "dist",
"sourceMap": true
},
"include": [
"src/**/*"
],
}