1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Moved network interface querying off main process

This commit is contained in:
Michael Hollister 2025-04-23 19:21:17 -05:00
parent 28a617daa7
commit 7ce2dcbca3
8 changed files with 143 additions and 123 deletions

View file

@ -1,11 +1,9 @@
import { PlayMessage, PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from 'common/Packets'; import { PlayMessage, PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from 'common/Packets';
import * as os from 'os';
import * as http from 'http'; import * as http from 'http';
import * as url from 'url'; import * as url from 'url';
import { AddressInfo } from 'modules/ws'; import { AddressInfo } from 'modules/ws';
import { v4 as uuidv4 } from 'modules/uuid'; import { v4 as uuidv4 } from 'modules/uuid';
import { Main } from 'src/Main'; import { Main } from 'src/Main';
import si from 'modules/systeminformation';
export class NetworkService { export class NetworkService {
static key: string = null; static key: string = null;
@ -13,8 +11,6 @@ export class NetworkService {
static proxyServer: http.Server; static proxyServer: http.Server;
static proxyServerAddress: AddressInfo; static proxyServerAddress: AddressInfo;
static proxiedFiles: Map<string, { url: string, headers: { [key: string]: string } }> = new Map(); static proxiedFiles: Map<string, { url: string, headers: { [key: string]: string } }> = new Map();
static networkStateChangeListenerTimeout = 2500;
private static networkStateChangeListenerInterfaces = [];
private static setupProxyServer(): Promise<void> { private static setupProxyServer(): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
@ -106,55 +102,4 @@ export class NetworkService {
NetworkService.proxiedFiles.set(proxiedUrl, { url: url, headers: headers }); NetworkService.proxiedFiles.set(proxiedUrl, { url: url, headers: headers });
return proxiedUrl; return proxiedUrl;
} }
static async networkStateChangeListener(forceUpdate: boolean, networkChangedCb: (networkInfo: any) => void) {
const queriedInterfaces: si.Systeminformation.NetworkInterfacesData[] = await new Promise<si.Systeminformation.NetworkInterfacesData[]>((resolve, reject) => {
si.networkInterfaces((data) => {
// console.log(data);
if (Array.isArray(data)) {
resolve(data);
}
else {
resolve([data]);
}
});
});
const wifiConnections: si.Systeminformation.WifiConnectionData[] = await new Promise<si.Systeminformation.WifiConnectionData[]>((resolve, reject) => {
si.wifiConnections((data) => {
// console.log(data);
if (Array.isArray(data)) {
resolve(data);
}
else {
resolve([data]);
}
});
});
const interfaces = [];
for (const iface of queriedInterfaces) {
if (iface.ip4 !== '' && !iface.internal && !iface.virtual) {
const isWireless = wifiConnections.some(e => {
if (e.iface === iface.iface) {
interfaces.push({ type: 'wireless', name: e.ssid, address: iface.ip4, signalLevel: e.quality });
return true;
}
return false;
});
if (!isWireless) {
interfaces.push({ type: 'wired', name: iface.ifaceName, address: iface.ip4 });
}
}
}
if (forceUpdate || (JSON.stringify(interfaces) !== JSON.stringify(NetworkService.networkStateChangeListenerInterfaces))) {
NetworkService.networkStateChangeListenerInterfaces = interfaces;
networkChangedCb(interfaces);
}
}
} }

View file

@ -7,6 +7,7 @@ const connectionStatusText = document.getElementById("connection-status-text");
const connectionStatusSpinner = document.getElementById("connection-spinner"); const connectionStatusSpinner = document.getElementById("connection-spinner");
const connectionStatusCheck = document.getElementById("connection-check"); const connectionStatusCheck = document.getElementById("connection-check");
let connections = []; let connections = [];
let renderedConnectionInfo = false;
let renderedAddresses = null; let renderedAddresses = null;
// Window might be re-created while devices are still connected // Window might be re-created while devices are still connected
@ -66,8 +67,8 @@ function renderIPsAndQRCode() {
toast("Network connections has changed, please reconnect sender devices to receiver if you experience issues", ToastIcon.WARNING); toast("Network connections has changed, please reconnect sender devices to receiver if you experience issues", ToastIcon.WARNING);
} }
else if (addresses.length === 0) { else if (addresses.length === 0) {
connInfo.setAttribute("style", "display: none"); connInfo.style.display = 'none';
connError.setAttribute("style", "display: block"); connError.style.display = 'block';
if (renderedAddresses !== null) { if (renderedAddresses !== null) {
toast("Lost network connection, please reconnect to a network", ToastIcon.ERROR); toast("Lost network connection, please reconnect to a network", ToastIcon.ERROR);
@ -78,8 +79,8 @@ function renderIPsAndQRCode() {
} }
if (renderedAddresses !== null && renderedAddresses.length === 0) { if (renderedAddresses !== null && renderedAddresses.length === 0) {
connInfo.setAttribute("style", "display: block"); connInfo.style.display = 'block';
connError.setAttribute("style", "display: none"); connError.style.display = 'none';
} }
renderIPs(value.interfaces); renderIPs(value.interfaces);
@ -124,6 +125,12 @@ function renderIPsAndQRCode() {
} }
}); });
if (!renderedConnectionInfo) {
const connInfoLoading = document.getElementById('connection-information-loading');
connInfoLoading.style.display = 'none';
connInfo.style.display = 'block';
}
onQRCodeRendered(); onQRCodeRendered();
} }

