Skip to content

Commit

Permalink
Merge pull request #373 from fairdataihub/detect-firewall
Browse files Browse the repository at this point in the history
feat: detect network interference between SODA and server and SODA and pennsieve
  • Loading branch information
aaronm-2112 authored Nov 22, 2024
2 parents b988d1c + c335134 commit c6eea8c
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 9 deletions.
1 change: 1 addition & 0 deletions .github/workflows/Build-and-deploy-win.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- main
- staging
- pre-staging
- detect-firewall

jobs:
deploy-on-windows:
Expand Down
39 changes: 39 additions & 0 deletions src/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ log.initialize({ preload: true });
log.transports.console.level = false;
log.transports.file.level = "debug";
let user_restart_confirmed = false;
global.serverLive = true;

ipcMain.handle("get-server-live-status", () => {
return global.serverLive;
});

let nodeStorage = new JSONStorage(app.getPath("userData"));

Expand Down Expand Up @@ -331,12 +336,27 @@ const createPyProc = async () => {
pyflaskProcess.stderr.on("data", (data) => {
const logOutput = `[pyflaskProcess stderr] ${data.toString()}`;
sessionServerOutput += `${logOutput}`;
global.serverLive = false;
});
// On close, log the outputs and the exit code
pyflaskProcess.on("close", (code) => {
log.info(`child process exited with code ${code}`);
log.info("Server output during session found below:");
log.info(sessionServerOutput);
global.serverLive = false;
});
// Event listener for when the process exits
pyflaskProcess.on("exit", (code, signal) => {
if (signal) {
log.info(`Process was killed by signal: ${signal}`);
global.serverLive = false;
} else if (code !== 0) {
log.info(`Process exited with error code: ${code}`);
global.serverLive = false;
} else {
log.info("Process exited successfully");
global.serverLive = false;
}
});
} else {
log.info("Application is not packaged");
Expand All @@ -351,10 +371,29 @@ const createPyProc = async () => {

pyflaskProcess.on("error", function (err) {
console.error("Failed to start pyflaskProcess:", err);
global.serverLive = false;
});

pyflaskProcess.on("close", function (err) {
console.error("Failed to start pyflaskProcess:", err);
global.serverLive = false;
});

// Event listener for when the process exits
pyflaskProcess.on("exit", (code, signal) => {
if (signal) {
global.serverLive = false;

log.info(`Process was killed by signal: ${signal}`);
} else if (code !== 0) {
global.serverLive = false;

log.info(`Process exited with error code: ${code}`);
} else {
global.serverLive = false;

log.info("Process exited successfully");
}
});
}
if (pyflaskProcess != null) {
Expand Down
8 changes: 7 additions & 1 deletion src/preload/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { contextBridge } from "electron";
import { contextBridge, ipcRenderer } from "electron";
import { electronAPI } from "@electron-toolkit/preload";
import os from "os";
import fs from "fs-extra";
Expand Down Expand Up @@ -406,6 +406,12 @@ if (process.contextIsolated) {
});
},
});
contextBridge.exposeInMainWorld("server", {
serverIsLive: async () => {
const status = await ipcRenderer.invoke("get-server-live-status");
return status;
},
});
} catch (error) {
log.error(error);
}
Expand Down
8 changes: 8 additions & 0 deletions src/renderer/src/assets/css/buttons.css
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,11 @@
#copy-icon-client-id:hover {
cursor: pointer;
}

#copy-icon-firewall-docs {
margin-left: 5px;
}

#copy-icon-firewall-docs:hover {
cursor: pointer;
}
2 changes: 1 addition & 1 deletion src/renderer/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "./assets/imports";

// Render React Components into their respective slots in the DOM
import "./components/renderers/ReactComponentRenderer";

import "./scripts/check-firewall/checkFirewall";
import "./assets/demo-btns";
import "./assets/nav";
import "./scripts/client";
Expand Down
50 changes: 50 additions & 0 deletions src/renderer/src/scripts/check-firewall/checkFirewall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import axios from "axios";
import { clientError } from "../others/http-error-handler/error-handler";

/**
* This function checks if the client is blocked by an external firewall.
* Assumptions: The client is connected to the internet.
* Returns: true if the client is blocked by an external firewall, false otherwise.
*/
export const clientBlockedByExternalFirewall = async (url) => {
// check that the client can make an api request to Pennsieve's public API
//make an axios request to this public endpoint: https://api.pennsieve.io/discover/datasets
//if the request fails, the client is blocked by an external firewall
try {
await axios.get(url);
return false;
} catch (error) {
clientError(error);
if (!error.response) {
// the request was made but no response was received. May be a firewall issue or the client
// may just need to wait to try again later.
return true;
}

// there is not a firewall issue if we get an actual repsonse from the server
return false;
}
};

let docsUrl = "https://docs.sodaforsparc.io/how-to/how-to-resolve-network-issues";
const copyClientIdToClipboard = () => {
window.electron.ipcRenderer.invoke("clipboard-write", docsUrl, "clipboard");
};

const commonHTML = `<p style="text-align:left;">Please refer to the SODA documentation page on resolving this issue by either clicking <a href="https://docs.sodaforsparc.io/how-to/how-to-resolve-network-issues" target="_blank">here</a> or by copying the url to the documentation page with the copy icon below.</p>
<div style="display:flex; margin:auto;">
<p style="margin-right: 10px;">${docsUrl}</p>
<div><i class="fas fa-copy" id="copy-icon-firewall-docs" click=${copyClientIdToClipboard()}></i></div>
</div>`;

export const blockedMessage = `
<p style="text-align:left;">SODA is unable to reach Pennsieve.
If this issue persists it is possible that your network is blocking access to Pennsieve from SODA.
</p>
${commonHTML}`;

export const hostFirewallMessage = `<p text-align:left;>SODA is unable to communicate with its server.
If this issue persists it is possible that your network is blocking access.
</p>
${commonHTML}`;
14 changes: 11 additions & 3 deletions src/renderer/src/scripts/globals.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import Swal from "sweetalert2";
import "bootstrap-select";
import DragSort from "@yaireo/dragsort";

import api from "./others/api/api";
import { clientError, userErrorMessage } from "./others/http-error-handler/error-handler";
import client from "./client";
import { swalShowError } from "./utils/swal-utils";
// import { window.clearValidationResults } from './validator/validate'
import { swalShowError, swalShowInfo } from "./utils/swal-utils";
// // Purpose: Will become preload.js in the future. For now it is a place to put global variables/functions that are defined in javascript files
// // needed by the renderer process in order to run.
import { clientBlockedByExternalFirewall, blockedMessage } from "./check-firewall/checkFirewall";

// // Contributors table for the dataset description editing page
const currentConTable = document.getElementById("table-current-contributors");
Expand Down Expand Up @@ -907,6 +906,13 @@ window.resetFFMUI = (ev) => {
};

window.addBfAccount = async (ev, verifyingOrganization = False) => {
// check for external firewall interference (aspirational in that may not be foolproof)
const pennsieveURL = "https://api.pennsieve.io/discover/datasets";
const blocked = await clientBlockedByExternalFirewall(pennsieveURL);
if (blocked) {
await swalShowInfo("Potential Firewall Interference", blockedMessage);
return;
}
let footerMessage = "No existing accounts to load. Please add an account.";
if (window.bfAccountOptionsStatus === "") {
if (Object.keys(bfAccountOptions).length === 1) {
Expand Down Expand Up @@ -1346,6 +1352,8 @@ var dropdownEventID = "";
window.openDropdownPrompt = async (ev, dropdown, show_timer = true) => {
// if users edit current account
if (dropdown === "bf") {
console.log("Calling opendropdown here?");

await window.addBfAccount(ev, false);
} else if (dropdown === "dataset") {
dropdownEventID = ev?.id ?? "";
Expand Down
33 changes: 29 additions & 4 deletions src/renderer/src/scripts/others/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ import {
setPennsieveAgentCheckInProgress,
setPostPennsieveAgentCheckAction,
} from "../../stores/slices/backgroundServicesSlice";
import {
clientBlockedByExternalFirewall,
blockedMessage,
hostFirewallMessage,
} from "../check-firewall/checkFirewall";

// add jquery to the window object
window.$ = jQuery;
Expand Down Expand Up @@ -462,6 +467,24 @@ const startupServerAndApiCheck = async () => {
{ value: 1 }
);

let serverIsLive = await window.server.serverIsLive();
if (serverIsLive) {
// notify the user that there may be a firewall issue preventing the client from connecting to the server
Swal.close();
await Swal.fire({
icon: "info",
title: "Potential Network Issues",
html: hostFirewallMessage,
heightAuto: false,
backdrop: "rgba(0,0,0, 0.4)",
confirmButtonText: "Restart SODA To Try Again",
allowOutsideClick: false,
allowEscapeKey: false,
width: 800,
});
await window.electron.ipcRenderer.invoke("relaunch-soda");
}

Swal.close();
await Swal.fire({
icon: "error",
Expand All @@ -479,6 +502,7 @@ const startupServerAndApiCheck = async () => {
// Check app version on current app and display in the side bar
// Also check the core systems to make sure they are all operational
const initializeSODARenderer = async () => {
// TODO: Add check for internal firewall that blocks us from talking to the server here (detect-firewall)
// check that the server is live and the api versions match
// If this fails after the allotted time, the app will restart
await startupServerAndApiCheck();
Expand All @@ -489,10 +513,11 @@ const initializeSODARenderer = async () => {

//Refresh the Pennsieve account list if the user has connected their Pennsieve account in the past
if (hasConnectedAccountWithPennsieve()) {
try {
// window.updateBfAccountList();
} catch (error) {
clientError(error);
// check for external firewall interference (aspirational in that may not be foolproof)
const pennsieveURL = "https://api.pennsieve.io/discover/datasets";
const blocked = await clientBlockedByExternalFirewall(pennsieveURL);
if (blocked) {
swalShowInfo("Potential Network Issue Detected", blockedMessage);
}
}

Expand Down

0 comments on commit c6eea8c

Please sign in to comment.