diff --git a/asd.html b/asd.html
index 87006d4..491c9e2 100644
--- a/asd.html
+++ b/asd.html
@@ -1,103 +1,313 @@
-
-
-
- ${name}
-
- ${name}

-
-
-
-
+
+
+
+
+
+ Camera: ${name}
+
+
+
+
+
+ Camera: ${name}
+
+
+
+

+
+
+
+
+
+
+
+
+
diff --git a/cmd/bin.ts b/cmd/bin.ts
index e4f03f2..9c23d9e 100644
--- a/cmd/bin.ts
+++ b/cmd/bin.ts
@@ -68,7 +68,7 @@ yargs(hideBin(process.argv))
if (majorVersion < 16) {
logger.error(`Node version ${majorVersion} is not supported, may malfunction`);
}
- if (argv.discovery_ip !== undefined) {
+ if (argv.discovery_ip !== undefined && typeof argv.discovery_ip === 'string') {
config.discovery_ips = [argv.discovery_ip];
}
pair({ ssid: argv.ssid, password: argv.password });
diff --git a/discovery.ts b/discovery.ts
index 2ec5a70..8a5d6a9 100644
--- a/discovery.ts
+++ b/discovery.ts
@@ -5,54 +5,101 @@ import { Commands } from "./datatypes.js";
import { create_LanSearch, parse_PunchPkt } from "./impl.js";
import { logger } from "./logger.js";
import { config } from "./settings.js";
+import { DevSerial } from "./impl.js";
const handleIncomingPunch = (msg: Buffer, ee: EventEmitter, rinfo: RemoteInfo) => {
- const ab = new Uint8Array(msg).buffer;
- const dv = new DataView(ab);
- const cmd_id = dv.readU16();
- if (cmd_id != Commands.PunchPkt) {
- return;
- }
- if (config.blacklisted_ips.indexOf(rinfo.address) !== -1) {
- logger.debug(`Dropping packet of blacklisted IP: ${rinfo.address}`);
- return;
- }
- logger.debug("Received a PunchPkt message");
- ee.emit("discover", rinfo, parse_PunchPkt(dv));
+ const ab = new Uint8Array(msg).buffer;
+ const dv = new DataView(ab);
+ const cmd_id = dv.readU16();
+ if (cmd_id != Commands.PunchPkt) {
+ return;
+ }
+ if (config.blacklisted_ips.indexOf(rinfo.address) !== -1) {
+ logger.debug(`Dropping packet of blacklisted IP: ${rinfo.address}`);
+ return;
+ }
+ logger.debug("Received a PunchPkt message");
+ ee.emit("discover", rinfo, parse_PunchPkt(dv));
};
export const discoverDevices = (discovery_ips: string[]): EventEmitter => {
- const sock = createSocket("udp4");
- const SEND_PORT = 32108;
- const ee = new EventEmitter();
-
- sock.on("error", (err) => {
- console.error(`sock error:\n${err.stack}`);
- sock.close();
- });
-
- sock.on("message", (msg, rinfo) => handleIncomingPunch(msg, ee, rinfo));
-
- let timers = [];
- sock.on("listening", () => {
- let ls_buf = create_LanSearch();
- sock.setBroadcast(true);
- discovery_ips.forEach((discovery_ip) => {
- logger.info(`Searching for devices on ${discovery_ip}`);
-
- let int = setInterval(() => {
- logger.log("trace", `>> LanSearch [${discovery_ip}]`);
- sock.send(new Uint8Array(ls_buf.buffer), SEND_PORT, discovery_ip);
- }, 3000);
- timers.push(int);
- logger.log("trace", `>> LanSearch [${discovery_ip}]`);
- sock.send(new Uint8Array(ls_buf.buffer), SEND_PORT, discovery_ip);
+ const sock = createSocket("udp4");
+ const SEND_PORT = 32108;
+ const ee = new EventEmitter();
+ let devicesDiscovered: Record = {}; // Track discovered devices
+ let discoveryRunning = true; // Flag to control discovery
+ let discoveryInterval: NodeJS.Timeout | null = null; // Store the interval ID
+ let discoveryTimeout: NodeJS.Timeout | null = null; // Timeout to stop discovery after 10 seconds
+
+ sock.on("error", (err) => {
+ console.error(`sock error:\n${err.stack}`);
+ sock.close();
});
- });
- sock.bind();
+ sock.on("message", (msg, rinfo) => handleIncomingPunch(msg, ee, rinfo));
- sock.on("close", () => timers.forEach((timer) => clearInterval(timer)));
- ee.on("close", () => sock.close());
- return ee;
-};
+ sock.on("listening", () => {
+ let ls_buf = create_LanSearch();
+ sock.setBroadcast(true);
+
+ const sendLanSearch = () => {
+ if (!discoveryRunning) {
+ return; // Stop if discovery is no longer running
+ }
+ discovery_ips.forEach((discovery_ip) => {
+ logger.log("trace", `>> LanSearch [${discovery_ip}]`);
+ sock.send(new Uint8Array(ls_buf.buffer), SEND_PORT, discovery_ip);
+ });
+ };
+
+ discovery_ips.forEach((discovery_ip) => {
+ logger.info(`Searching for devices on ${discovery_ip}`);
+ });
+
+ discoveryInterval = setInterval(sendLanSearch, 3000);
+ sendLanSearch(); // Send the first LanSearch immediately
+
+ // Stop discovery after 10 seconds
+ discoveryTimeout = setTimeout(() => {
+ discoveryRunning = false;
+ if (discoveryInterval) {
+ clearInterval(discoveryInterval);
+ }
+ sock.close();
+ logger.info("Discovery process stopped after 10 seconds.");
+ }, 10000);
+ });
+
+ sock.bind();
+
+ sock.on("close", () => {
+ if (discoveryInterval) {
+ clearInterval(discoveryInterval);
+ }
+ if (discoveryTimeout) {
+ clearTimeout(discoveryTimeout);
+ }
+ });
+
+ ee.on("close", () => {
+ discoveryRunning = false;
+ if (discoveryInterval) {
+ clearInterval(discoveryInterval);
+ }
+ if (discoveryTimeout) {
+ clearTimeout(discoveryTimeout);
+ }
+ sock.close();
+ });
+
+ ee.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
+ if (devicesDiscovered[dev.devId]) {
+ logger.info(`Camera ${dev.devId} at ${rinfo.address} already discovered, ignoring`);
+ } else {
+ devicesDiscovered[dev.devId] = true;
+ logger.info(`Discovered new camera: ${dev.devId} at ${rinfo.address}`);
+ }
+ });
+
+ return ee;
+};
\ No newline at end of file
diff --git a/http_server.ts b/http_server.ts
index eba7cc4..f8cff3b 100644
--- a/http_server.ts
+++ b/http_server.ts
@@ -34,23 +34,30 @@ const cameraName = (id: string): string => config.cameras[id].alias || id;
export const serveHttp = (port: number) => {
const server = http.createServer((req, res) => {
if (req.url.startsWith("/ui/")) {
- let devId = req.url.split("/")[2];
- let s = sessions[devId];
- if (s === undefined) {
+ const url = new URL(req.url, `http://${req.headers.host}`);
+ const devId = url.pathname.split("/")[2];
+ const session = sessions[devId];
+
+ if (!session) {
res.writeHead(400);
- res.end("invalid ID");
+ res.end("Invalid ID");
return;
}
- if (!s.connected) {
+
+ if (!session.connected) {
res.writeHead(400);
- res.end("Nothing online");
+ res.end("Camera is offline");
return;
}
+
+ const cameraData = config.cameras[devId];
const ui = html_template
.toString()
.replace(/\${id}/g, devId)
.replace(/\${name}/g, cameraName(devId))
- .replace(/\${audio}/g, config.cameras[devId].audio ? "true" : "false");
+ .replace(/\${audio}/g, cameraData.audio ? "true" : "false");
+
+ res.writeHead(200, { "Content-Type": "text/html" });
res.end(ui);
return;
}
@@ -118,17 +125,289 @@ export const serveHttp = (port: number) => {
responses[devId] = responses[devId].filter((r) => r !== res);
logger.info(`Video stream closed for camera ${devId}`);
});
+ } else if (req.url.startsWith("/discover")) {
+ logger.info("Discovery triggered by client.");
+ const devEv = discoverDevices(config.discovery_ips);
+
+ devEv.on("discover", (rinfo: RemoteInfo, dev: DevSerial) => {
+ logger.info(`Discovered camera ${dev.devId} at ${rinfo.address}`);
+ });
+
+ devEv.on("close", () => {
+ logger.info("Discovery process completed.");
+ });
+
+ res.writeHead(200, { "Content-Type": "application/json" });
+ res.end(JSON.stringify({ message: "Discovery started for 10 seconds." }));
+ return;
} else {
res.write("");
res.write("");
res.write(``);
res.write("All cameras");
+ res.write(`
+
+ `);
res.write("");
res.write("");
- res.write("All cameras
");
- Object.keys(sessions).forEach((id) =>
- res.write(`${cameraName(id)}

`),
- );
+ res.write(`
+
+ All Cameras
+
+
+
+
+
+
+
+
+ `);
+
res.write("");
res.write("");
res.end();
@@ -164,10 +443,10 @@ export const serveHttp = (port: number) => {
const assembled = Buffer.concat([jpegHeader, ...s.curImage.slice(1)]);
const header = Buffer.from(`\r\n--${BOUNDARY}\r\nContent-Length: ${assembled.length}\r\nContent-Type: image/jpeg\r\n\r\n`);
responses[dev.devId].forEach((res) => {
- res.write(header);
- res.write(assembled);
+ res.write(header);
+ res.write(assembled);
});
- });
+ });
s.eventEmitter.on("disconnect", () => {
logger.info(`Camera ${dev.devId} disconnected`);
@@ -188,4 +467,4 @@ export const serveHttp = (port: number) => {
logger.info(`Starting HTTP server on port ${port}`);
server.listen(port);
-};
+};
\ No newline at end of file
diff --git a/logger.ts b/logger.ts
index 56b59bb..228a5b1 100644
--- a/logger.ts
+++ b/logger.ts
@@ -1,5 +1,5 @@
import { isatty } from "node:tty";
-import { addColors, config, createLogger, format, transports as wtransports } from "winston";
+import winston, { addColors, config, createLogger, format, transports as wtransports } from "winston";
const myFormat = format.printf(({ level, message, timestamp }) => {
return `${timestamp} [${level}] ${message}`;
@@ -9,7 +9,7 @@ const transports = {
console: new wtransports.Console(),
};
-export let logger = undefined;
+export let logger: winston.Logger = undefined;
export const buildLogger = (level: string, colorize: boolean | undefined) => {
let use_color = colorize === undefined ? isatty(1) : colorize;
diff --git a/package-lock.json b/package-lock.json
index 1ffa07c..d84099c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"yargs": "^17.7.2"
},
"devDependencies": {
+ "@types/node": "^22.13.11",
"@types/yargs": "^17.0.32",
"esbuild": "^0.20.2",
"mocha": "^10.2.0",
@@ -468,13 +469,13 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.11.6",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.6.tgz",
- "integrity": "sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==",
+ "version": "22.13.11",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.11.tgz",
+ "integrity": "sha512-iEUCUJoU0i3VnrCmgoWCXttklWcvoCIx4jzcP22fioIVSdTmjgoEvmAO/QPw6TcS9k5FrNgn4w7q5lGOd1CT5g==",
"dev": true,
- "peer": true,
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.20.0"
}
},
"node_modules/@types/triple-beam": {
@@ -1628,11 +1629,11 @@
}
},
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
- "peer": true
+ "license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
diff --git a/package.json b/package.json
index 8340e77..d87f1ea 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"build": "esbuild cmd/bin.ts --bundle --platform=node --outfile=dist/bin.cjs --target=node12 --loader:.gz=binary --loader:.html=text"
},
"devDependencies": {
+ "@types/node": "^22.13.11",
"@types/yargs": "^17.0.32",
"esbuild": "^0.20.2",
"mocha": "^10.2.0",