View file

@ -72,11 +72,15 @@ body, html {
gap: 15vw; gap: 15vw;
font-family: InterVariable; font-family: InterVariable;
font-size: 20px; font-size: 28px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }
#main-view {
padding: 25px;
}
#title-container { #title-container {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -85,7 +89,7 @@ body, html {
#title-text { #title-text {
font-family: Outfit; font-family: Outfit;
font-size: 100px; font-size: 140px;
font-weight: 800; font-weight: 800;
text-align: center; text-align: center;
@ -96,12 +100,12 @@ body, html {
} }
#title-icon { #title-icon {
width: 84px; width: 124px;
height: 84px; height: 124px;
background-image: url(../assets/icons/app/icon.svg); background-image: url(../assets/icons/app/icon.svg);
background-size: cover; background-size: cover;
margin-right: 15px; margin-right: 25px;
} }
#connection-status { #connection-status {
@ -109,35 +113,13 @@ body, html {
text-align: center; text-align: center;
} }
#main-view { #connection-status-text {
padding: 25px;
}
#manual-connection-info {
font-weight: 700;
line-height: 24px;
margin: 10px;
}
#manual-connection-info-separator {
height: 1px;
background: #2E2E2E;
margin-top: 3px;
margin-bottom: 3px;
}
#qr-code {
display: flex;
margin: 20px auto;
flex-direction: column;
align-items: center;
padding: 20px;
background-color: white;
}
#scan-to-connect {
margin-top: 20px; margin-top: 20px;
font-weight: bold; white-space: pre;
}
#connection-spinner {
padding: 20px;
} }
#connection-error { #connection-error {
@ -159,9 +141,37 @@ body, html {
font-weight: bold; font-weight: bold;
} }
#connection-status-text { #connection-information-loading {
display: flex;
flex-direction: column;
align-items: center;
}
#connection-information-loading-text {
width: auto;
height: auto;
margin: 10px 20px;
}
#connection-information {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
}
#scan-to-connect {
margin-top: 20px; margin-top: 20px;
white-space: pre; font-weight: bold;
}
#qr-code {
display: flex;
margin: 20px auto;
flex-direction: column;
align-items: center;
padding: 20px;
background-color: white;
} }
#ips { #ips {
@ -219,10 +229,6 @@ body, html {
background-image: url(../assets/icons/app/wifi-strength-outline.svg); background-image: url(../assets/icons/app/wifi-strength-outline.svg);
} }
#connection-spinner {
padding: 20px;
}
#window-can-be-closed { #window-can-be-closed {
color: #666666; color: #666666;
position: absolute; position: absolute;
@ -230,7 +236,7 @@ body, html {
margin-bottom: 20px; margin-bottom: 20px;
font-family: InterVariable; font-family: InterVariable;
font-size: 18px; font-size: 24px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }
@ -238,15 +244,15 @@ body, html {
.lds-ring { .lds-ring {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 80px; width: 120px;
height: 80px; height: 120px;
} }
.lds-ring div { .lds-ring div {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
position: absolute; position: absolute;
width: 64px; width: 104px;
height: 64px; height: 104px;
margin: 8px; margin: 8px;
border: 8px solid #fff; border: 8px solid #fff;
border-radius: 50%; border-radius: 50%;
@ -275,8 +281,8 @@ body, html {
/* display: inline-block; */ /* display: inline-block; */
display: none; display: none;
position: relative; position: relative;
width: 64px; width: 104px;
height: 64px; height: 104px;
margin: 18px; margin: 18px;
padding: 10px; padding: 10px;
@ -329,8 +335,8 @@ body, html {
} }
#toast-icon { #toast-icon {
width: 48px; width: 88px;
height: 48px; height: 88px;
background-image: url(../assets/icons/app/info.svg); background-image: url(../assets/icons/app/info.svg);
background-size: cover; background-size: cover;
flex-shrink: 0; flex-shrink: 0;
@ -345,7 +351,7 @@ body, html {
word-break: break-word; word-break: break-word;
font-family: InterVariable; font-family: InterVariable;
font-size: 20px; font-size: 28px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }

View file

@ -302,27 +302,28 @@ export class Main {
} }
}); });
let networkStateChangeListener = null;
Main.mainWindow.loadFile(path.join(__dirname, 'main/index.html')); Main.mainWindow.loadFile(path.join(__dirname, 'main/index.html'));
Main.mainWindow.on('closed', () => { Main.mainWindow.on('closed', () => {
Main.mainWindow = null; Main.mainWindow = null;
clearInterval(networkStateChangeListener);
}); });
Main.mainWindow.maximize(); Main.mainWindow.maximize();
Main.mainWindow.show(); Main.mainWindow.show();
Main.mainWindow.on('ready-to-show', () => { Main.mainWindow.on('ready-to-show', () => {
NetworkService.networkStateChangeListener(true, (interfaces: any) => { const networkWorker = new BrowserWindow({
Main.mainWindow.webContents.send("device-info", { name: os.hostname(), interfaces: interfaces }); show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
preload: path.join(__dirname, 'main/networkWorker.js')
}
});
networkStateChangeListener = setInterval(() => { ipcMain.on('network-changed', (event: IpcMainEvent, value: any) => {
NetworkService.networkStateChangeListener(false, (interfaces: any) => { Main.mainWindow.webContents.send("device-info", { name: os.hostname(), interfaces: value });
Main.mainWindow.webContents.send("device-info", { name: os.hostname(), interfaces: interfaces });
});
},
NetworkService.networkStateChangeListenerTimeout);
}); });
networkWorker.loadFile(path.join(__dirname, 'main/worker.html'));
}); });
} }

View file

@ -0,0 +1,47 @@
import { ipcRenderer } from 'electron';
import si from 'modules/systeminformation';
const networkStateChangeListenerTimeout = 2500;
let networkStateChangeListenerInterfaces = [];
networkStateChangeListener(true);
setInterval(networkStateChangeListener, networkStateChangeListenerTimeout);
function networkStateChangeListener(forceUpdate: boolean) {
new Promise<void>((resolve) => {
si.networkInterfaces((data) => {
// console.log(data);
const queriedInterfaces = Array.isArray(data) ? data : [data];
si.wifiConnections((data) => {
// console.log(data);
const wifiConnections = Array.isArray(data) ? data : [data];
const interfaces = [];
for (const iface of queriedInterfaces) {
if (iface.ip4 !== '' && !iface.internal && !iface.virtual) {
const isWireless = wifiConnections.some(e => {
if (e.iface === iface.iface) {
interfaces.push({ type: 'wireless', name: e.ssid, address: iface.ip4, signalLevel: e.quality });
return true;
}
return false;
});
if (!isWireless) {
interfaces.push({ type: 'wired', name: iface.ifaceName, address: iface.ip4 });
}
}
}
if (forceUpdate || (JSON.stringify(interfaces) !== JSON.stringify(networkStateChangeListenerInterfaces))) {
networkStateChangeListenerInterfaces = interfaces;
ipcRenderer.send('network-changed', interfaces);
}
resolve();
});
});
});
}

View file

@ -47,6 +47,14 @@
<div class="non-selectable card-title">Connection Information</div> <div class="non-selectable card-title">Connection Information</div>
<div class="card-title-separator"></div> <div class="card-title-separator"></div>
<div id="connection-information-loading">
<div id="connection-information-loading-text" class="lds-ring">Fetching Network Info...</div>
<div id="connection-information-loading-spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
</div>
<div id="connection-error">
<div id="connection-error-icon"></div>
<div id="connection-error-text">Device not connected to a network</div>
</div>
<div id="connection-information"> <div id="connection-information">
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app</div> <div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app</div>
<canvas id="qr-code"></canvas> <canvas id="qr-code"></canvas>
@ -64,10 +72,6 @@
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div> <div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
</div> </div>
</div> </div>
<div id="connection-error">
<div id="connection-error-icon"></div>
<div id="connection-error-text">Device not connected to a network</div>
</div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<title>FCast Receiver Worker</title>
<meta charset="UTF-8">
</head>
<body>
</body>
</html>

View file

@ -63,6 +63,7 @@ module.exports = [
entry: { entry: {
preload: './src/main/Preload.ts', preload: './src/main/Preload.ts',
renderer: './src/main/Renderer.ts', renderer: './src/main/Renderer.ts',
networkWorker: './src/main/NetworkWorker.ts'
}, },
target: 'electron-renderer', target: 'electron-renderer',
module: { module: {