diff options
-rw-r--r-- | src/app/css/theming.css | 2 | ||||
-rw-r--r-- | src/app/icons/offline.png | bin | 0 -> 2707 bytes | |||
-rw-r--r-- | src/app/index.html | 9 | ||||
-rw-r--r-- | src/app/js/browser.js | 15 | ||||
-rw-r--r-- | src/app/js/mods.js | 2 | ||||
-rw-r--r-- | src/app/js/request.js | 110 | ||||
-rw-r--r-- | src/app/js/set_buttons.js | 12 | ||||
-rw-r--r-- | src/app/js/update.js | 10 | ||||
-rw-r--r-- | src/app/main.css | 8 | ||||
-rw-r--r-- | src/lang/en.json | 3 | ||||
-rw-r--r-- | src/modules/requests.js | 48 |
11 files changed, 198 insertions, 21 deletions
diff --git a/src/app/css/theming.css b/src/app/css/theming.css index c0b6497..efba26a 100644 --- a/src/app/css/theming.css +++ b/src/app/css/theming.css @@ -37,7 +37,7 @@ a { transition: filter 0.2s ease-in !important; } -a.disabled:not("[onclick='kill('game')']") { +a.disabled:not([onclick="kill('game')"]) { opacity: 0.5; pointer-events: none; } diff --git a/src/app/icons/offline.png b/src/app/icons/offline.png Binary files differnew file mode 100644 index 0000000..a9d2f1f --- /dev/null +++ b/src/app/icons/offline.png diff --git a/src/app/index.html b/src/app/index.html index 98c59d4..5421a3f 100644 --- a/src/app/index.html +++ b/src/app/index.html @@ -14,6 +14,9 @@ <div id="toasts"></div> <div id="winbtns"> + <div class="hidden" id="offline" tooltip="%%tooltip.offline%%" tooltip-position="horizontal"> + <img src="icons/offline.png"> + </div> <div id="settings" tooltip="%%tooltip.settings%%" tooltip-position="horizontal" onclick="settings.popup.toggle()"> <img src="icons/settings.png"> </div> @@ -35,7 +38,7 @@ <div id="overlay" onclick="popups.set_all(false)"></div> <div class="popup" id="options"> <div class="misc"> - <input class="search default-selection" placeholder="%%gui.search%%"> + <input class="search default-selection requires-internet" placeholder="%%gui.search%%"> <button id="apply" onclick="settings.popup.apply();settings.popup.toggle(false)"> <img src="icons/apply.png"> %%gui.settings.save%% @@ -338,7 +341,7 @@ <button id="playNsBtn" class="playBtn" onclick="launch('northstar')">%%gui.launch%%</button> <div class="inline"> <div id="nsversion"></div> - <a id="update" href="#" onclick="update.ns()" class="disable-when-installing">(%%gui.update.check%%)</a> + <a id="update" href="#" onclick="update.ns()" class="disable-when-installing requires-internet">(%%gui.update.check%%)</a> <div id="serverstatus" class="checking"></div> </div> </div> @@ -360,7 +363,7 @@ <img src="icons/downloads.png"> %%gui.mods.install%% </button> - <button id="findmod" class="bg-blue2" onclick="browser.toggle(true)"> + <button id="findmod" class="requires-internet bg-blue2" onclick="browser.toggle(true)"> <img src="icons/search.png"> %%gui.mods.find%% </button> diff --git a/src/app/js/browser.js b/src/app/js/browser.js index 88e8017..f306da8 100644 --- a/src/app/js/browser.js +++ b/src/app/js/browser.js @@ -107,7 +107,15 @@ var browser = { browser.filters.toggle(false); } }, - install: (package_obj, clear_queue = false) => { + install: async (package_obj, clear_queue = false) => { + let can_connect = await request.check_with_toasts( + "Thunderstore", "https://thunderstore.io" + ) + + if (! can_connect) { + return; + } + return mods.install_from_url( package_obj.download || package_obj.versions[0].download_url, package_obj.dependencies || package_obj.versions[0].dependencies, @@ -489,11 +497,11 @@ browser.mod_el = (properties) => { <div class="text"> <div class="title">${properties.title}</div> <div class="description">${properties.description}</div> - <button class="install bg-blue" onclick=''> + <button class="install bg-blue requires-internet" onclick=''> <img src="icons/${installicon}.png"> <span>${installstr}</span> </button> - <button class="info" onclick="browser.preview.set('${properties.url}')"> + <button class="info requires-internet" onclick="browser.preview.set('${properties.url}')"> <img src="icons/open.png"> <span>${lang('gui.browser.view')}</span> </button> @@ -563,7 +571,6 @@ ipcRenderer.on("legacy-duped-mod", (_, modname) => { }) ipcRenderer.on("no-internet", () => { - set_buttons(true); toasts.show({ timeout: 10000, scheme: "error", diff --git a/src/app/js/mods.js b/src/app/js/mods.js index 755aad1..9eb528b 100644 --- a/src/app/js/mods.js +++ b/src/app/js/mods.js @@ -74,7 +74,7 @@ mods.load = (mods_obj) => { <div class="title">${mod_details.name}</div> <div class="description">${mod_details.description}</div> <button class="switch on orange"></button> - <button class="update bg-blue"> + <button class="update bg-blue requires-internet"> <img src="icons/downloads.png"> <span>${lang("gui.browser.update")}</span> </button> diff --git a/src/app/js/request.js b/src/app/js/request.js index 29b8883..81e10b1 100644 --- a/src/app/js/request.js +++ b/src/app/js/request.js @@ -1,18 +1,118 @@ +const lang = require("../../lang"); +const toasts = require("./toasts"); +const launcher = require("./launcher"); +const set_buttons = require("./set_buttons"); const ipcRenderer = require("electron").ipcRenderer; -// show a toast message if no Internet connection has been detected. -if (! navigator.onLine) { - ipcRenderer.send("no-internet"); -} - // invokes `requests.get()` from `src/modules/requests.js` through the // main process, and returns the output let request = async (...args) => { return await ipcRenderer.invoke("request", ...args); } +// invokes `requests.check()` from `src/modules/requests.js` through the +// main process, and returns the output +request.check = async (...args) => { + return await ipcRenderer.invoke("request-check", ...args); +} + request.delete_cache = () => { ipcRenderer.send("delete-request-cache"); } +// does `request.check(...args)` and shows toast if the check failed, +// using `name` inside the toast message +request.check_with_toasts = async (name, ...args) => { + // perform check + let can_connect = ( + await request.check(...args) + ).succeeded.length; + + // show toast, as the check failed + if (! can_connect) { + toasts.show({ + timeout: 10000, + scheme: "error", + title: lang("gui.toast.title.failed_to_connect"), + description: lang("gui.toast.desc.failed_to_connect").replaceAll("%s", name) + }) + } + + return can_connect; +} + +// keeps track of whether we've already sent a toast since we last went +// offline, to prevent multiple toasts +let sent_error_toast = false; + +// shows or hides offline icon, and shows toast depending on `is_online` +let state_action = async (is_online) => { + if (is_online) { + // hide offline icon + sent_error_toast = false; + offline.classList.add("hidden"); + + // re-enable buttons that require internet + set_buttons( + true, false, + document.querySelectorAll(".requires-internet") + ) + + await launcher.check_servers(); + serverstatus.style.opacity = "1.0"; + } else { + // show toast + if (! sent_error_toast) { + sent_error_toast = true; + ipcRenderer.send("no-internet"); + } + + // show offline icon + offline.classList.remove("hidden"); + + // disable buttons that require internet + set_buttons( + false, false, + document.querySelectorAll(".requires-internet") + ) + + serverstatus.style.opacity = "0.0"; + + // close mod browser + try { + require("./browser").toggle(false); + } catch(err) {} + } +} + +setTimeout(() => state_action(navigator.onLine), 100); +window.addEventListener("online", () => state_action(navigator.onLine)); +window.addEventListener("offline", () => state_action(navigator.onLine)); + +// checks a list of endpoints/domains we need to be functioning for a +// lot of the WAN functionality +let check_endpoints = async () => { + // if we're not online according to the navigator, it's highly + // unlikely the endpoints will succeed + if (! navigator.onLine) { + return state_action(false); + } + + // check endpoints + let status = await request.check([ + "https://github.com", + "https://northstar.tf", + "https://thunderstore.io" + ]) + + // handle result of check + state_action(!! status.succeeded.length); +} + +// check endpoints on startup +setTimeout(check_endpoints, 100); + +// check endpoints every 30 seconds +setInterval(check_endpoints, 30000); + module.exports = request; diff --git a/src/app/js/set_buttons.js b/src/app/js/set_buttons.js index 9cb9d3e..4a0e1b2 100644 --- a/src/app/js/set_buttons.js +++ b/src/app/js/set_buttons.js @@ -6,22 +6,24 @@ ipcRenderer.on("set-buttons", (_, state) => { // disables or enables certain buttons when for example // updating/installing Northstar. -module.exports = (state, enable_gamepath_btns) => { - playNsBtn.disabled = ! state; +module.exports = (state, enable_gamepath_btns, elements) => { + if (! elements) { + playNsBtn.disabled = ! state; + } let disable_array = (array) => { for (let i = 0; i < array.length; i++) { array[i].disabled = ! state; if (state) { - array[i].classList.remove("disabled") + array[i].classList.remove("disabled"); } else { - array[i].classList.add("disabled") + array[i].classList.add("disabled"); } } } - disable_array(document.querySelectorAll([ + disable_array(elements || document.querySelectorAll([ "#modsdiv .el button", ".disable-when-installing", ".playBtnContainer .playBtn", diff --git a/src/app/js/update.js b/src/app/js/update.js index d03db02..3732f89 100644 --- a/src/app/js/update.js +++ b/src/app/js/update.js @@ -125,7 +125,15 @@ let update = { // updates Northstar, `force_update` forcefully updates Northstar, // causing it to update, even if its already up-to-date - ns: (force_update) => { + ns: async (force_update) => { + let can_connect = await request.check_with_toasts( + "GitHub", "https://github.com" + ) + + if (! can_connect) { + return; + } + update.ns.last_checked = new Date().getTime(); ipcRenderer.send("update-northstar", force_update); update.ns.should_install = false; diff --git a/src/app/main.css b/src/app/main.css index 9ade423..b6b0ac7 100644 --- a/src/app/main.css +++ b/src/app/main.css @@ -59,8 +59,14 @@ button:active {filter: brightness(90%)} margin-right: calc(var(--padding) / 2); } +#winbtns div.hidden { + width: 0px; + opacity: 0.0; + margin-right: 0px; + pointer-events: none; +} + #winbtns div img { - width: 100%; height: 100%; transition: transform 0.25s ease-in-out; } diff --git a/src/lang/en.json b/src/lang/en.json index 5d622e2..9540ad2 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -267,6 +267,7 @@ "malformed": "Incorrect folder structure!", "unknown_error": "Unknown Error!", "no_internet": "No Internet", + "failed_to_connect": "Failed to connect", "failed_launch_command": "Failed running launch command", "missing_launch_command": "Missing launch command", "missing_steam": "Missing Steam", @@ -281,6 +282,7 @@ "duped": "has multiple mod folders in it, with the same name, causing duplicate folders, if you're the developer, you should fix this.", "unknown_error": "An unknown error occurred, click for more details. You may want to take a screenshot of the detailed error when filing a bug report.", "no_internet": "Viper may not work properly.", + "failed_to_connect": "A connection could not be established to %s, check your internet, firewall or whether %s is currently down", "failed_launch_command": "Something went wrong whilst running the custom launch command", "missing_launch_command": "There's currently no custom launch command set, one has to be configured to launch", "missing_steam": "Can't launch with Steam directly, as it doesn't seem to be installed", @@ -342,6 +344,7 @@ "close": "Close Viper", "minimize": "Minimize Viper", "settings": "Settings", + "offline": "Internet Offline", "pages": { "viper": "Viper", "northstar": "Northstar", diff --git a/src/modules/requests.js b/src/modules/requests.js index dac3a4f..ffa57c6 100644 --- a/src/modules/requests.js +++ b/src/modules/requests.js @@ -25,6 +25,15 @@ ipcMain.handle("request", async (e, ...args) => { return res; }) +ipcMain.handle("request-check", async (_, ...args) => { + let res = false; + + try { + res = await requests.check(...args); + }catch(err) {} + + return res; +}) // updates `cache_dir` and `cache_file` function set_paths() { @@ -244,4 +253,43 @@ requests.get = (host, path, cache_key, ignore_max_time_when_offline = true, max_ }) } +// checks whether a list of `endpoints` can be contacted +requests.check = async (endpoints) => { + // turn `endpoints` into an array, if it isn't already + if (typeof endpoints == "string") { + endpoints = [endpoints]; + } + + // list of what failed and succeeded, will be returned later + let res = { + failed: [], + succeeded: [] + } + + // run through all the endpoints + for (let endpoint of endpoints) { + let req; + + // attempt to do a request + try { + req = await fetch(endpoint); + } catch(err) { // something went wrong! + res.failed.push(endpoint); + continue; + } + + // if we're within the `200-299` response code range, we + // consider it a success + if (req.status < 300 && req.status >= 200) { + res.succeeded.push(endpoint); + continue; + } + + // we failed! + res.failed.push(endpoint); + } + + return res; +} + module.exports = requests; |