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

Receivers: Reworked network interface UI

This commit is contained in:
Michael Hollister 2025-04-22 17:28:02 -05:00
parent ed89b61cab
commit 28a617daa7
15 changed files with 491 additions and 246 deletions

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
width="1em"
height="1em">
<path
fill="white"
d="M232 114h-98V86h10a14 14 0 0 0 14-14V40a14 14 0 0 0-14-14h-32a14 14 0 0 0-14 14v32a14 14 0 0 0 14 14h10v28H24a6 6 0 0 0 0 12h34v36H48a14 14 0 0 0-14 14v32a14 14 0 0 0 14 14h32a14 14 0 0 0 14-14v-32a14 14 0 0 0-14-14H70v-36h116v36h-10a14 14 0 0 0-14 14v32a14 14 0 0 0 14 14h32a14 14 0 0 0 14-14v-32a14 14 0 0 0-14-14h-10v-36h34a6 6 0 0 0 0-12M110 72V40a2 2 0 0 1 2-2h32a2 2 0 0 1 2 2v32a2 2 0 0 1-2 2h-32a2 2 0 0 1-2-2M82 176v32a2 2 0 0 1-2 2H48a2 2 0 0 1-2-2v-32a2 2 0 0 1 2-2h32a2 2 0 0 1 2 2m128 0v32a2 2 0 0 1-2 2h-32a2 2 0 0 1-2-2v-32a2 2 0 0 1 2-2h32a2 2 0 0 1 2 2"
></path>
</svg>

After

Width:  |  Height:  |  Size: 729 B

View file

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.44873 1.9375C7.13805 0.6875 8.86195 0.6875 9.55246 1.9375L15.7599 13.1875C15.9172 13.4725 16 13.7959 16 14.125C16 14.4541 15.9172 14.7774 15.7599 15.0625C15.6027 15.3475 15.3764 15.5842 15.104 15.7488C14.8316 15.9133 14.5226 16 14.2081 16H1.79314C1.47848 16.0002 1.16932 15.9137 0.896742 15.7492C0.624165 15.5848 0.397785 15.3481 0.240368 15.063C0.0829523 14.7779 5.04209e-05 14.4545 2.29922e-08 14.1253C-5.03749e-05 13.7961 0.0827525 13.4726 0.240081 13.1875L6.44873 1.9375Z" fill="#fdb022"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.09511 5C8.31294 5 8.52184 5.08889 8.67587 5.24713C8.8299 5.40536 8.91643 5.61997 8.91643 5.84375V9.21875C8.91643 9.44253 8.8299 9.65714 8.67587 9.81537C8.52184 9.97361 8.31294 10.0625 8.09511 10.0625C7.87728 10.0625 7.66837 9.97361 7.51434 9.81537C7.36031 9.65714 7.27378 9.44253 7.27378 9.21875V5.84375C7.27378 5.61997 7.36031 5.40536 7.51434 5.24713C7.66837 5.08889 7.87728 5 8.09511 5ZM8.09511 14C8.38555 14 8.66409 13.8815 8.86946 13.6705C9.07483 13.4595 9.19021 13.1734 9.19021 12.875C9.19021 12.5766 9.07483 12.2905 8.86946 12.0795C8.66409 11.8685 8.38555 11.75 8.09511 11.75C7.80467 11.75 7.52612 11.8685 7.32075 12.0795C7.11538 12.2905 7 12.5766 7 12.875C7 13.1734 7.11538 13.4595 7.32075 13.6705C7.52612 13.8815 7.80467 14 8.09511 14Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em">
<path
fill="white"
d="M12 3C7.79 3 3.7 4.41.38 7C4.41 12.06 7.89 16.37 12 21.5c4.08-5.08 8.24-10.26 11.65-14.5C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36A8.4 8.4 0 0 0 12 13c-1.25 0-2.5.28-3.61.8L3.27 7.44C5.91 5.85 8.93 5 12 5"
></path>
</svg>

After

Width:  |  Height:  |  Size: 383 B

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em">
<path
fill="white"
d="M12 3C7.79 3 3.7 4.41.38 7C4.41 12.06 7.89 16.37 12 21.5c4.08-5.08 8.24-10.26 11.65-14.5C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98C16.26 10.74 14.37 10 12 10c-2.38 0-4.26.75-5.5 1.43L3.27 7.44C5.91 5.85 8.93 5 12 5"
></path>
</svg>

After

Width:  |  Height:  |  Size: 394 B

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em">
<path
fill="white"
d="M12 3C7.79 3 3.7 4.41.38 7C4.41 12.06 7.89 16.37 12 21.5c4.08-5.08 8.24-10.26 11.65-14.5C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43C17.26 9 14.88 8 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.4C5.91 5.85 8.93 5 12 5"
></path>
</svg>

After

Width:  |  Height:  |  Size: 384 B

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em">
<path
fill="white"
d="M12 3C7.79 3 3.7 4.41.38 7C4.41 12.06 7.89 16.37 12 21.5c4.08-5.08 8.24-10.26 11.65-14.5C20.32 4.41 16.22 3 12 3"
></path>
</svg>

After

Width:  |  Height:  |  Size: 270 B

View file

@ -0,0 +1,10 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width="1em"
height="1em">
<path
fill="white"
d="M12 3C7.79 3 3.7 4.41.38 7H.36C4.24 11.83 8.13 16.66 12 21.5c3.89-4.84 7.77-9.67 11.64-14.5h.01C20.32 4.41 16.22 3 12 3m0 2c3.07 0 6.09.86 8.71 2.45L12 18.3L3.27 7.44C5.9 5.85 8.92 5 12 5"
></path>
</svg>

After

Width:  |  Height:  |  Size: 345 B

View file

@ -5,6 +5,7 @@ import * as url from 'url';
import { AddressInfo } from 'modules/ws';
import { v4 as uuidv4 } from 'modules/uuid';
import { Main } from 'src/Main';
import si from 'modules/systeminformation';
export class NetworkService {
static key: string = null;
@ -12,6 +13,8 @@ export class NetworkService {
static proxyServer: http.Server;
static proxyServerAddress: AddressInfo;
static proxiedFiles: Map<string, { url: string, headers: { [key: string]: string } }> = new Map();
static networkStateChangeListenerTimeout = 2500;
private static networkStateChangeListenerInterfaces = [];
private static setupProxyServer(): Promise<void> {
return new Promise<void>((resolve, reject) => {
@ -104,21 +107,54 @@ export class NetworkService {
return proxiedUrl;
}
static getAllIPv4Addresses() {
const interfaces = os.networkInterfaces();
const ipv4Addresses: string[] = [];
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);
for (const interfaceName in interfaces) {
const addresses = interfaces[interfaceName];
if (!addresses) continue;
if (Array.isArray(data)) {
resolve(data);
}
else {
resolve([data]);
}
});
});
for (const addressInfo of addresses) {
if (addressInfo.family === 'IPv4' && !addressInfo.internal) {
ipv4Addresses.push(addressInfo.address);
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 });
}
}
}
return ipv4Addresses;
if (forceUpdate || (JSON.stringify(interfaces) !== JSON.stringify(NetworkService.networkStateChangeListenerInterfaces))) {
NetworkService.networkStateChangeListenerInterfaces = interfaces;
networkChangedCb(interfaces);
}
}
}

View file

@ -1,5 +1,6 @@
export enum ToastIcon {
INFO,
WARNING,
ERROR,
}
@ -40,6 +41,10 @@ function renderToast(message: string, icon: ToastIcon = ToastIcon.INFO, duration
toastIcon.style.backgroundImage = 'url(../assets/icons/app/info.svg)';
break;
case ToastIcon.WARNING:
toastIcon.style.backgroundImage = 'url(../assets/icons/app/warning.svg)';
break;
case ToastIcon.ERROR:
toastIcon.style.backgroundImage = 'url(../assets/icons/app/error.svg)';
break;

View file

@ -7,6 +7,7 @@ const connectionStatusText = document.getElementById("connection-status-text");
const connectionStatusSpinner = document.getElementById("connection-spinner");
const connectionStatusCheck = document.getElementById("connection-check");
let connections = [];
let renderedAddresses = null;
// Window might be re-created while devices are still connected
window.targetAPI.onPing((_event, value: any) => {
@ -56,14 +57,41 @@ function renderIPsAndQRCode() {
const value = window.targetAPI.getDeviceInfo();
console.log("device info", value);
const ipsElement = document.getElementById('ips');
if (ipsElement) {
ipsElement.innerHTML = `IPs<br>${value.addresses.join('<br>')}`;
const addresses = [];
value.interfaces.forEach((e) => addresses.push(e.address));
const connInfo = document.getElementById('connection-information');
const connError = document.getElementById('connection-error');
if (renderedAddresses !== null && addresses.length > 0) {
toast("Network connections has changed, please reconnect sender devices to receiver if you experience issues", ToastIcon.WARNING);
}
else if (addresses.length === 0) {
connInfo.setAttribute("style", "display: none");
connError.setAttribute("style", "display: block");
if (renderedAddresses !== null) {
toast("Lost network connection, please reconnect to a network", ToastIcon.ERROR);
}
renderedAddresses = []
return;
}
if (renderedAddresses !== null && renderedAddresses.length === 0) {
connInfo.setAttribute("style", "display: block");
connError.setAttribute("style", "display: none");
}
renderIPs(value.interfaces);
if (JSON.stringify(addresses) === JSON.stringify(renderedAddresses)) {
return;
}
renderedAddresses = addresses;
const fcastConfig = {
name: value.name,
addresses: value.addresses,
addresses: addresses,
services: [
{ port: 46899, type: 0 }, //TCP
{ port: 46898, type: 1 }, //WS
@ -98,3 +126,54 @@ function renderIPsAndQRCode() {
onQRCodeRendered();
}
function renderIPs(interfaces: any) {
const ipsElement = document.getElementById('ips');
if (ipsElement) {
const ipsIconColumn = document.getElementById('ips-iface-icon');
ipsIconColumn.innerHTML = '';
const ipsTextColumn = document.getElementById('ips-iface-text');
ipsTextColumn.innerHTML = '';
const ipsNameColumn = document.getElementById('ips-iface-name');
ipsNameColumn.innerHTML = '';
for (const iface of interfaces) {
const ipIcon = document.createElement("div");
let icon = 'iconSize ';
if (iface.type === 'wired') {
icon += 'ip-wired-icon';
}
else if (iface.type === 'wireless' && (iface.signalLevel === 0 || iface.signalLevel >= 90)) {
icon += 'ip-wireless-4-icon';
}
else if (iface.type === 'wireless' && iface.signalLevel >= 70) {
icon += 'ip-wireless-3-icon';
}
else if (iface.type === 'wireless' && iface.signalLevel >= 50) {
icon += 'ip-wireless-2-icon';
}
else if (iface.type === 'wireless' && iface.signalLevel >= 30) {
icon += 'ip-wireless-1-icon';
}
else if (iface.type === 'wireless') {
icon += 'ip-wireless-0-icon';
}
ipIcon.className = icon;
ipsIconColumn.append(ipIcon);
const ipText = document.createElement("div");
ipText.className = 'ip-entry-text';
ipText.textContent = iface.address;
ipsTextColumn.append(ipText);
const ipName = document.createElement("div");
ipName.className = 'ip-entry-text';
ipName.textContent = iface.name;
ipsNameColumn.append(ipName);
}
}
}

View file

@ -45,6 +45,12 @@ body, html {
margin-bottom: 3px;
}
.iconSize {
width: 32px;
height: 32px;
background-size: cover;
}
#ui-container {
display: flex;
flex-direction: column;
@ -134,11 +140,85 @@ body, html {
font-weight: bold;
}
#connection-status-text, #ips, #automatic-discovery {
#connection-error {
display: none;
}
#connection-error-icon {
width: 84px;
height: 84px;
margin: auto;
margin-top: 20px;
background-image: url(../assets/icons/app/error.svg);
background-size: cover;
}
#connection-error-text {
margin-top: 20px;
font-weight: bold;
}
#connection-status-text {
margin-top: 20px;
white-space: pre;
}
#ips {
display: flex;
margin-top: 20px;
white-space: pre;
gap: 10px;
justify-content: center;
}
#ips-iface-icon {
display: flex;
flex-direction: column;
}
#ips-iface-text {
display: flex;
flex-direction: column;
margin-right: 20px;
align-items: start;
}
#ips-iface-name {
display: flex;
flex-direction: column;
align-items: start;
}
.ip-entry-text {
margin-top: 4px;
margin-bottom: 4px;
}
.ip-wired-icon {
background-image: url(../assets/icons/app/network-light.svg);
}
.ip-wireless-4-icon {
background-image: url(../assets/icons/app/wifi-strength-4.svg);
}
.ip-wireless-3-icon {
background-image: url(../assets/icons/app/wifi-strength-3.svg);
}
.ip-wireless-2-icon {
background-image: url(../assets/icons/app/wifi-strength-2.svg);
}
.ip-wireless-1-icon {
background-image: url(../assets/icons/app/wifi-strength-1.svg);
}
.ip-wireless-0-icon {
background-image: url(../assets/icons/app/wifi-strength-outline.svg);
}
#connection-spinner {
padding: 20px;
}

