From 8eb2d69e13a9584cc7a6887e7066e3f9de39b715 Mon Sep 17 00:00:00 2001 From: Tobias Hopp Date: Fri, 29 Mar 2024 01:29:30 +0100 Subject: [PATCH] Update --- package-lock.json | 46 +++++++++- scripts/addWifi.sh | 46 ++++++++++ scripts/connectToWifi.sh | 88 ++++++++++++++++++- src/IPCConstants.ts | 10 ++- src/OSHandler.ts | 84 ++++++++++++++---- src/SmartMonopoly.ts | 4 +- src/web/Startup.tsx | 181 ++++++++++++++++++++++++++++----------- src/web/WiFi.tsx | 23 +++-- src/web/index.css | 5 +- yarn.lock | 4 +- 10 files changed, 408 insertions(+), 83 deletions(-) create mode 100644 scripts/addWifi.sh diff --git a/package-lock.json b/package-lock.json index 0a08709..b915df0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,10 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@fontsource/roboto": "^5.0.12", + "@mui/icons-material": "^5.15.14", "@mui/material": "^5.15.14", "electron-squirrel-startup": "^1.0.0", + "node-wifi-scanner": "git+https://git.gaminggeneration.de/tobiash/node-wifi-scanner", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -1431,6 +1433,31 @@ "url": "https://opencollective.com/mui-org" } }, + "node_modules/@mui/icons-material": { + "version": "5.15.14", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz", + "integrity": "sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw==", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@mui/material": { "version": "5.15.14", "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.14.tgz", @@ -2887,6 +2914,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -7548,7 +7580,6 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, "license": "MIT" }, "node_modules/lodash._reinterpolate": { @@ -8281,6 +8312,19 @@ "dev": true, "license": "MIT" }, + "node_modules/node-wifi-scanner": { + "version": "1.1.3", + "resolved": "git+https://git.gaminggeneration.de/tobiash/node-wifi-scanner#c5cbde1a3cd51687dd7fa887be8ae075417a0ca4", + "license": "MIT", + "dependencies": { + "async": "3.2.4", + "lodash": "4.17.21" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 2.0.0" + } + }, "node_modules/nopt": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", diff --git a/scripts/addWifi.sh b/scripts/addWifi.sh new file mode 100644 index 0000000..ee9c33f --- /dev/null +++ b/scripts/addWifi.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + + +file="/etc/wpa_supplicant/wpa_supplicant-monopoly.conf" + +ssid="" +psk="" + +# Parse arguments +while getopts ":s:p:" opt; do + case ${opt} in + s ) + ssid="$OPTARG" + ;; + p ) + psk="$OPTARG" + ;; + : ) + echo "Invalid option: $OPTARG" 1>&2 + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + +if [ ! -f "$file" ]; then + sudo mkdir -p /etc/wpa_supplicant/ + echo "ctrl_interface=/run/wpa_supplicant +update_config=1 +" | sudo tee "$file" >/dev/null +fi + +# Validate arguments +if [ -z "$ssid" ] || [ -z "$psk" ]; then + echo "invalid-args" + exit 1 +fi + +echo " +network={ + ssid=\"$ssid\" + psk=\"$psk\" +} +" | sudo tee -a "$file" >/dev/null + +echo "ok" \ No newline at end of file diff --git a/scripts/connectToWifi.sh b/scripts/connectToWifi.sh index 5850dad..5aa0203 100644 --- a/scripts/connectToWifi.sh +++ b/scripts/connectToWifi.sh @@ -1,12 +1,94 @@ #!/bin/bash -sudo ip addr flush dev wlan0 +iface=$(iw dev | awk '$1=="Interface"{print $2}' | grep '^wlan') +file="/etc/wpa_supplicant/wpa_supplicant-monopoly.conf" + +ssid="no_args_given" + +# Parse arguments +while getopts ":s:p:" opt; do + case ${opt} in + s ) + ssid="$OPTARG" + ;; + : ) + echo "Invalid option: $OPTARG" 1>&2 + exit 1 + ;; + esac +done +shift $((OPTIND -1)) + + + +# Remove all disabled +sudo sed -i '/disabled=1/d' "$file" + +# Temporary file to store modified content +temp_file=$(mktemp) + +# Use awk to add disabled=1 within each network block +awk ' +BEGIN { RS="\n\n"; FS="\n"; OFS="\n" } +{ + found=0 + for(i=1; i<=NF; i++) { + if ($i ~ /^network=/) { + for(j=i+1; j<=NF && $j !~ /^}/; j++) { + if ($j ~ /^disabled=1/) { + found=1 + break + } + } + if (!found) { + $i = $i "\n disabled=1" + } + } + } + print $0 "\n" +}' "$file" > "$temp_file" + +# Overwrite the original file with the modified content +sudo mv -f "$temp_file" "$file" + + +temp_file=$(mktemp) + +while IFS= read -r line +do + if [[ "$line" == *"$ssid"* ]]; then + echo $line $ssid + remove_line=1 + echo "remove_line=1" + fi + + if [[ "$remove_line" -eq 1 && "$line" == *"disabled=1"* ]]; then + echo "skipped disabled line" + continue + fi + + if [[ "$line" == *"}"* ]]; then + in_block=0 + remove_line=0 + fi + + echo "$line" >> "$temp_file" +done < "$file" + +# Overwrite the original file with the modified content +sudo mv -f "$temp_file" "$file" + +cat $file +exit 0 + + +sudo ip addr flush dev $iface sudo killall wpa_supplicant sudo truncate -s 0 /tmp/wifi_connection_status.txt -sudo wpa_supplicant -B -i wlan0 -f /tmp/wifi_connection_status.txt -c /etc/wpa_supplicant/wpa_supplicant.conf +sudo wpa_supplicant -B -i $iface -f /tmp/wifi_connection_status.txt -c $file declare -i i=0 -declare -i timeout=10 +declare -i timeout=5 while [ $i -le $timeout ]; do if grep -iq 'CTRL-EVENT-CONNECTED' /tmp/wifi_connection_status.txt; then sudo dhclient wlan0 diff --git a/src/IPCConstants.ts b/src/IPCConstants.ts index c3a9334..e6871d3 100644 --- a/src/IPCConstants.ts +++ b/src/IPCConstants.ts @@ -1,6 +1,13 @@ -export type IPCChannel = 'WIFI_STATUS' | 'WIFI_SCAN' | 'WIFI_LIST' | 'WIFI_CONNECT' | 'FUNCTION_TEST'; +export type IPCChannel = + 'WIFI_STATUS' + | 'WIFI_SCAN' + | 'WIFI_LIST' + | 'WIFI_CONNECT' + | 'FUNCTION_TEST' + | 'CLOUD_STATUS' + | 'CLOUD_CONNECT' export interface IPCAnswer { @@ -16,6 +23,7 @@ export interface WiFiNetwork { ssid: string, isSecured: boolean, isKnown?: boolean, + psk?: string, rssi?: number, } diff --git a/src/OSHandler.ts b/src/OSHandler.ts index 41ff6ab..5d9e78c 100644 --- a/src/OSHandler.ts +++ b/src/OSHandler.ts @@ -1,20 +1,80 @@ import {WiFiNetwork} from "./IPCConstants"; import {spawn, exec} from 'node:child_process'; +import * as dns from "dns"; +import * as process from "process"; +import * as path from "path"; + const wifiScan = require("node-wifi-scanner"); export default class OSHandler { static getKnownWifis(): Promise { return new Promise((resolve, reject) => { + exec("sudo touch /etc/wpa_supplicant/wpa_supplicant-monopoly.conf && sudo cat /etc/wpa_supplicant/wpa_supplicant-monopoly.conf", (err, stdout) => { + if(err) + return reject(err); + + let lines = stdout.split("\n"); + let wifis: WiFiNetwork[] = []; + + + let inBlock = false; + let currentNetwork: WiFiNetwork = {ssid: "", psk: "", isSecured: false}; + for(let line of lines) + { + if(line.includes("network={")) + inBlock = true; + + if(line.includes("}")) { + inBlock = false; + currentNetwork.isSecured = !!currentNetwork.psk; + if(currentNetwork.ssid) + wifis.push(currentNetwork); + currentNetwork.ssid = ""; + currentNetwork.psk = ""; + currentNetwork.isSecured = false; + } + + if(inBlock && line.includes("ssid")) + currentNetwork.ssid = line.substring(line.indexOf('"')+1, line.lastIndexOf('"')); + if(inBlock && line.includes("psk")) + currentNetwork.psk = line.substring(line.indexOf('"')+1, line.lastIndexOf('"')); + } + + resolve(wifis); + }) + }); + } + + static addWifi(wifi: string, passkey: string | null) { + return new Promise((resolve, reject) => { + let p = path.resolve(process.cwd(), "/scripts/addWifi.sh"); + exec(p + ` -s "${wifi}" -p "${passkey}"`, (err, stdout) => { + if(err) + return reject(err); + if(stdout == "ok") + resolve(); + else + reject("no-return"); + }) + }); + } + + static connectToWifi(ssid: string) + { + return new Promise((resolve, reject) => { + + let p = path.resolve(process.cwd(), "/scripts/connectToWifi.sh"); + exec(p + ``) + }); + } + + static disableAllWifis() { + return new Promise((resolve, reject) => { }); } - static addWifi(wifi: WiFiNetwork, passkey: string|null) { - return new Promise((resolve, reject) => { - - }); - } static scanWifis() { return new Promise((resolve, reject) => { @@ -66,7 +126,6 @@ export default class OSHandler { if (x.ssid == ele.ssid) { return i == index; } - i++; } return true; @@ -78,11 +137,6 @@ export default class OSHandler { }) } - static disableAllWifis() { - return new Promise((resolve, reject) => { - - }); - } static checkForSudo() { return new Promise((resolve) => { @@ -102,12 +156,12 @@ export default class OSHandler { static checkForInternet() { return new Promise((resolve) => { - exec("ping -q -w 1 -c 1 `ip r | grep default | cut -d ' ' -f 3` > /dev/null && echo ok || echo error", (error, stdout, stderr) => { - if (error) + dns.lookup("google.de", 4, (err, address, family) => { + if (err) resolve(false); else - resolve(stdout == "1"); - }); + resolve(true); + }) }); } diff --git a/src/SmartMonopoly.ts b/src/SmartMonopoly.ts index cb65f4f..b32f546 100644 --- a/src/SmartMonopoly.ts +++ b/src/SmartMonopoly.ts @@ -12,7 +12,7 @@ const wifiScan = require("node-wifi-scanner"); export default class SmartMonopoly { static run() { this.setupIPCEvents(); - OSHandler.checkForSudo().then(r => console.log("Wifistatus " + r)) + OSHandler.getKnownWifis().then(console.log); } static setupIPCEvents() { @@ -30,7 +30,7 @@ export default class SmartMonopoly { }); IPCHandler("WIFI_CONNECT", async (e, request, args) => { - let data = request.data as {wifi: WiFiNetwork, password: string} + let data = request.data as {wifi: string, password: string} await OSHandler.addWifi(data.wifi, data.password); diff --git a/src/web/Startup.tsx b/src/web/Startup.tsx index 4930a22..f43ce9a 100644 --- a/src/web/Startup.tsx +++ b/src/web/Startup.tsx @@ -9,18 +9,22 @@ import { Typography, Button, Dialog, - DialogTitle, DialogContent, DialogContentText, DialogActions + DialogTitle, DialogContent, DialogContentText, DialogActions, Chip, FormControl } from "@mui/material"; import {FunctionTest} from "../IPCConstants"; - +import SettingsIcon from '@mui/icons-material/Settings'; +import PlayCircleIcon from '@mui/icons-material/PlayCircle'; +import CloudOffIcon from '@mui/icons-material/CloudOff'; +import CloudIcon from '@mui/icons-material/Cloud'; interface StartupState { statusTxt: string, - open: boolean, - nextStep: boolean, - cloudConnect: boolean, - connectionIssue: boolean, + openCloudConnectModal: boolean, + showStartBtn: boolean, + isConnected: boolean, + isConnectionIssue: boolean, openWifiQuestion: boolean, + startCounter: number, } export default class Startup extends Component<{}, StartupState> { @@ -28,19 +32,22 @@ export default class Startup extends Component<{}, StartupState> { super(props); this.state = { statusTxt: "Smart-Monopoly wird gestartet...", - open: false, - nextStep: false, - cloudConnect: false, - connectionIssue: false, - openWifiQuestion: false + openCloudConnectModal: false, + showStartBtn: false, + isConnected: false, + isConnectionIssue: false, + openWifiQuestion: false, + startCounter: 10, }; } + counterInterval: string | number | NodeJS.Timeout; + componentDidMount() { setTimeout(() => { this.setState((prevState) => ({ ...prevState, - open: true, + openCloudConnectModal: true, statusTxt: "Möchten Sie CloudConnect+ nutzen?" })); this.cloudDecision(true).then(); @@ -51,25 +58,18 @@ export default class Startup extends Component<{}, StartupState> { } - connectToCloud = () => { - - } - - checkForNext = () => { - if(this.state.cloudConnect) - { - // Connect to cloud - } - else - { - // Just start + connectToCloud = async (): Promise => { + try { + let response = await window.api.request("CLOUD_CONNECT", {}); + return response.status; + } catch (e) { + return false; } } - handleClose = () => this.setState((prevState) => ({ - ...prevState, - open: false - })); + startupBtnClick = () => { + // Startup handle + } style = { position: 'absolute', @@ -83,41 +83,80 @@ export default class Startup extends Component<{}, StartupState> { p: 4, }; + + async cloudDecision(decision: boolean) { - this.handleClose(); this.setState((prevState) => ({ ...prevState, - cloudConnect: decision, - openWifiQuestion: false + openWifiQuestion: false, + openCloudConnectModal: false, + isConnectionIssue: false, + isConnected: false, + startCounter: 30 })); + clearInterval(this.counterInterval); - if(decision) { + if (decision) { this.setState((prevState) => ({ ...prevState, - statusTxt: "WiFi-Verbindung wird hergestellt..." + statusTxt: "Internetverbindung wird geprüft..." })); let status = (await window.api.request("FUNCTION_TEST", {})).data as FunctionTest; - if(!status.hasInternet) - { + if (!status.hasInternet) { this.setState((prevState) => ({ ...prevState, - openWifiQuestion: true + openWifiQuestion: true, // Weiterleiten auf WiFiFrage + statusTxt: "Warten auf Netzwerkkonfiguration..." })); + } else { + this.setState((prevState) => ({ + ...prevState, + statusTxt: "Warten auf Cloud...", + })); + + this.connectToCloud().then((connected) => { + this.setState((prevState) => ({ + ...prevState, + statusTxt: "Bereit zum spielen?", + showStartBtn: true, + isConnectionIssue: !connected, + isConnected: connected + }) + ) + if(connected) + this.counterInterval = setInterval(() => { + this.setState((prevState) => ({ + ...prevState, + startCounter: prevState.startCounter-1 + })); + if(this.state.startCounter == 0) { + clearInterval(this.counterInterval); + this.startupBtnClick(); + } + }, 1000); + }); } - else - { - this.checkForNext(); - } - } - else { + } else { this.setState((prevState) => ({ ...prevState, - statusTxt: "Ready to go!" + statusTxt: "Bereit zum spielen?", + showStartBtn: true, + startCounter: 30 })); - this.checkForNext(); + this.counterInterval = setInterval(() => { + this.setState((prevState) => ({ + ...prevState, + startCounter: prevState.startCounter-1 + })); + if(this.state.startCounter == 0) { + clearInterval(this.counterInterval); + this.startupBtnClick(); + } + }, 1000); } + } render() { @@ -142,14 +181,41 @@ export default class Startup extends Component<{}, StartupState> { - + + + + + + Cloud-Verbindung fehlgeschlagen! + + + + Die Cloud-Verbindung konnte nicht hergestellt werden.
+ Möglicherweise liegt ein Problem mit der Internetverbindung vor,
+ oder die Cloud ist derzeit in Wartung.
+
+
+ + + +
{ }, }} > - + CloudConnect+ nutzen? @@ -171,19 +237,34 @@ export default class Startup extends Component<{}, StartupState> { Dafür wird eine WiFi-Verbindung hergestellt

- +
- + +

Willkommen!


{this.state.statusTxt}


- + {!this.state.showStartBtn && } + + + + {this.state.showStartBtn && } + + + {this.state.showStartBtn && } + + + this.setState(prevState => ({ + ...prevState, + openCloudConnectModal: true + }))} label={this.state.isConnected ? : }/> +
diff --git a/src/web/WiFi.tsx b/src/web/WiFi.tsx index a77a8f1..f67072b 100644 --- a/src/web/WiFi.tsx +++ b/src/web/WiFi.tsx @@ -23,6 +23,7 @@ import {IPCAnswer, WiFiNetwork} from "../IPCConstants"; interface WiFiState { open: boolean, currentSelection: string, + selectedSecured: boolean, foundWiFis: WiFiNetwork[], scanning: boolean, status: status @@ -36,6 +37,7 @@ export default class WiFi extends Component<{}, WiFiState> { this.state = { open: true, currentSelection: "pleaseSelect", + selectedSecured: false, foundWiFis: [], scanning: false, status: "NONE", @@ -107,10 +109,20 @@ export default class WiFi extends Component<{}, WiFiState> { } onChange = (event: SelectChangeEvent) => { + + let isSecured = false; + for(let x of this.state.foundWiFis) + { + if(x.ssid == event.target.value) + isSecured = x.isSecured; + } + this.setState((prevState) => ({ ...prevState, - currentSelection: event.target.value + currentSelection: event.target.value, + selectedSecured: isSecured, })); + } render() { @@ -151,8 +163,7 @@ export default class WiFi extends Component<{}, WiFiState> { labelId="wifi-select-label" id="wifi-select" value={this.state.currentSelection} - label="Age" - + label="WiFi" disabled={this.state.scanning} onChange={this.onChange} > @@ -182,7 +193,7 @@ export default class WiFi extends Component<{}, WiFiState> { - + @@ -210,10 +221,6 @@ export default class WiFi extends Component<{}, WiFiState> { onClick={() => this.handleClose()} color="error">Abbrechen - - - - diff --git a/src/web/index.css b/src/web/index.css index 25740b0..0773a7d 100644 --- a/src/web/index.css +++ b/src/web/index.css @@ -43,5 +43,8 @@ body { } - +#root { + width: 100%; + height: 100%; +} diff --git a/yarn.lock b/yarn.lock index 4a1c10e..4533b33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -732,7 +732,7 @@ "@mui/icons-material@^5.15.14": version "5.15.14" - resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.14.tgz#333468c94988d96203946d1cfeb8f4d7e8e7de34" + resolved "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.14.tgz" integrity sha512-vj/51k7MdFmt+XVw94sl30SCvGx6+wJLsNYjZRgxhS6y3UtnWnypMOsm3Kmg8TN+P0dqwsjy4/fX7B1HufJIhw== dependencies: "@babel/runtime" "^7.23.9" @@ -1639,7 +1639,7 @@ astral-regex@^2.0.0: async@3.2.4: version "3.2.4" - resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" + resolved "https://registry.npmjs.org/async/-/async-3.2.4.tgz" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== at-least-node@^1.0.0: