aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author0neGal <mail@0negal.com>2024-12-20 20:23:20 +0100
committerGitHub <noreply@github.com>2024-12-20 20:23:20 +0100
commit98fa01696b123b8d602b20534f866debdb64df52 (patch)
tree9e4d4ac04b01010b4c2faa71543440114308be86
parentfab59ba8b8b1c567ee158b9681c5b373fb7303e4 (diff)
parent00d04db553eb53abb48f0b4e3649a6912fdc10df (diff)
downloadViper-98fa01696b123b8d602b20534f866debdb64df52.tar.gz
Viper-98fa01696b123b8d602b20534f866debdb64df52.zip
Merge pull request #246 from Jan200101/PR/onLine-event
feat: handle no internet better
-rw-r--r--src/app/css/theming.css2
-rw-r--r--src/app/icons/offline.pngbin0 -> 2707 bytes
-rw-r--r--src/app/index.html9
-rw-r--r--src/app/js/browser.js15
-rw-r--r--src/app/js/mods.js2
-rw-r--r--src/app/js/request.js110
-rw-r--r--src/app/js/set_buttons.js12
-rw-r--r--src/app/js/update.js10
-rw-r--r--src/app/main.css8
-rw-r--r--src/lang/en.json3
-rw-r--r--src/modules/requests.js48
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
new file mode 100644
index 0000000..a9d2f1f
--- /dev/null
+++ b/src/app/icons/offline.png
Binary files differ
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;