ELECTRON-967 (Handle network state and implement retry logic) (#599)

* ELECTRON-967 - Display Error content when there is no active network connection

* ELECTRON-967 - Fix css issue for Windows

* ELECTRON-967 - Optimize code
This commit is contained in:
Kiran Niranjan
2019-03-19 19:00:36 +05:30
committed by Vishwas Shashidhar
parent 565818cfce
commit c1b09e4315
14 changed files with 374 additions and 26 deletions

View File

@@ -25,7 +25,9 @@ const cmds = keyMirror({
setIsInMeeting: null,
setLocale: null,
keyPress: null,
openScreenSharingIndicator: null
openScreenSharingIndicator: null,
cancelNetworkStatusCheck: null,
quitWindow: null,
});
module.exports = {

View File

@@ -5,6 +5,7 @@
* from the renderer process.
*/
const electron = require('electron');
const app = electron.app;
const windowMgr = require('./windowMgr.js');
const log = require('./log.js');
@@ -190,6 +191,12 @@ electron.ipcMain.on(apiName, (event, arg) => {
openScreenSharingIndicator(event.sender, arg.displayId, arg.id);
}
break;
case apiCmds.cancelNetworkStatusCheck:
windowMgr.cancelNetworkStatusCheck();
break;
case apiCmds.quitWindow:
app.quit();
break;
default:
}

126
js/networkError/contents.js Normal file
View File

@@ -0,0 +1,126 @@
const errorContent = (data) => {
return (`<div id="main-frame" class="content-wrapper">
<div id="main-content">
<div class="NetworkError-icon">
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="50px" viewBox="0 0 19.7 32" style="enable-background:new 0 0 19.7 32;" xml:space="preserve">
<style type="text/css">
.st0 {
fill: url(#Shape_8_);
}
.st1 {
fill: url(#Shape_9_);
}
.st2 {
fill: url(#Shape_10_);
}
.st3 {
fill: url(#Shape_11_);
}
.st4 {
fill: url(#Shape_12_);
}
.st5 {
fill: url(#Shape_13_);
}
.st6 {
fill: url(#Shape_14_);
}
.st7 {
fill: url(#Shape_15_);
}
</style>
<title>Symphony_logo</title>
<g id="Page-1">
<g id="Symphony_logo">
<g id="logo2">
<linearGradient id="Shape_8_" gradientUnits="userSpaceOnUse" x1="39.8192" y1="23.9806"
x2="39.2461" y2="23.7258"
gradientTransform="matrix(7.6157 0 0 -3.458 -295.3247 101.0398)">
<stop offset="0" style="stop-color:#197A68"/>
<stop offset="1" style="stop-color:#329D87"/>
</linearGradient>
<path id="Shape" class="st0" d="M2.4,17.4c0,1.2,0.3,2.4,0.8,3.5l6.8-3.5H2.4z"/>
<linearGradient id="Shape_9_" gradientUnits="userSpaceOnUse" x1="28.9162" y1="22.8111" x2="29.9162"
y2="22.8111" gradientTransform="matrix(2.7978 0 0 -4.7596 -73.7035 128.374)">
<stop offset="0" style="stop-color:#1D7E7B"/>
<stop offset="1" style="stop-color:#35B0B7"/>
</linearGradient>
<path id="Shape_1_" class="st1" d="M7.2,21.3C8,21.9,9,22.2,10,22.2v-4.8L7.2,21.3z"/>
<linearGradient id="Shape_10_" gradientUnits="userSpaceOnUse" x1="37.9575" y1="21.1358" x2="38.1785"
y2="21.8684" gradientTransform="matrix(6.1591 0 0 -11.4226 -223.952 256.8769)">
<stop offset="0" style="stop-color:#175952"/>
<stop offset="1" style="stop-color:#3A8F88"/>
</linearGradient>
<path id="Shape_2_" class="st2"
d="M14.4,6.9C13,6.3,11.5,6,10,6C9.4,6,8.8,6,8.2,6.1L10,17.4L14.4,6.9z"/>
<linearGradient id="Shape_11_" gradientUnits="userSpaceOnUse" x1="40.5688" y1="22.0975" x2="41.0294"
y2="22.3767" gradientTransform="matrix(9.5186 0 0 -5.5951 -373.339 140.3243)">
<stop offset="0" style="stop-color:#39A8BA"/>
<stop offset="1" style="stop-color:#3992B4"/>
</linearGradient>
<path id="Shape_3_" class="st3" d="M10,17.4h9.5c0-2-0.6-4-1.8-5.6L10,17.4z"/>
<linearGradient id="Shape_12_" gradientUnits="userSpaceOnUse" x1="41.2142" y1="22.3254" x2="40.7055"
y2="22.5477" gradientTransform="matrix(9.9955 0 0 -5.2227 -404.7955 132.8762)">
<stop offset="0" style="stop-color:#021C3C"/>
<stop offset="1" style="stop-color:#215180"/>
</linearGradient>
<path id="Shape_4_" class="st4" d="M1.5,12.2c-1,1.6-1.5,3.4-1.5,5.2h10L1.5,12.2z"/>
<linearGradient id="Shape_13_" gradientUnits="userSpaceOnUse" x1="33.5112" y1="22.1509" x2="34.5112"
y2="22.1509" gradientTransform="matrix(3.9169 0 0 -6.6631 -125.1783 161.684)">
<stop offset="0" style="stop-color:#23796C"/>
<stop offset="1" style="stop-color:#41BEAF"/>
</linearGradient>
<path id="Shape_5_" class="st5" d="M10,10.8c-1.4,0-2.8,0.4-3.9,1.3l3.9,5.4V10.8z"/>
<linearGradient id="Shape_14_" gradientUnits="userSpaceOnUse" x1="36.1289" y1="21.9578" x2="36.4868"
y2="21.4811" gradientTransform="matrix(5.0353 0 0 -8.5671 -171.5901 208.3326)">
<stop offset="0" style="stop-color:#14466A"/>
<stop offset="1" style="stop-color:#286395"/>
</linearGradient>
<path id="Shape_6_" class="st6" d="M10,26c1.8,0,3.6-0.6,5-1.6l-5-6.9V26z"/>
<linearGradient id="Shape_15_" gradientUnits="userSpaceOnUse" x1="38.5336" y1="23.6558" x2="39.0866"
y2="23.5777" gradientTransform="matrix(6.663 0 0 -3.5931 -244.8399 102.8351)">
<stop offset="0" style="stop-color:#261D49"/>
<stop offset="1" style="stop-color:#483A6D"/>
</linearGradient>
<path id="Shape_7_" class="st7"
d="M16.6,16.4l-6.6,1l6.2,2.6c0.3-0.8,0.5-1.7,0.5-2.6C16.7,17.1,16.6,16.7,16.6,16.4z"/>
</g>
</g>
</g>
</svg>
</div>
<div class="main-message">
<p class="NetworkError-header">${data["Problem connecting to Symphony"] || "Problem connecting to Symphony"}</p>
<p id="NetworkError-reason">
${data["Looks like you are not connected to the Internet. We'll try to reconnect automatically."]
|| "Looks like you are not connected to the Internet. We'll try to reconnect automatically."}
</p>
<div id="error-code" class="NetworkError-error-code">ERR_INTERNET_DISCONNECTED</div>
<button id="cancel-retry-button" class="NetworkError-button">${data["Cancel Retry"] || "Cancel Retry"}</button>
<button id="quit-button" class="NetworkError-button">${data["Quit Symphony"] || "Quit Symphony"}</button>
</div>
</div>
</div>`);
};
module.exports = {
errorContent,
};

44
js/networkError/index.js Normal file
View File

@@ -0,0 +1,44 @@
const { ipcRenderer } = require('electron');
const apiEnums = require('../enums/api.js');
const apiCmds = apiEnums.cmds;
const apiName = apiEnums.apiName;
const htmlContents = require('./contents');
class NetworkError {
constructor() {
this.domParser = new DOMParser();
}
showError(data) {
if (!data) {
return;
}
const { message, error } = data;
const errorContent = this.domParser.parseFromString(htmlContents.errorContent(message), 'text/html');
errorContent.getElementById('error-code').innerText = error || "UNKNOWN_ERROR";
// Add event listeners for buttons
const cancelRetryButton = errorContent.getElementById('cancel-retry-button');
cancelRetryButton.addEventListener('click', () => {
ipcRenderer.send(apiName, {
cmd: apiCmds.cancelNetworkStatusCheck
});
});
const quitButton = errorContent.getElementById('quit-button');
quitButton.addEventListener('click', () => {
ipcRenderer.send(apiName, {
cmd: apiCmds.quitWindow
})
});
const mainFrame = errorContent.getElementById('main-frame');
document.body.appendChild(mainFrame);
}
}
module.exports = {
NetworkError,
};

71
js/networkError/style.css Normal file
View File

@@ -0,0 +1,71 @@
body {
background-color: rgb(247, 247, 247);
color: rgb(100, 100, 100);
margin: 0;
display: flex;
}
a {
color: rgb(17, 85, 204);
text-decoration: none;
}
.NetworkError-header {
color: #333;
font-size: 1.6em;
font-weight: normal;
line-height: 1.25em;
margin-bottom: 16px;
margin-top: 0;
word-wrap: break-word;
}
.NetworkError-icon {
background-repeat: no-repeat;
background-size: 100%;
height: 72px;
margin: 0 0 40px;
width: 72px;
-webkit-user-select: none;
display: inline-block;
}
.NetworkError-error-code {
color: #646464;
text-transform: uppercase;
display: block;
font-size: .8em;
margin-top: 10px;
}
.content-wrapper {
box-sizing: border-box;
line-height: 1.6em;
margin: 14vh auto 0;
max-width: 600px;
width: 100%;
-webkit-text-size-adjust: 100%;
font-size: 100%;
font-family: sans-serif, "Segoe UI", "Helvetica Neue", Arial;
}
.NetworkError-reason {
display: inline;
}
.NetworkError-button {
font-weight: 600;
margin: 20px 0;
text-transform: uppercase;
transform: translatez(0);
background: rgb(66, 133, 244);
border: 0;
border-radius: 2px;
box-sizing: border-box;
color: #fff;
cursor: pointer;
font-size: .875em;
padding: 10px 24px;
transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
user-select: none;
}

View File

@@ -21,7 +21,9 @@ const getMediaSources = require('../desktopCapturer/getSources');
const getMediaSource = require('../desktopCapturer/getSource');
const showScreenSharingIndicator = require('../screenSharingIndicator/showScreenSharingIndicator');
const { TitleBar } = require('../windowsTitlebar');
const { NetworkError } = require('../networkError');
const titleBar = new TitleBar();
const networkError = new NetworkError();
const { buildNumber } = require('../../package.json');
const SnackBar = require('../snackBar').SnackBar;
const KeyCodes = {
@@ -493,6 +495,13 @@ function createAPI() {
}
});
// display network error info
local.ipcRenderer.on('network-error', (event, data) => {
if (data && typeof data === 'object') {
networkError.showError(data);
}
});
/**
* an event triggered by the main process when
* the locale is changed

View File

@@ -10,9 +10,9 @@ const path = require('path');
const nodeURL = require('url');
const querystring = require('querystring');
const filesize = require('filesize');
const fetch = require('electron-fetch').default;
const { getTemplate, getMinimizeOnClose, getTitleBarStyle } = require('./menus/menuTemplate.js');
const loadErrors = require('./dialogs/showLoadError.js');
const isInDisplayBounds = require('./utils/isInDisplayBounds.js');
const getGuid = require('./utils/getGuid.js');
const log = require('./log.js');
@@ -26,6 +26,7 @@ const { isWhitelisted, parseDomain } = require('./utils/whitelistHandler');
const { initCrashReporterMain, initCrashReporterRenderer } = require('./crashReporter.js');
const i18n = require('./translation/i18n');
const getCmdLineArg = require('./utils/getCmdLineArg');
const config = readConfigFileSync();
const SpellChecker = require('./spellChecker').SpellCheckHelper;
const spellchecker = new SpellChecker();
@@ -48,6 +49,8 @@ let isAutoReload = false;
let devToolsEnabled = true;
let isCustomTitleBarEnabled = true;
let titleBarStyles;
let networkStatusCheckIntervalId;
const networkStatusCheckInterval = 5000;
const KeyCodes = {
Esc: 27,
@@ -68,6 +71,12 @@ const MIN_HEIGHT = 300;
const DEFAULT_WIDTH = 300;
const DEFAULT_HEIGHT = 600;
const IGNORE_ERROR_CODES = [
0,
-3,
-111
];
// Certificate transparency whitelist
let ctWhitelist = [];
@@ -174,8 +183,6 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
let url = initialUrl;
let key = getGuid();
const config = readConfigFileSync();
// condition whether to enable custom Windows 10 title bar
isCustomTitleBarEnabled = typeof isCustomTitleBar === 'boolean'
&& isCustomTitleBar
@@ -268,17 +275,6 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
}
}
function retry() {
if (!isOnline) {
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
return;
}
if (mainWindow.webContents) {
mainWindow.webContents.reload();
}
}
// Event needed to hide native menu bar on Windows 10 as we use custom menu bar
mainWindow.webContents.once('did-start-loading', () => {
if ((isCustomTitleBarEnabled || isWindowsOS) && mainWindow && !mainWindow.isDestroyed()) {
@@ -320,15 +316,13 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
}
if (!isOnline) {
loadErrors.showNetworkConnectivityError(mainWindow, url, retry);
} else {
// updates the notify config with user preference
notify.updateConfig({ position: position, display: display });
// removes all existing notifications when main window reloads
notify.reset();
log.send(logLevels.INFO, 'loaded main window url: ' + url);
return;
}
// updates the notify config with user preference
notify.updateConfig({ position: position, display: display });
// removes all existing notifications when main window reloads
notify.reset();
log.send(logLevels.INFO, 'loaded main window url: ' + url);
// ELECTRON-540 - needed to automatically
// select desktop capture source
@@ -345,9 +339,28 @@ function doCreateMainWindow(initialUrl, initialBounds, isCustomTitleBar) {
}
});
mainWindow.webContents.on('did-fail-load', function (event, errorCode,
errorDesc, validatedURL) {
loadErrors.showLoadFailure(mainWindow, validatedURL, errorDesc, errorCode, retry, false);
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDesc, validatedURL) => {
// Verify error code and display appropriate error content
if (!IGNORE_ERROR_CODES.includes(parseInt(errorCode, 10))) {
const message = i18n.getMessageFor('NetworkError');
mainWindow.webContents.insertCSS(fs.readFileSync(path.join(__dirname, '/networkError/style.css'), 'utf8').toString());
mainWindow.webContents.send('network-error', { message, error: errorDesc });
isSymphonyReachable(mainWindow, validatedURL);
}
});
mainWindow.webContents.on('did-stop-loading', () => {
// Verify if SDA ended up in a blank page
mainWindow.webContents.executeJavaScript('document.location.href').then((href) => {
if (href === 'data:text/html,chromewebdata') {
const message = i18n.getMessageFor('NetworkError');
mainWindow.webContents.insertCSS(fs.readFileSync(path.join(__dirname, '/networkError/style.css'), 'utf8').toString());
mainWindow.webContents.send('network-error', { message, error: "stop loading"});
isSymphonyReachable(mainWindow, config.url);
}
}).catch((error) => {
log.send(logLevels.ERROR, `Could not read document.location error: ${error}`);
});
});
// In case a renderer process crashes, provide an
@@ -1304,6 +1317,44 @@ const logBrowserWindowEvents = (browserWindow, windowName) => {
};
/**
* Validates the network by fetching the pod url
* every 5sec, on active reloads the given window
*
* @param window
* @param url
*/
const isSymphonyReachable = (window, url) => {
if (networkStatusCheckIntervalId) {
return;
}
networkStatusCheckIntervalId = setInterval(() => {
fetch(config.url).then((rsp) => {
if (rsp.status === 200 && isOnline) {
window.loadURL(url);
if (networkStatusCheckIntervalId) {
clearInterval(networkStatusCheckIntervalId);
networkStatusCheckIntervalId = null;
}
} else {
log.send(logLevels.INFO, `Symphony down! statusCode: ${rsp.status} is online: ${isOnline}`);
}
}).catch((error) => {
log.send(logLevels.INFO, `Network status check: No active network connection ${error}`);
});
}, networkStatusCheckInterval);
};
/**
* Clears the network status check interval
*/
const cancelNetworkStatusCheck = () => {
if (networkStatusCheckIntervalId) {
clearInterval(networkStatusCheckIntervalId);
networkStatusCheckIntervalId = null;
}
};
module.exports = {
createMainWindow: createMainWindow,
getMainWindow: getMainWindow,
@@ -1322,4 +1373,5 @@ module.exports = {
getIsOnline: getIsOnline,
getSpellchecker: getSpellchecker,
isMisspelled: isMisspelled,
cancelNetworkStatusCheck: cancelNetworkStatusCheck,
};

View File

@@ -68,6 +68,12 @@
"Minimize on Close": "Minimize on Close",
"Native": "Native",
"Network connectivity has been lost. Check your internet connection.": "Network connectivity has been lost. Check your internet connection.",
"NetworkError": {
"Problem connecting to Symphony": "Problem connecting to Symphony",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "Looks like you are not connected to the Internet. We'll try to reconnect automatically.",
"Cancel Retry": "Cancel Retry",
"Quit Symphony": "Quit Symphony"
},
"No crashes available to share": "No crashes available to share",
"No logs are available to share": "No logs are available to share",
"Not Allowed": "Not Allowed",

View File

@@ -68,6 +68,12 @@
"Minimize on Close": "Minimize on Close",
"Native": "Native",
"Network connectivity has been lost. Check your internet connection.": "Network connectivity has been lost. Check your internet connection.",
"NetworkError": {
"Problem connecting to Symphony": "Problem connecting to Symphony",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "Looks like you are not connected to the Internet. We'll try to reconnect automatically.",
"Cancel Retry": "Cancel Retry",
"Quit Symphony": "Quit Symphony"
},
"No crashes available to share": "No crashes available to share",
"No logs are available to share": "No logs are available to share",
"Not Allowed": "Not Allowed",

View File

@@ -68,6 +68,12 @@
"Minimiser on Close": "Minimiser à la fermeture",
"Native": "Originaire",
"Network connectivity has been lost. Check your internet connection.": "La connectivité a été perdue. Vérifiez votre connection à l'internet.",
"NetworkError": {
"Problem connecting to Symphony": "Problème de connexion à Symphony",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "On dirait que vous n'êtes pas connecté à Internet. Nous allons essayer de vous reconnecter automatiquement.",
"Cancel Retry": "Annuler réessayer",
"Quit Symphony": "Quitter Symphony"
},
"No crashes available to share": "Pas de crash à partager",
"No logs are available to share": "Pas de journal à partager",
"Not Allowed": "Interdit",

View File

@@ -68,6 +68,12 @@
"Minimiser on Close": "Minimiser à la fermeture",
"Native": "Originaire",
"Network connectivity has been lost. Check your internet connection.": "La connectivité a été perdue. Vérifiez votre connection à l'internet.",
"NetworkError": {
"Problem connecting to Symphony": "Problème de connexion à Symphony",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "On dirait que vous n'êtes pas connecté à Internet. Nous allons essayer de vous reconnecter automatiquement.",
"Cancel Retry": "Annuler réessayer",
"Quit Symphony": "Quitter Symphony"
},
"No crashes available to share": "Pas de crash à partager",
"No logs are available to share": "Pas de journal à partager",
"Not Allowed": "Interdit",

View File

@@ -68,6 +68,12 @@
"Minimize on Close": "閉じるで最小化",
"Native": "Native",
"Network connectivity has been lost. Check your internet connection.": "ネットワーク接続が失われました。インターネット接続を確認してください。",
"NetworkError": {
"Problem connecting to Symphony": "Symphonyへの接続に関する問題",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "インターネットに接続していないようです。 自動的に再接続します。",
"Cancel Retry": "再試行をキャンセル",
"Quit Symphony": "Symphonyを終了します"
},
"No crashes available to share": "共有できるクラッシュはありません",
"No logs are available to share": "共有できるログはありません",
"Not Allowed": "許可されていませ。",

View File

@@ -68,6 +68,12 @@
"Minimize on Close": "閉じるで最小化",
"Native": "Native",
"Network connectivity has been lost. Check your internet connection.": "ネットワーク接続が失われました。インターネット接続を確認してください。",
"NetworkError": {
"Problem connecting to Symphony": "Symphonyへの接続に関する問題",
"Looks like you are not connected to the Internet. We'll try to reconnect automatically.": "インターネットに接続していないようです。 自動的に再接続します。",
"Cancel Retry": "再試行をキャンセル",
"Quit Symphony": "Symphonyを終了します"
},
"No crashes available to share": "共有できるクラッシュはありません",
"No logs are available to share": "共有できるログはありません",
"Not Allowed": "許可されていませ。",

View File

@@ -113,6 +113,7 @@
"async.mapseries": "0.5.2",
"auto-launch": "5.0.5",
"electron-dl": "1.12.0",
"electron-fetch": "1.3.0",
"electron-log": "2.2.17",
"electron-spellchecker": "git+https://github.com/symphonyoss/electron-spellchecker.git#v1.1.4",
"ffi": "git+https://github.com/symphonyoss/node-ffi.git#v1.2.8",