diff --git a/app/config/README.md b/app/config/README.md index 2b75999f..bfe890b9 100644 --- a/app/config/README.md +++ b/app/config/README.md @@ -39,6 +39,7 @@ Here is the list of available arguments and its usage: | disableNotificationSoundIfNotAvailable | Disable chat/meeting start notification sound if status is not Available (e.g. busy, in a call) | true | | disableNotificationWindowFlash | A flag indicates whether to disable window flashing when there is a notification | false | | help | show the available commands | false | +| onlineCheckMethod | Type of network test for checking online status, can be: https, dns, native, none | https | | partition | [BrowserWindow](https://electronjs.org/docs/api/browser-window) webpreferences partition | persist:teams-4-linux | | proxyServer | Proxy Server with format address:port | None | | menubar | A value controls the menu bar behaviour (auto/visible/hidden) | auto | diff --git a/app/config/index.js b/app/config/index.js index f82d0977..1fcfafa9 100644 --- a/app/config/index.js +++ b/app/config/index.js @@ -207,6 +207,12 @@ function argv(configPath) { default: false, describe: 'Enable debug at start', type: 'boolean' + }, + onlineCheckMethod: { + default: 'https', + describe: 'Type of network test for checking online status.', + type: 'string', + choices: ['https', 'dns', 'native', 'none'] } }) .parse(process.argv.slice(1)); diff --git a/app/connectionManager/index.js b/app/connectionManager/index.js index 614a700d..f5e5dc6c 100644 --- a/app/connectionManager/index.js +++ b/app/connectionManager/index.js @@ -1,5 +1,4 @@ -const { ipcMain, powerMonitor } = require('electron'); -const { httpHelper } = require('../helpers'); +const { ipcMain, net, powerMonitor } = require('electron'); const { LucidLog } = require('lucid-log'); let _ConnectionManager_window = new WeakMap(); @@ -55,12 +54,12 @@ class ConnectionManager { async refresh() { const currentUrl = this.window.webContents.getURL(); const hasUrl = currentUrl && currentUrl.startsWith('https://') ? true : false; - const connected = await httpHelper.isOnline(1000, 1); + const connected = await this.isOnline(1000, 1); if (!connected) { this.window.setTitle('Waiting for network...'); this.logger.debug('Waiting for network...'); } - const retryConnected = await httpHelper.isOnline(1000, 30); + const retryConnected = connected || await this.isOnline(1000, 30); if (retryConnected) { if (hasUrl) { this.window.reload(); @@ -72,6 +71,54 @@ class ConnectionManager { this.logger.error('No internet connection'); } } + + /** + * @param {number} timeout + * @param {number} retries + * @returns + */ + async isOnline(timeout, retries) { + const onlineCheckMethod = this.config.onlineCheckMethod; + var resolved = false; + for (var i = 1; i <= retries && !resolved; i++) { + resolved = this.isOnlineTest(onlineCheckMethod, this.config.url); + if (!resolved) await sleep(timeout); + } + if (resolved) { + this.logger.debug('Network test successful with method ' + onlineCheckMethod); + } else { + this.logger.debug('Network test failed with method ' + onlineCheckMethod); + } + return resolved; + } + + async isOnlineTest(onlineCheckMethod, testUrl) { + switch (onlineCheckMethod) { + case 'none': + // That's more an escape gate in case all methods are broken, it disables + // the network test (assumes we're online). + this.logger.warn('Network test is disabled, assuming online status.'); + return true; + case 'dns': + // Sometimes too optimistic, might be false-positive where an HTTP proxy is + // mandatory but not reachable yet. + const testDomain = (new URL(testUrl)).hostname; + this.logger.debug('Testing network using net.resolveHost() for ' + testDomain); + return await isOnlineDns(testDomain); + + case 'native': + // Sounds good but be careful, too optimistic in my experience; and at the contrary, + // might also be false negative where no DNS is available for internet domains, but + // an HTTP proxy is actually available and working. + this.logger.debug('Testing network using net.isOnline()'); + return net.isOnline(); + case 'https': + default: + // Perform an actual HTTPS request, similar to loading the Teams app. + this.logger.debug('Testing network using net.request() for ' + testUrl); + return await isOnlineHttps(testUrl); + } + } } /** @@ -105,4 +152,32 @@ function assignOnDidFailLoadEventHandler(cm) { }; } +function sleep(timeout) { + return new Promise(r => setTimeout(r, timeout)); +} + +function isOnlineHttps(testUrl) { + return new Promise((resolve) => { + var req = net.request({ + url: testUrl, + method: 'HEAD' + }); + req.on('response', () => { + resolve(true); + }); + req.on('error', () => { + resolve(false); + }); + req.end(); + }); +} + +function isOnlineDns(testDomain) { + return new Promise((resolve) => { + net.resolveHost(testDomain) + .then(() => resolve(true)) + .catch(() => resolve(false)); + }); +} + module.exports = new ConnectionManager(); \ No newline at end of file diff --git a/app/helpers/connectionHelper.js b/app/helpers/connectionHelper.js deleted file mode 100644 index 46e7eeee..00000000 --- a/app/helpers/connectionHelper.js +++ /dev/null @@ -1,36 +0,0 @@ -const dns = require('dns'); -async function checkConnectivity(timeout, retries) { - var resolved = false; - for (var i = 1; i <= retries && !resolved; i++) { - if (i > 1) await sleep(timeout); - resolved = await checkConnectionState(); - } - return resolved; -} - -async function checkConnectionState() { - try { - await resolveDNS(); - return true; - } catch (err) { - return false; - } -} - -function sleep(timeout) { - return new Promise(r => setTimeout(r, timeout)); -} - -function resolveDNS() { - return new Promise((res, rej) => { - dns.resolve('www.microsoft.com', (err) => { - if (err) { - rej(err); - } else { - res(); - } - }); - }); -} - -module.exports = checkConnectivity; \ No newline at end of file diff --git a/app/helpers/httpHelper.js b/app/helpers/httpHelper.js index afe3f866..c739c6d4 100644 --- a/app/helpers/httpHelper.js +++ b/app/helpers/httpHelper.js @@ -1,5 +1,4 @@ -const https = require('https'); -const http = require('http'); +const { net } = require('electron'); class HTTPHelper { joinURLs(url1, url2) { @@ -11,28 +10,8 @@ class HTTPHelper { processRequest(url, res, rej); }); } - - /** - * @param {number} timeout - * @param {number} retries - * @param {string} proxyAddress - * @returns - */ - async isOnline(timeout, retries, proxyAddress) { - var resolved = false; - for (var i = 1; i <= retries && !resolved; i++) { - resolved = await isOnlineInternal(proxyAddress); - if (!resolved) await sleep(timeout); - } - return resolved; - } -} - -function sleep(timeout) { - return new Promise(r => setTimeout(r, timeout)); } - function removeLeadingSlash(url) { return (url[0] == '/') ? url = url.substr(1) : url; } @@ -42,7 +21,9 @@ function removeTrailingSlash(url) { } function processRequest(url, resolve, reject) { - const request = getHttpClient(url).request(url, (response) => { + const request = net.request(url); + + request.on('response', (response) => { let data = ''; if (response.statusCode >= 200 && response.statusCode < 300) { response.on('data', (chunk) => { @@ -64,28 +45,4 @@ function processRequest(url, resolve, reject) { request.end(); } -function getHttpClient(url) { - return url.startsWith('http://') ? http : https; -} - -async function isOnlineInternal() { - return await isOnlineInternalWithoutProxy(); -} - -function isOnlineInternalWithoutProxy() { - return new Promise((resolve) => { - var req = https.request({ - host: 'teams.microsoft.com', - method: 'CONNECT' - }); - req.on('connect', () => { - resolve(true); - }); - req.on('error', () => { - resolve(false); - }); - req.end(); - }); -} - -module.exports = new HTTPHelper(); \ No newline at end of file +module.exports = new HTTPHelper(); diff --git a/app/helpers/index.js b/app/helpers/index.js index 00aab928..c45a0a45 100644 --- a/app/helpers/index.js +++ b/app/helpers/index.js @@ -1,6 +1,4 @@ const httpHelper = require('./httpHelper'); -const checkConnectivity = require('./connectionHelper'); module.exports = { - httpHelper, - checkConnectivity -}; \ No newline at end of file + httpHelper +}; diff --git a/app/index.js b/app/index.js index 27f1fc9e..d0dc4e82 100644 --- a/app/index.js +++ b/app/index.js @@ -96,7 +96,6 @@ if (!gotTheLock) { ipcMain.handle('play-notification-sound', playNotificationSound); ipcMain.handle('user-status-changed', userStatusChangedHandler); ipcMain.handle('set-badge-count', setBadgeCountHandler); - downloadCustomBGServiceRemoteConfig(); } // eslint-disable-next-line no-unused-vars @@ -139,6 +138,7 @@ function onAppTerminated(signal) { } function handleAppReady() { + downloadCustomBGServiceRemoteConfig(); process.on('SIGTRAP', onAppTerminated); process.on('SIGINT', onAppTerminated); process.on('SIGTERM', onAppTerminated); diff --git a/com.github.IsmaelMartinez.teams_for_linux.appdata.xml b/com.github.IsmaelMartinez.teams_for_linux.appdata.xml index dd281cf6..daabb23d 100644 --- a/com.github.IsmaelMartinez.teams_for_linux.appdata.xml +++ b/com.github.IsmaelMartinez.teams_for_linux.appdata.xml @@ -14,6 +14,21 @@ https://github.com/IsmaelMartinez/teams-for-linux/issues com.github.IsmaelMartinez.teams_for_linux.desktop + + +
    +
  • Fix: fixed HTTPS network check from behind an HTTP proxy
  • +
  • New: Added --onlineCheckMethod {https,dns,native,none} to enable picking a different network check method (or disabling)
  • +
+
+
+ + +
    +
  • Update: reordering and cleaning up the list of config options
  • +
+
+
    diff --git a/package.json b/package.json index 9b934d1d..46298a41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "teams-for-linux", - "version": "1.3.9", + "version": "1.3.10", "main": "app/index.js", "description": "Unofficial client for Microsoft Teams for Linux", "homepage": "https://github.com/IsmaelMartinez/teams-for-linux",