View file

@ -19,6 +19,7 @@
"https": "^1.0.0",
"log4js": "^6.9.1",
"qrcode": "^1.5.3",
"systeminformation": "^5.25.11",
"url": "^0.11.4",
"uuid": "^11.0.3",
"ws": "^8.18.0",
@ -34,7 +35,7 @@
"@electron-forge/plugin-auto-unpack-natives": "^7.8.0",
"@electron-forge/plugin-fuses": "^7.8.0",
"@electron/fuses": "^1.8.0",
"@eslint/js": "^9.10.0",
"@eslint/js": "^9.25.0",
"@futo/forge-maker-wix-linux": "^7.5.0",
"@types/electron-json-storage": "^4.5.4",
"@types/jest": "^29.5.11",
@ -44,18 +45,18 @@
"@types/workerpool": "^6.1.1",
"@types/ws": "^8.5.10",
"@types/yargs": "^17.0.33",
"copy-webpack-plugin": "^12.0.2",
"electron": "^32.2.1",
"eslint": "^9.10.0",
"globals": "^15.9.0",
"copy-webpack-plugin": "^13.0.0",
"electron": "^35.2.0",
"eslint": "^9.25.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"mdns-js": "github:mdns-js/node-mdns-js",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
"webpack": "^5.99.6",
"webpack-cli": "^6.0.1"
}
},
"node_modules/@ampproject/remapping": {
@ -598,13 +599,13 @@
"optional": true
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz",
"integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
"node": ">=14.17.0"
}
},
"node_modules/@electron-forge/cli": {
@ -1480,9 +1481,9 @@
}
},
"node_modules/@eslint/core": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz",
"integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==",
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
@ -1530,9 +1531,9 @@
}
},
"node_modules/@eslint/js": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz",
"integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==",
"version": "9.25.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.0.tgz",
"integrity": "sha512-iWhsUS8Wgxz9AXNfvfOPFSW4VfMXdVhp1hjkZVhXCrpgh/aLcc45rX6MPu+tIVUWDw0HfNwth7O28M1xDxNf9w==",
"dev": true,
"license": "MIT",
"engines": {
@ -1563,19 +1564,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@futo/electron-wix-msi-linux": {
"version": "5.1.3",
"resolved": "https://gitlab.futo.org/api/v4/projects/305/packages/npm/@futo/electron-wix-msi-linux/-/@futo/electron-wix-msi-linux-5.1.3.tgz",
@ -2414,19 +2402,6 @@
"url": "https://github.com/sindresorhus/is?sponsor=1"
}
},
"node_modules/@sindresorhus/merge-streams": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
"integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
@ -3136,45 +3111,45 @@
}
},
"node_modules/@webpack-cli/configtest": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
"integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz",
"integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.15.0"
"node": ">=18.12.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
"webpack": "^5.82.0",
"webpack-cli": "6.x.x"
}
},
"node_modules/@webpack-cli/info": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
"integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz",
"integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.15.0"
"node": ">=18.12.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
"webpack": "^5.82.0",
"webpack-cli": "6.x.x"
}
},
"node_modules/@webpack-cli/serve": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
"integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz",
"integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.15.0"
"node": ">=18.12.0"
},
"peerDependencies": {
"webpack": "5.x.x",
"webpack-cli": "5.x.x"
"webpack": "^5.82.0",
"webpack-cli": "6.x.x"
},
"peerDependenciesMeta": {
"webpack-dev-server": {
@ -4434,18 +4409,17 @@
"license": "MIT"
},
"node_modules/copy-webpack-plugin": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz",
"integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==",
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-13.0.0.tgz",
"integrity": "sha512-FgR/h5a6hzJqATDGd9YG41SeDViH+0bkHn6WNXCi5zKAZkeESeSxLySSsFLHqLEVCh0E+rITmCf0dusXWYukeQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.1",
"globby": "^14.0.0",
"normalize-path": "^3.0.0",
"schema-utils": "^4.2.0",
"serialize-javascript": "^6.0.2"
"serialize-javascript": "^6.0.2",
"tinyglobby": "^0.2.12"
},
"engines": {
"node": ">= 18.12.0"
@ -4886,15 +4860,15 @@
}
},
"node_modules/electron": {
"version": "32.3.3",
"resolved": "https://registry.npmjs.org/electron/-/electron-32.3.3.tgz",
"integrity": "sha512-7FT8tDg+MueAw8dBn5LJqDvlM4cZkKJhXfgB3w7P5gvSoUQVAY6LIQcXJxgL+vw2rIRY/b9ak7ZBFbCMF2Bk4w==",
"version": "35.2.0",
"resolved": "https://registry.npmjs.org/electron/-/electron-35.2.0.tgz",
"integrity": "sha512-GHda7oCkN0pA23qzah735DEbRa06IPwlzP3uvjAmf9af8gxdj5i93JEHeQVGVmSVpd7sSb1pfecs9nz7B1q5ag==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@electron/get": "^2.0.0",
"@types/node": "^20.9.0",
"@types/node": "^22.7.7",
"extract-zip": "^2.0.1"
},
"bin": {
@ -5403,16 +5377,6 @@
"global-agent": "^3.0.0"
}
},
"node_modules/electron/node_modules/@types/node": {
"version": "20.17.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz",
"integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
}
},
"node_modules/electron/node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
@ -5448,13 +5412,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/electron/node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true,
"license": "MIT"
},
"node_modules/electron/node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
@ -5641,20 +5598,20 @@
}
},
"node_modules/eslint": {
"version": "9.24.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz",
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
"version": "9.25.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.0.tgz",
"integrity": "sha512-MsBdObhM4cEwkzCiraDv7A6txFXEqtNXOb877TsSp2FCkBNl8JfVQrmiuDqC1IkejT6JLPzYBXx/xAiYhyzgGA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
"@eslint/config-array": "^0.20.0",
"@eslint/config-helpers": "^0.2.0",
"@eslint/core": "^0.12.0",
"@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.13.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "9.24.0",
"@eslint/plugin-kit": "^0.2.7",
"@eslint/js": "9.25.0",
"@eslint/plugin-kit": "^0.2.8",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
@ -6592,9 +6549,9 @@
}
},
"node_modules/globals": {
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz",
"integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==",
"dev": true,
"license": "MIT",
"engines": {
@ -6622,37 +6579,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/globby": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz",
"integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sindresorhus/merge-streams": "^2.1.0",
"fast-glob": "^3.3.3",
"ignore": "^7.0.3",
"path-type": "^6.0.0",
"slash": "^5.1.0",
"unicorn-magic": "^0.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globby/node_modules/ignore": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz",
"integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@ -9507,19 +9433,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/path-type": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz",
"integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pe-library": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pe-library/-/pe-library-1.0.1.tgz",
@ -10794,19 +10707,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/slash": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
"integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/slice-ansi": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
@ -11231,6 +11131,32 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/systeminformation": {
"version": "5.25.11",
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.25.11.tgz",
"integrity": "sha512-jI01fn/t47rrLTQB0FTlMCC+5dYx8o0RRF+R4BPiUNsvg5OdY0s9DKMFmJGrx5SwMZQ4cag0Gl6v8oycso9b/g==",
"license": "MIT",
"os": [
"darwin",
"linux",
"win32",
"freebsd",
"openbsd",
"netbsd",
"sunos",
"android"
],
"bin": {
"systeminformation": "lib/cli.js"
},
"engines": {
"node": ">=8.0.0"
},
"funding": {
"type": "Buy me a coffee",
"url": "https://www.buymeacoffee.com/systeminfo"
}
},
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@ -11404,6 +11330,51 @@
"license": "MIT",
"optional": true
},
"node_modules/tinyglobby": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
"integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/tinyglobby/node_modules/fdir": {
"version": "6.4.4",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
"integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tmp": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@ -11718,19 +11689,6 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/unicorn-magic": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
"integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unique-filename": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
@ -11940,9 +11898,9 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.99.5",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.5.tgz",
"integrity": "sha512-q+vHBa6H9qwBLUlHL4Y7L0L1/LlyBKZtS9FHNCQmtayxjI5RKC9yD8gpvLeqGv5lCQp1Re04yi0MF40pf30Pvg==",
"version": "5.99.6",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.6.tgz",
"integrity": "sha512-TJOLrJ6oeccsGWPl7ujCYuc0pIq2cNsuD6GZDma8i5o5Npvcco/z+NKvZSFsP0/x6SShVb0+X2JK/JHUjKY9dQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11987,43 +11945,40 @@
}
},
"node_modules/webpack-cli": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz",
"integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz",
"integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^2.1.1",
"@webpack-cli/info": "^2.0.2",
"@webpack-cli/serve": "^2.0.5",
"@discoveryjs/json-ext": "^0.6.1",
"@webpack-cli/configtest": "^3.0.1",
"@webpack-cli/info": "^3.0.1",
"@webpack-cli/serve": "^3.0.1",
"colorette": "^2.0.14",
"commander": "^10.0.1",
"commander": "^12.1.0",
"cross-spawn": "^7.0.3",
"envinfo": "^7.7.3",
"envinfo": "^7.14.0",
"fastest-levenshtein": "^1.0.12",
"import-local": "^3.0.2",
"interpret": "^3.1.1",
"rechoir": "^0.8.0",
"webpack-merge": "^5.7.3"
"webpack-merge": "^6.0.1"
},
"bin": {
"webpack-cli": "bin/cli.js"
},
"engines": {
"node": ">=14.15.0"
"node": ">=18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "5.x.x"
"webpack": "^5.82.0"
},
"peerDependenciesMeta": {
"@webpack-cli/generators": {
"optional": true
},
"webpack-bundle-analyzer": {
"optional": true
},
@ -12033,28 +11988,28 @@
}
},
"node_modules/webpack-cli/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
"node": ">=18"
}
},
"node_modules/webpack-merge": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
"integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz",
"integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==",
"dev": true,
"license": "MIT",
"dependencies": {
"clone-deep": "^4.0.1",
"flat": "^5.0.2",
"wildcard": "^2.0.0"
"wildcard": "^2.0.1"
},
"engines": {
"node": ">=10.0.0"
"node": ">=18.0.0"
}
},
"node_modules/webpack-sources": {

View file

@ -22,7 +22,7 @@
"@electron-forge/plugin-auto-unpack-natives": "^7.8.0",
"@electron-forge/plugin-fuses": "^7.8.0",
"@electron/fuses": "^1.8.0",
"@eslint/js": "^9.10.0",
"@eslint/js": "^9.25.0",
"@futo/forge-maker-wix-linux": "^7.5.0",
"@types/electron-json-storage": "^4.5.4",
"@types/jest": "^29.5.11",
@ -32,18 +32,18 @@
"@types/workerpool": "^6.1.1",
"@types/ws": "^8.5.10",
"@types/yargs": "^17.0.33",
"copy-webpack-plugin": "^12.0.2",
"electron": "^32.2.1",
"eslint": "^9.10.0",
"globals": "^15.9.0",
"copy-webpack-plugin": "^13.0.0",
"electron": "^35.2.0",
"eslint": "^9.25.0",
"globals": "^16.0.0",
"jest": "^29.7.0",
"mdns-js": "github:mdns-js/node-mdns-js",
"ts-jest": "^29.1.1",
"ts-loader": "^9.4.2",
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"webpack": "^5.75.0",
"webpack-cli": "^5.0.1"
"webpack": "^5.99.6",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"@vscode/sudo-prompt": "^9.3.1",
@ -56,6 +56,7 @@
"https": "^1.0.0",
"log4js": "^6.9.1",
"qrcode": "^1.5.3",
"systeminformation": "^5.25.11",
"url": "^0.11.4",
"uuid": "^11.0.3",
"ws": "^8.18.0",

View file

@ -206,30 +206,30 @@ export class Main {
ipcMain.on('send-volume-update', (event: IpcMainEvent, value: VolumeUpdateMessage) => {
l.send(Opcode.VolumeUpdate, value);
});
});
ipcMain.on('send-download-request', async () => {
if (!Updater.isDownloading) {
try {
await Updater.downloadUpdate();
Main.mainWindow.webContents.send("download-complete");
} catch (err) {
await dialog.showMessageBox({
type: 'error',
title: 'Failed to download update',
message: err,
buttons: ['OK'],
defaultId: 0
});
ipcMain.on('send-download-request', async () => {
if (!Updater.isDownloading) {
try {
await Updater.downloadUpdate();
Main.mainWindow.webContents.send("download-complete");
} catch (err) {
await dialog.showMessageBox({
type: 'error',
title: 'Failed to download update',
message: err,
buttons: ['OK'],
defaultId: 0
});
Main.logger.error('Failed to download update:', err);
Main.mainWindow.webContents.send("download-failed");
}
Main.logger.error('Failed to download update:', err);
Main.mainWindow.webContents.send("download-failed");
}
});
}
});
ipcMain.on('send-restart-request', async () => {
Updater.restart();
});
ipcMain.on('send-restart-request', async () => {
Updater.restart();
});
ipcMain.handle('updater-progress', async () => { return Updater.updateProgress; });
@ -302,16 +302,27 @@ export class Main {
}
});
let networkStateChangeListener = null;
Main.mainWindow.loadFile(path.join(__dirname, 'main/index.html'));
Main.mainWindow.on('closed', () => {
Main.mainWindow = null;
clearInterval(networkStateChangeListener);
});
Main.mainWindow.maximize();
Main.mainWindow.show();
Main.mainWindow.on('ready-to-show', () => {
Main.mainWindow.webContents.send("device-info", {name: os.hostname(), addresses: NetworkService.getAllIPv4Addresses()});
NetworkService.networkStateChangeListener(true, (interfaces: any) => {
Main.mainWindow.webContents.send("device-info", { name: os.hostname(), interfaces: interfaces });
networkStateChangeListener = setInterval(() => {
NetworkService.networkStateChangeListener(false, (interfaces: any) => {
Main.mainWindow.webContents.send("device-info", { name: os.hostname(), interfaces: interfaces });
});
},
NetworkService.networkStateChangeListenerTimeout);
});
});
}

View file

@ -44,16 +44,30 @@
</div>
</div>
<div id="detail-view" class="card">
<div class="non-selectable card-title">Manual connection information</div>
<div class="non-selectable card-title">Connection Information</div>
<div class="card-title-separator"></div>
<div>
<div id="ips">IPs</div><br />
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
<div id="connection-information">
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app</div>
<canvas id="qr-code"></canvas>
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <a href="https://grayjay.app" target="_blank">https://grayjay.app</a></div>
<br />
<div class="non-selectable card-title">Connection Details</div>
<div class="card-title-separator"></div>
<div>
<div id="ips">
<div id="ips-iface-icon"></div>
<div id="ips-iface-text"></div>
<div id="ips-iface-name"></div>
</div><br />
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</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="automatic-discovery" class="non-selectable">Automatic discovery is available via mDNS</div>
<canvas id="qr-code"></canvas>
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app.</div>
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <a href="https://grayjay.app" target="_blank">https://grayjay.app</a></div>
</div>
</div>