Repository: louislam/uptime-kuma
File: server/monitor-types/tailscale-ping.js (JavaScript)

Code before:
            throw new Error(`Error checking Tailscale ping: ${err}`);
        }
    }

// runTailscalePing is now marked as async
    async runTailscalePing(url) {
        let cmd = `tailscale ping ${url}`;
        return new Promise((resolve, reject) => {
            //set a timeout here just incase tailscale ping hangs
            exec(cmd, {timeout: 10000}, (error, stdout, stderr) => {
                // Also to trigger a function here of error.message or stderr in the web gui 
                // we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues) 
                if (error) {
                    reject(`Execution error: ${error.message}`);
                    return;
                }
                if (stderr) {
                    reject(`Error in output: ${stderr}`);
                    return;
                }
                resolve(stdout);
            });
        });
    }

// logic for handling stdout is now moved to parseTailscaleOutput
    parseTailscaleOutput(tailscaleOutput, heartbeat) {
        let lines = tailscaleOutput.split('\n');

        for (let line of lines) {
            // "pong from" counts as UP, and we update heartbeat status and ping time
            // otherwise if "no matching peer" (nonexistant or inaccessible due to ACLs) or "is local Tailscale IP" (ping self), throw error
            // otherwise throw error and the unexpected output itself
            if (line.includes("pong from")) {
                heartbeat.status = UP;
                let time = line.split(" in ")[1].split(" ")[0];
                heartbeat.ping = parseInt(time);
                heartbeat.msg = line;
                break;
            } else if (line.includes("no matching peer") || line.includes("is local Tailscale IP")) {
                throw new Error(`"${line}"`);
            } else if (line !== '') {
                throw new Error(`Unexpected output: "${line}"`);
            }
        }
    }
}

module.exports = {
    TailscalePing,
};

Review comment (suggestion):
Please document your functions using `JSDoc` like `@param ...`, `@returns ...`
Also please add a comment what the function does instead of what it does not ^^

Code after:
    async runTailscalePing(hostname, interval) {
        let cmd = `tailscale ping ${hostname}`;

        log.debug("Tailscale", cmd);

        return new Promise((resolve, reject) => {
            let timeout = interval * 1000 * 0.8;
            exec(cmd, { timeout: timeout }, (error, stdout, stderr) => {
                // we may need to handle more cases if tailscale reports an error that isn't necessarily an error (such as not-logged in or DERP health-related issues)
                if (error) {
                    reject(`Execution error: ${error.message}`);
                    return;
                }
                if (stderr) {
                    reject(`Error in output: ${stderr}`);
                    return;
                }

                resolve(stdout);
            });
        });
    }

    /**
     * Parses the output of the Tailscale ping command to update the heartbeat.
     *
     * @param {string} tailscaleOutput - The output of the Tailscale ping command.
     * @param {Object} heartbeat - The heartbeat object to update.
     * @throws Will throw an eror if the output contains any unexpected string.
     */
    parseTailscaleOutput(tailscaleOutput, heartbeat) {
        let lines = tailscaleOutput.split("\n");

        for (let line of lines) {
            if (line.includes("pong from")) {
                heartbeat.status = UP;
                let time = line.split(" in ")[1].split(" ")[0];
                heartbeat.ping = parseInt(time);
                heartbeat.msg = line;
                break;
            } else if (line.includes("timed out")) {
                throw new Error(`Ping timed out: "${line}"`);
                // Immediately throws upon "timed out" message, the server is expected to re-call the check function
            } else if (line.includes("no matching peer")) {
                throw new Error(`Nonexistant or inaccessible due to ACLs: "${line}"`);
            } else if (line.includes("is local Tailscale IP")) {
                throw new Error(`Tailscale only works if used on other machines: "${line}"`);
            } else if (line !== "") {
                throw new Error(`Unexpected output: "${line}"`);
            }
        }