diff options
-rw-r--r-- | src/app/js/browser.js | 16 | ||||
-rw-r--r-- | src/app/js/launcher.js | 10 | ||||
-rw-r--r-- | src/app/main.js | 6 | ||||
-rw-r--r-- | src/index.js | 25 | ||||
-rw-r--r-- | src/modules/releases.js | 80 | ||||
-rw-r--r-- | src/modules/requests.js | 371 | ||||
-rw-r--r-- | src/modules/update.js | 14 | ||||
-rw-r--r-- | src/modules/version.js | 7 |
8 files changed, 338 insertions, 191 deletions
diff --git a/src/app/js/browser.js b/src/app/js/browser.js index c1da9b5..07572d9 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -166,7 +166,21 @@ var Browser = { packagecount = 0; if (packages.length < 1) { - packages = await (await fetch("https://northstar.thunderstore.io/api/v1/package/")).json(); + let host = "northstar.thunderstore.io"; + let path = "/api/v1/package/"; + + packages = []; + + // attempt to get the list of packages from Thunderstore, if + // this has been done recently, it'll simply return a cached + // version of the request + try { + packages = JSON.parse( + await request(host, path, "thunderstore-packages") + ) + }catch(err) { + console.error(err) + } Browser.add_pkg_properties(); diff --git a/src/app/js/launcher.js b/src/app/js/launcher.js index 5330b7a..c1901f3 100644 --- a/src/app/js/launcher.js +++ b/src/app/js/launcher.js @@ -127,7 +127,15 @@ async function loadServers() { serverstatus.classList.add("checking"); try { - let servers = await (await fetch("https://northstar.tf/client/servers")).json(); + let host = "northstar.tf"; + let path = "/client/servers"; + + // ask the masterserver for the list of servers, if this has + // been done recently, it'll simply return the cached version + let servers = JSON.parse( + await request(host, path, "ns-servers") + ) + masterserver = true; playercount = 0; diff --git a/src/app/main.js b/src/app/main.js index f5fe19f..98a5fda 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -30,6 +30,12 @@ var settings = { ] } +// invokes `requests.get()` from `src/modules/requests.js` through the +// main process, and returns the output +async function request(...args) { + return await ipcRenderer.invoke("request", ...args); +} + // Sets the lang to the system default ipcRenderer.send("setlang", settings.lang); diff --git a/src/index.js b/src/index.js index 220d490..917b8bd 100644 --- a/src/index.js +++ b/src/index.js @@ -19,6 +19,7 @@ const version = require("./modules/version"); const gamepath = require("./modules/gamepath"); const settings = require("./modules/settings"); const requests = require("./modules/requests"); +const releases = require("./modules/releases"); const packages = require("./modules/packages"); const is_running = require("./modules/is_running"); @@ -41,8 +42,6 @@ function start() { // as it's fairly responsive, but for now we won't allow that. resizable: false, - userAgent: "test", - frame: false, titleBarStyle: "hidden", icon: path.join(__dirname, "assets/icons/512x512.png"), @@ -59,7 +58,7 @@ function start() { // general setup win.removeMenu(); win.loadURL("file://" + __dirname + "/app/index.html", { - userAgent: "viper/" + json(path.join(__dirname, "../package.json")).version, + userAgent: "viper/" + version.viper(), }); win.send = (channel, data) => { @@ -98,7 +97,7 @@ function start() { }); ipcMain.on("delete-request-cache", () => { - requests.delete_cache(); + requests.cache.delete.all(); }); ipcMain.on("delete-install-cache", () => { @@ -165,6 +164,17 @@ function start() { packages.install(url, author, package_name, version); }); + // lets renderer use `requests.get()` + ipcMain.handle("request", async (e, ...args) => { + let res = false; + + try { + res = await requests.get(...args); + }catch(err) {} + + return res; + }) + win.webContents.on("dom-ready", () => { send("mods", mods.list()); }); @@ -277,7 +287,7 @@ function sendVersionsInfo() { send("version", { ns: version.northstar(), tf2: version.titanfall(), - vp: "v" + require("../package.json").version + vp: "v" + version.viper() }); } @@ -360,15 +370,16 @@ if (cli.hasArgs()) { } else { app.on("ready", () => { app.setPath("userData", path.join(app.getPath("cache"), app.name)); + start(); }) } // returns cached requests ipcMain.on("get-ns-notes", async () => { - win.send("ns-notes", await requests.getNsReleaseNotes()); + win.send("ns-notes", await releases.notes.northstar()); }); ipcMain.on("get-vp-notes", async () => { - win.send("vp-notes", await requests.getVpReleaseNotes()); + win.send("vp-notes", await releases.notes.viper()); }); diff --git a/src/modules/releases.js b/src/modules/releases.js new file mode 100644 index 0000000..c071970 --- /dev/null +++ b/src/modules/releases.js @@ -0,0 +1,80 @@ +const requests = require("./requests"); + +let releases = { + notes: {}, + latest: {} +} + +// gets and returns the release notes of a GitHub repo +async function github_releases(repo) { + let request = false; + + // attempt to perform the request, while caching it + try { + request = JSON.parse(await requests.get( + "api.github.com", `/repos/${repo}/releases`, + "release-notes-" + repo + )) + }catch(err) { + // request or parsing failed, return `false` + return false; + } + + // request is somehow falsy, return `false` + if (! request) { + return false; + } + + // return the actual request as parsed JSON + return request; +} + +// returns release notes for Viper +releases.notes.viper = async () => { + return await github_releases("0neGal/viper"); +} + +// returns release notes for Northstar +releases.notes.northstar = async () => { + return await github_releases("R2Northstar/Northstar"); +} + +// gets and returns some details of the latest release of a GitHub repo +async function github_latest(repo) { + let request = false; + + // attempt to perform the request, while caching it + try { + request = JSON.parse(await requests.get( + "api.github.com", `/repos/${repo}/releases/latest`, + "latest-release-" + repo + )) + }catch(err) { + // request or parsing failed, return `false` + return false; + } + + // request is somehow falsy, return `false` + if (! request) { + return false; + } + + // return the actual request as parsed JSON + return { + notes: request.body, + version: request.tag_name, + download_link: request.assets[0].browser_download_url + } +} + +// returns latest release for Viper +releases.latest.viper = async () => { + return await github_latest("0neGal/viper"); +} + +// returns latest release for Northstar +releases.latest.northstar = async () => { + return await github_latest("R2Northstar/Northstar"); +} + +module.exports = releases; diff --git a/src/modules/requests.js b/src/modules/requests.js index a67bc26..bc113bb 100644 --- a/src/modules/requests.js +++ b/src/modules/requests.js @@ -1,196 +1,217 @@ -const { app } = require("electron"); -const path = require("path"); const fs = require("fs"); -const { https, http } = require("follow-redirects"); -const lang = require("../lang"); +const path = require("path"); +const app = require("electron").app; +const https = require("follow-redirects").https; const json = require("./json"); +const version = require("./version"); + +var cache_dir = app.getPath("userData"); +var cache_file = path.join(cache_dir, "cached-requests.json"); -// all requests results are stored in this file -const cachePath = path.join(app.getPath("cache"), "viper-requests.json"); -const NORTHSTAR_LATEST_RELEASE_KEY = "nsLatestRelease"; -const NORTHSTAR_RELEASE_NOTES_KEY = "nsReleaseNotes"; -const VIPER_RELEASE_NOTES_KEY = "vpReleaseNotes"; +// updates `cache_dir` and `cache_file` +function set_paths() { + cache_dir = app.getPath("userData"); + cache_file = path.join(cache_dir, "cached-requests.json"); +} + +let requests = { + cache: {} +} -const user_agent = "viper/" + json(path.join(__dirname, "../../package.json")).version; +// verifies and ensures `cache_dir` exists +function ensure_dir() { + set_paths(); -function _saveCache(data) { - fs.writeFileSync(cachePath, JSON.stringify(data)); + // does the folder exist? + let exists = fs.existsSync(cache_dir); + + // shorthand for creating folder + let mkdir = () => {fs.mkdirSync(cache_dir)}; + + // if folder doesn't exist at all, create it + if (! exists) { + mkdir(); + return; + } + + // if it does exist, but somehow is a file, remove it, then recreate + // it as an actual folder, wait how did this even happen? + if (exists && fs.statSync(cache_dir).isFile()) { + fs.rmSync(cache_dir); + mkdir(); + } } -function _getRequestsCache() { - if (fs.existsSync(cachePath)) { - return JSON.parse(fs.readFileSync(cachePath, "utf8")); - } else { - fs.writeFileSync(cachePath, "{}"); - return {}; - } +// check `cache_file` and optionally check for the existence of +// `cache_key`, and if it exists, return it as is +let check_file = (cache_key) => { + // if `cache_file` doesn't exist, or isn't even a file, somehow, + // simply return `false`, and if it wasn't a file, we'll also remove + // the non-file item. + if (! fs.existsSync(cache_file) + || ! fs.statSync(cache_file).isFile()) { + + if (fs.existsSync(cache_file)) { + fs.rmSync(cache_file, {recursive: true}); + } + + return false; + } + + // attempt to read and parse `cache_file` as JSON + let file = json(cache_file); + + // if parsing failed, remove file, and return `false` + if (! file) { + fs.rmSync(cache_file); + return false; + } + + if (! cache_key) { + return file; + } + + // if `cache_key` isn't found, return `false` + if (! file[cache_key]) { + return false; + } + + return file[cache_key]; } -function delete_cache() { - if (fs.existsSync(cachePath)) { - return fs.rmSync(cachePath); +// attempts to get a `cache_key`'s value, unless it's been set more than +// `max_time_min` ago, set it to a falsy value to disable +requests.cache.get = (cache_key, max_time_min = 5) => { + ensure_dir(); + + let key = check_file(cache_key); + + // something went wrong with the config file or the key doesn't + // exist, return `false` + if (! key) { + return false; } + + // if the key is missing `.data` or `.time`, return `false` + if (! key.data || ! key.time) { + return false; + } + + // convert from minutes to milliseconds + max_time_min = max_time_min * 1000 * 60; + + let now = new Date().getTime(); + + // check if `key.time` is more than `max_time_min` since it got set + if (now - key.time > max_time_min && max_time_min) { + return false; + } + + return key.data; } -// Returns latest Northstar version available from GitHub releases. If -// there's no cache result for this request, or if cache exists but is -// old, refreshes cache with new data. -async function getLatestNsVersion() { - return new Promise((resolve, reject) => { - let cache = _getRequestsCache(); - - if (cache[NORTHSTAR_LATEST_RELEASE_KEY] && (Date.now() - cache[NORTHSTAR_LATEST_RELEASE_KEY]["time"]) < 5 * 60 * 1000) { - resolve( cache[NORTHSTAR_LATEST_RELEASE_KEY]["body"]["tag_name"] ); - } else { - https.get({ - host: "api.github.com", - port: 443, - path: "/repos/R2Northstar/Northstar/releases/latest", - method: "GET", - headers: { "User-Agent": user_agent } - }, - - response => { - response.setEncoding("utf8"); - let responseData = ""; - - response.on("data", data => { - responseData += data; - }); - - response.on("end", _ => { - cache[NORTHSTAR_LATEST_RELEASE_KEY] = { - "time": Date.now(), - "body": JSON.parse(responseData) - }; - _saveCache(cache); - resolve( cache[NORTHSTAR_LATEST_RELEASE_KEY]["body"]["tag_name"] ); - }); - }) - - .on('error', () => { - console.error('Failed to get latest Northstar version.'); - resolve( false ); - }); - } - }); +// attempt to delete `cache_key` from `cache_file` +requests.cache.delete = (cache_key) => { + ensure_dir(); + let file = check_file(); + + // if something went wrong when checking the `cache_file`, simply + // set the file to an empty Object + if (! file) { + file = {}; + } + + delete file[cache_key]; + fs.writeFileSync(cache_file, JSON.stringify(file)); } -// Returns the download link to latest Northstar version. Should always -// be called after getLatestNsVersion, as it refreshes cache data (if -// needed). -function getLatestNsVersionLink() { - const cache = _getRequestsCache(); - return cache[NORTHSTAR_LATEST_RELEASE_KEY]["body"].assets[0].browser_download_url; +// deletes all cached keys +requests.cache.delete.all = () => { + // if the file already exists, and actually is a file, we will + // simply empty it by writing an empty Object to it + if (fs.existsSync(cache_file) + && fs.statSync(cache_file).isFile()) { + + fs.writeFileSync(cache_file, "{}"); + } else if (fs.existsSync(cache_file)) { + // if `cache_file` does exist, but its not a file, we'll delete + // it completely + fs.rmSync(cache_file, {recursive: true}); + } } -// Returns and caches the Northstar release notes -async function getNsReleaseNotes() { - return new Promise(resolve => { - let cache = _getRequestsCache(); - - if (cache[NORTHSTAR_RELEASE_NOTES_KEY] && (Date.now() - cache[NORTHSTAR_RELEASE_NOTES_KEY]["time"]) < 5 * 60 * 1000) { - resolve( cache[NORTHSTAR_RELEASE_NOTES_KEY]["body"] ); - } else { - https.get({ - host: "api.github.com", - port: 443, - path: "/repos/R2Northstar/Northstar/releases", - method: "GET", - headers: { "User-Agent": user_agent } - }, - - response => { - response.setEncoding("utf8"); - let responseData = ""; - - response.on("data", data => { - responseData += data; - }); - - response.on("end", _ => { - cache[NORTHSTAR_RELEASE_NOTES_KEY] = { - "time": Date.now(), - "body": JSON.parse(responseData) - }; - _saveCache(cache); - resolve( cache[NORTHSTAR_RELEASE_NOTES_KEY]["body"] ); - }); - }) - - // When GitHub cannot be reached (when user doesn't have Internet - // access for instance), we return latest cache content even if - // it's not up-to-date, or display an error message if cache - // is empty. - .on('error', () => { - if ( cache[NORTHSTAR_RELEASE_NOTES_KEY] ) { - console.warn("Couldn't fetch Northstar release notes, returning data from cache."); - resolve( cache[NORTHSTAR_RELEASE_NOTES_KEY]["body"] ); - } else { - console.error("Couldn't fetch Northstar release notes, cache is empty."); - resolve( [lang("request.no_ns_release_notes")] ); - } - }); - } - }); +// sets `cache_key` to `data` and updates its timestamp +requests.cache.set = (cache_key, data) => { + ensure_dir(); + let file = check_file(); + + // if something went wrong when checking the `cache_file`, simply + // set the file to an empty Object + if (! file) { + file = {}; + } + + file[cache_key] = { + data: data, + time: new Date().getTime() + } + + fs.writeFileSync(cache_file, JSON.stringify(file)); } -// Returns and caches the Viper release notes -async function getVpReleaseNotes() { - return new Promise(resolve => { - let cache = _getRequestsCache(); - - if (cache[VIPER_RELEASE_NOTES_KEY] && (Date.now() - cache[VIPER_RELEASE_NOTES_KEY]["time"]) < 5 * 60 * 1000) { - resolve( cache[VIPER_RELEASE_NOTES_KEY]["body"] ); - } else { - https.get({ - host: "api.github.com", - port: 443, - path: "/repos/0negal/viper/releases", - method: "GET", - headers: { "User-Agent": user_agent } - }, - - response => { - response.setEncoding("utf8"); - let responseData = ""; - - response.on("data", data => { - responseData += data; - }); - - response.on("end", _ => { - cache[VIPER_RELEASE_NOTES_KEY] = { - "time": Date.now(), - "body": JSON.parse(responseData) - }; - _saveCache(cache); - resolve( cache[VIPER_RELEASE_NOTES_KEY]["body"] ); - }); - }) - - // When GitHub cannot be reached (when user doesn't have Internet - // access for instance), we return latest cache content even if - // it's not up-to-date, or display an error message if cache - // is empty. - .on('error', () => { - if ( cache[VIPER_RELEASE_NOTES_KEY] ) { - console.warn("Couldn't fetch Viper release notes, returning data from cache."); - resolve( cache[VIPER_RELEASE_NOTES_KEY]["body"] ); - } else { - console.error("Couldn't fetch Viper release notes, cache is empty."); - resolve( [lang("request.no_vp_release_notes")] ); - } - }); - } - }); +// attempts to `GET` `https://<host>/<path>`, and then returns the +// result or if it fails it'll reject with `false` +// +// if `cache_key` is set, we'll first attempt to check if any valid +// cache with that key exists, and then return it directly if its still +// valid cache. +requests.get = (host, path, cache_key, max_time_min) => { + let cached = requests.cache.get(cache_key, max_time_min); + if (cached) { + return cached; + } + + // we'll use this as the `User-Agent` header for the request + let user_agent = "viper/" + version.viper(); + + return new Promise((resolve, reject) => { + // start `GET` request + https.get({ + host: host, + port: 443, + path: path, + method: "GET", + headers: { "User-Agent": user_agent } + }, + + // on data response + response => { + // set correct encoding + response.setEncoding("utf8"); + + // this'll be filled with incoming data + let res_data = ""; + + // data has arrived, add it on `res_data` + response.on("data", data => { + res_data += data; + }) + + // request is done, return result + response.on("end", _ => { + resolve(res_data); + if (cache_key) { + requests.cache.set(cache_key, res_data); + } + }) + }) + + // an error occured, simply `reject()` + .on("error", () => { + reject(false); + }) + }) } -module.exports = { - delete_cache, - getLatestNsVersion, - getLatestNsVersionLink, - getNsReleaseNotes, - getVpReleaseNotes -}; +module.exports = requests; diff --git a/src/modules/update.js b/src/modules/update.js index a45223a..458b511 100644 --- a/src/modules/update.js +++ b/src/modules/update.js @@ -8,7 +8,7 @@ const lang = require("../lang"); const win = require("./window"); const version = require("./version"); const settings = require("./settings"); -const requests = require("./requests"); +const releases = require("./releases"); const gamepath = require("./gamepath"); const is_running = require("./is_running"); @@ -92,7 +92,7 @@ update.northstar_autoupdate = () => { // returns whether an update is available for Northstar async function northstar_update_available() { let local = version.northstar(); - let distant = await requests.getLatestNsVersion(); + let distant = (await releases.latest.northstar()).version; if (distant == false) { return false; @@ -193,9 +193,9 @@ update.northstar = async (force_install) => { console.info(lang("cli.update.checking")); let ns_version = version.northstar(); - const latest_version = await requests.getLatestNsVersion(); + let latest = await releases.latest.northstar(); - if (latest_version == false) { + if (latest && latest.version == false) { ipcMain.emit("ns-update-event", "cli.update.noInternet"); return; } @@ -211,13 +211,13 @@ update.northstar = async (force_install) => { } else { if (ns_version != "unknown") { console.info(lang("cli.update.current"), ns_version); - }; + } } exclude_files(); // start the download of the zip - https.get(requests.getLatestNsVersionLink(), (res) => { + https.get(latest.download_link, (res) => { // cancel out if zip can't be retrieved and or found if (res.statusCode !== 200) { ipcMain.emit("ns-update-event", "cli.update.uptodate_short"); @@ -243,7 +243,7 @@ update.northstar = async (force_install) => { } } - console.info(lang("cli.update.downloading") + ":", latest_version); + console.info(lang("cli.update.downloading") + ":", latest.version); ipcMain.emit("ns-update-event", { progress: 0, btn_text: "1/2", diff --git a/src/modules/version.js b/src/modules/version.js index 652ffc5..6195cad 100644 --- a/src/modules/version.js +++ b/src/modules/version.js @@ -76,4 +76,11 @@ version.titanfall = () => { } } +// returns Viper's current version, taken from `package.json` +version.viper = () => { + return json( + path.join(__dirname, "../../package.json") + ).version; +} + module.exports = version; |