/* nodejs-poolController. An application to control pool equipment. Copyright (C) 2016, 2017, 2018, 2019, 2020. Russell Goldin, tagyoureit. russ.goldin@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import * as path from "path"; import * as fs from "fs"; const extend = require("extend"); import { logger } from "../logger/Logger"; // import { https } from "follow-redirects"; import * as https from 'https'; // import { AppVersionState, state } from "../controller/State"; // import { sys } from "../controller/Equipment"; // import { Timestamp } from "../controller/Constants"; import { execSync } from 'child_process'; import { config } from './Config'; import { Timestamp } from '../Constants'; class VersionCheck { private userAgent: string; private gitApiHost: string; private gitLatestReleaseJSONPath: string; private redirects: number; private gitAvailable: boolean; constructor() { this.userAgent = 'tagyoureit-nodejs-poolController-dashPanel-app'; this.gitApiHost = 'api.github.com'; this.gitLatestReleaseJSONPath = '/repos/rstrouse/nodejs-poolController-dashPanel/releases/latest'; this.gitAvailable = this.detectGit(); } private detectGit(): boolean { try { execSync('git --version', { stdio: 'ignore' }); return true; } catch { return false; } } public checkGitRemote() { let c = config.getSection('appVersion'); // need to significantly rate limit this because GitHub will start to throw 'too many requests' error // and we simply don't need to check that often if the app needs to be updated if (typeof c.nextCheckTime === 'undefined' || new Date() > new Date(c.nextCheckTime)) this.checkAll(); this.checkGitLocal(); c = config.getSection('appVersion'); return c; } public checkGitLocal() { // check local git version let c = config.getSection('appVersion'); let env = process.env; let out: string; try { if (typeof env.SOURCE_BRANCH !== 'undefined') { out = env.SOURCE_BRANCH; // provided via build/CI } else if (this.gitAvailable) { let res = execSync('git rev-parse --abbrev-ref HEAD'); out = res.toString().trim(); } else { out = '--'; } if (out !== '--') logger.info(`The current git local branch output is ${out}`); switch (out) { case 'fatal': case 'command': c.gitLocalBranch = '--'; break; default: c.gitLocalBranch = out; } } catch (err) { logger.warn(`Git branch unavailable (git missing or error). Suppressing further branch errors.`); c.gitLocalBranch = '--'; } try { if (typeof env.SOURCE_COMMIT !== 'undefined') { out = env.SOURCE_COMMIT; } else if (this.gitAvailable) { let res = execSync('git rev-parse HEAD'); out = res.toString().trim(); } else { out = '--'; } if (out !== '--') logger.info(`The current git local commit output is ${out}`); switch (out) { case 'fatal': case 'command': c.gitLocalCommit = '--'; break; default: c.gitLocalCommit = out; } } catch (err) { logger.warn(`Git commit unavailable (git missing or error). Suppressing further commit errors.`); c.gitLocalCommit = '--'; } config.setSection('appVersion', c); } private checkAll() { try { let c = config.getSection('appVersion'); this.redirects = 0; let dt = new Date(); dt.setDate(dt.getDate() + 2); // check every 2 days c.nextCheckTime = Timestamp.toISOLocal(dt); this.getLatestRelease().then((publishedVersion) => { c.githubRelease = publishedVersion; config.setSection('appVersion', c); this.compare(); }).catch((err) => { logger.warn(`Error get git latest release: ${err}`); }); } catch (err) { logger.error(err); } } private async getLatestRelease(redirect?: string): Promise { var options = { method: 'GET', headers: { 'User-Agent': this.userAgent } } let url: string; if (typeof redirect === 'undefined') { url = `https://${this.gitApiHost}${this.gitLatestReleaseJSONPath}`; } else { url = redirect; this.redirects += 1; } if (this.redirects >= 20) return Promise.reject(`Too many redirects.`) return new Promise(async (resolve, reject) => { let r = https.request(url, options, async res => { if (res.statusCode > 300 && res.statusCode < 400 && res.headers.location) await this.getLatestRelease(res.headers.location); let data = ''; res.on('data', d => { data += d; }) .on('end', () => { let jdata = JSON.parse(data); if (typeof jdata.tag_name !== 'undefined') resolve(jdata.tag_name.replace('v', '')); else reject(`No data returned.`) }) .on('error', (err) => { logger.error(`Error with getLatestRelease: ${err.message}`); }) }) .end(); r.on('error', (err) => { logger.error(`here??? ${err.message}`); }) }) } public compare() { logger.info(`Checking dashPanel versions...`); let c = config.getSection('appVersion'); if (typeof c.githubRelease === 'undefined' || typeof c.installed === 'undefined') { c.status = 'unknown'; logger.warn(`Unable to compare installed version to github version.`) config.setSection('appVersion', c); return; } let publishedVersionArr = c.githubRelease.split('.'); let installedVersionArr = c.installed.split('.'); if (installedVersionArr.length !== publishedVersionArr.length) { // this is in case local a.b.c doesn't have same # of elements as another version a.b.c.d. We should never get here. logger.warn(`Cannot check for updated app. Version length of installed app (${installedVersionArr}) and remote (${publishedVersionArr}) do not match.`); c.status = 'unknown'; config.setSection('appVersion', c); return; } else { for (var i = 0; i < installedVersionArr.length; i++) { if (publishedVersionArr[i] > installedVersionArr[i]) { c.status = 'behind'; logger.info(`New version available. Current:${c.installed} Github:${c.githubRelease}`); config.setSection('appVersion', c); return; } else if (publishedVersionArr[i] < installedVersionArr[i]) { c.status = 'ahead'; logger.info(`Currently running a newer version than released version. Current:${c.installed} Github:${c.githubRelease}`); config.setSection('appVersion', c); return; } } } c.status = 'current'; logger.info(`Current installed dashPanel version matches Github release. ${c.installed}`) config.setSection('appVersion', c); } } export var versionCheck = new VersionCheck();