diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index 51b5305..74b9acd 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -55,7 +55,7 @@ jobs: run: pnpm install - name: build Tauri app - run: pnpm tauri build -- ${{ matrix.args }} + run: pnpm run tauri:build -- ${{ matrix.args }} env: TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} @@ -65,23 +65,27 @@ jobs: run: | mkdir -p pkg_temp/scripts cp scripts/postinstall.sh pkg_temp/scripts/ - APP_PATH=$(find src-tauri/target/release/bundle/macos/ -name "*.app" | head -1) + MACOS_BUNDLE_DIR=$(find src-tauri/target -path "*/release/bundle/macos" -type d | head -n 1) + APP_PATH=$(find "$MACOS_BUNDLE_DIR" -name "*.app" -type d | head -n 1) + if [ -z "$APP_PATH" ]; then + echo "No .app found in $MACOS_BUNDLE_DIR" + exit 1 + fi APP_NAME=$(basename "$APP_PATH") pkgbuild --root "$APP_PATH/.." \ --install-location /Applications \ --scripts pkg_temp/scripts \ --identifier "com.emerald.legacy" \ --version "1.0.0" \ - --install-location "/Applications" \ - --root "$APP_PATH" \ "Emerald-Legacy-Launcher.pkg" - mv "Emerald-Legacy-Launcher.pkg" src-tauri/target/release/bundle/macos/ + mv "Emerald-Legacy-Launcher.pkg" "$MACOS_BUNDLE_DIR/" - name: upload build artifacts uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact-name }} - path: src-tauri/target/release/bundle/ + path: | + src-tauri/target/*/release/bundle/**/* build-flatpak: runs-on: ubuntu-latest diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index f0c8549..701cc33 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -14,12 +14,16 @@ jobs: include: - platform: 'macos-latest' args: '--target aarch64-apple-darwin' + artifact-name: macos-aarch64 - platform: 'macos-latest' args: '--target x86_64-apple-darwin' + artifact-name: macos-x86_64 - platform: 'ubuntu-22.04' args: '' + artifact-name: linux - platform: 'windows-latest' args: '' + artifact-name: windows runs-on: ${{ matrix.platform }} steps: @@ -53,18 +57,34 @@ jobs: - name: install frontend dependencies run: pnpm install - - name: build and fix macOS - if: matrix.platform == 'macos-latest' - run: | - pnpm tauri build ${{ matrix.args }} - pnpm run post-build:macos - - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: build Tauri app + run: pnpm run tauri:build -- ${{ matrix.args }} + + - name: upload build artifacts + uses: actions/upload-artifact@v4 with: - tagName: nightly - releaseName: 'Emerald Nightly' - releaseBody: | + name: ${{ matrix.artifact-name }} + path: | + src-tauri/target/*/release/bundle/**/* + + nightly-release: + needs: [nightly-tauri, nightly-flatpak] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: '*' + merge-multiple: true + path: artifacts + + - uses: softprops/action-gh-release@v2 + with: + tag_name: nightly + name: 'Emerald Nightly' + body: | # Emerald Legacy Launcher (Nightly) # What to download? - `Emerald.Legacy.Launcher-*.rpm` for GNU/Linux (RHEL) @@ -77,16 +97,17 @@ jobs: --- # Windows SmartScreen This software is completely safe. Because the launcher is unsigned, you will see a "Windows protected your PC" window. To proceed: - + 1. Click the **"More info"** link under the main text. 2. Click the **"Run anyway"** button that appears. --- # macOS "Damaged" Error Fix The macOS builds now include automatic fixes for Gatekeeper quarantine issues. The app should launch without "application is damaged" errors. - - releaseDraft: false + files: artifacts/* + fail_on_unmatched_files: false prerelease: true - args: ${{ matrix.args }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} nightly-flatpak: runs-on: ubuntu-latest @@ -104,9 +125,7 @@ jobs: bundle: io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.flatpak manifest-path: flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml cache-key: flatpak-builder-${{ github.sha }} - - uses: softprops/action-gh-release@v2 + - uses: actions/upload-artifact@v4 with: - files: io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.flatpak - tag_name: nightly - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + name: flatpak + path: io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.flatpak diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 843eaa0..4c1e919 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: 'publish' +name: "publish" on: workflow_dispatch: @@ -20,14 +20,14 @@ jobs: fail-fast: false matrix: include: - - platform: 'macos-latest' - args: '--target aarch64-apple-darwin' - - platform: 'macos-latest' - args: '--target x86_64-apple-darwin' - - platform: 'ubuntu-22.04' - args: '' - - platform: 'windows-latest' - args: '' + - platform: "macos-latest" + args: "--target aarch64-apple-darwin" + - platform: "macos-latest" + args: "--target x86_64-apple-darwin" + - platform: "ubuntu-22.04" + args: "" + - platform: "windows-latest" + args: "" runs-on: ${{ matrix.platform }} steps: @@ -47,7 +47,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - cache: 'pnpm' + cache: "pnpm" - name: install Rust stable uses: dtolnay/rust-toolchain@stable @@ -57,69 +57,43 @@ jobs: - name: Rust cache uses: swatinem/rust-cache@v2 with: - workspaces: './src-tauri -> target' + workspaces: "./src-tauri -> target" - name: install frontend dependencies run: pnpm install - - name: Create PKG with post-install script + + - name: build Tauri app + run: pnpm run tauri:build -- ${{ matrix.args }} + env: + TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} + + - name: Create PKG with post-install script (macOS only) if: matrix.platform == 'macos-latest' run: | - # Create temporary directory for PKG creation mkdir -p pkg_temp/scripts cp scripts/postinstall.sh pkg_temp/scripts/ - - # Build the app first - pnpm tauri build -- ${{ matrix.args }} - - # Find the built app - APP_PATH=$(find src-tauri/target/release/bundle/macos/ -name "*.app" | head -1) + MACOS_BUNDLE_DIR=$(find src-tauri/target -path "*/release/bundle/macos" -type d | head -n 1) + APP_PATH=$(find "$MACOS_BUNDLE_DIR" -name "*.app" -type d | head -n 1) + if [ -z "$APP_PATH" ]; then + echo "No .app found in $MACOS_BUNDLE_DIR" + exit 1 + fi APP_NAME=$(basename "$APP_PATH") - - # Create PKG with post-install script pkgbuild --root "$APP_PATH/.." \ --install-location /Applications \ --scripts pkg_temp/scripts \ --identifier "com.emerald.legacy" \ --version "1.0.0" \ - --install-location "/Applications" \ - --root "$APP_PATH" \ "Emerald-Legacy-Launcher.pkg" - - # Move PKG to bundle directory - mkdir -p src-tauri/target/release/bundle/macos/ - mv "Emerald-Legacy-Launcher.pkg" src-tauri/target/release/bundle/macos/ - - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} - with: - uploadPlainBinary: true - tagName: v__VERSION__ - releaseName: 'Emerald v__VERSION__' - releaseBody: | - # What to download? - - `Emerald.Legacy.Launcher-*.rpm` for GNU/Linux (RHEL) - - `Emerald.Legacy.Launcher-*.deb` for GNU/Linux (Debian+Ubuntu) - - `Emerald.Legacy.Launcher-*.AppImage` for GNU/Linux (Universal, Currently Broken) - - `Emerald.Legacy.Launcher-*_x64.dmg` for macOS (Intel) - - `Emerald.Legacy.Launcher-*_aarch64.dmg` for macOS (M-series) - - `Emerald.Legacy.Launcher-*.exe` for Windows (Setup) - - `Emerald.Legacy.Launcher-*.msi` for Windows (Setup) - - `Emerald.Legacy.Launcher.flatpak` for GNU/Linux (Flatpak, Recommended) - --- - # Windows SmartScreen - This software is completely safe. Because the launcher is unsigned, you will see a "Windows protected your PC" window. To proceed: - - 1. Click the **"More info"** link under the main text. - 2. Click the **"Run anyway"** button that appears. - --- - # macOS "Damaged" Error Fix - The macOS builds now include automatic fixes for Gatekeeper quarantine issues. The app should launch without "application is damaged" errors. + mv "Emerald-Legacy-Launcher.pkg" "$MACOS_BUNDLE_DIR/" - releaseDraft: false - prerelease: false - args: ${{ matrix.args }} + - name: upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.platform }} + path: | + src-tauri/target/*/release/bundle/**/* publish-flatpak: needs: get-version @@ -140,9 +114,49 @@ jobs: bundle: Emerald.Legacy.Launcher.flatpak manifest-path: flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml cache-key: flatpak-builder-${{ github.sha }} + - name: upload flatpak artifact + uses: actions/upload-artifact@v4 + with: + name: flatpak + path: Emerald.Legacy.Launcher.flatpak + + publish-release: + needs: [get-version, publish-tauri, publish-flatpak] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: "*" + merge-multiple: true + path: artifacts + - uses: softprops/action-gh-release@v2 with: tag_name: v${{ needs.get-version.outputs.version }} - files: Emerald.Legacy.Launcher.flatpak + name: "Emerald v${{ needs.get-version.outputs.version }}" + body: | + # What to download? + - `Emerald.Legacy.Launcher-*.rpm` for GNU/Linux (RHEL) + - `Emerald.Legacy.Launcher-*.deb` for GNU/Linux (Debian+Ubuntu) + - `Emerald.Legacy.Launcher-*.AppImage` for GNU/Linux (Universal, Currently Broken) + - `Emerald.Legacy.Launcher-*_x64.dmg` for macOS (Intel) + - `Emerald.Legacy.Launcher-*_aarch64.dmg` for macOS (M-series) + - `Emerald.Legacy.Launcher-*.exe` for Windows (Setup) + - `Emerald.Legacy.Launcher-*.msi` for Windows (Setup) + - `Emerald.Legacy.Launcher.flatpak` for GNU/Linux (Flatpak, Recommended) + --- + # Windows SmartScreen + This software is completely safe. Because the launcher is unsigned, you will see a "Windows protected your PC" window. To proceed: + + 1. Click the **"More info"** link under the main text. + 2. Click the **"Run anyway"** button that appears. + --- + # macOS "Damaged" Error Fix + The macOS builds now include automatic fixes for Gatekeeper quarantine issues. The app should launch without "application is damaged" errors. + files: artifacts/* + draft: true + prerelease: false env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml b/flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml index d602d6b..6e5199f 100644 --- a/flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml +++ b/flatpak/io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher.yml @@ -8,7 +8,7 @@ sdk-extensions: command: emerald-legacy-launcher finish-args: - --share=network - - --share=ipc + - --share=ipc - --device=input - --socket=x11 - --socket=wayland diff --git a/src-tauri/scripts/post-build-macos.sh b/src-tauri/scripts/post-build-macos.sh index e1c5bb5..e3a5d3d 100755 --- a/src-tauri/scripts/post-build-macos.sh +++ b/src-tauri/scripts/post-build-macos.sh @@ -5,6 +5,11 @@ set -e +if [[ "$(uname)" != "Darwin" ]]; then + echo "Not macOS, skipping post-build fixes" + exit 0 +fi + APP_NAME="Emerald Legacy Launcher" BUNDLE_ID="com.emerald.legacy" BASE_DIR="src-tauri/target" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index d7d6eaa..9fe5aac 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs; use std::io::Write; use std::net::UdpSocket; @@ -100,7 +101,7 @@ pub struct DownloadState { pub token: Arc>> } pub struct GameState { pub child: Arc>> } pub struct ProxyGuard { - pub cancel_token: Arc>>, + pub cancel_tokens: Arc>>, pub local_port: Arc>>, } @@ -1774,10 +1775,19 @@ async fn add_to_steam( title_base64: String, panorama_base64: String, ) -> Result<(), String> { - let exe_path = std::env::current_exe().map_err(|e| e.to_string())?; - let exe_str = exe_path.to_string_lossy().to_string(); - let launch_options = format!("\"{}\"", instance_id); - let start_dir = exe_path.parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default(); + let in_flatpak = std::path::Path::new("/.flatpak-info").exists(); + let (exe_str, launch_options, start_dir) = if in_flatpak { + ( + "/usr/bin/flatpak".to_string(), + format!("run io.github.Emerald_Legacy_Launcher.Emerald_Legacy_Launcher \"{}\"", instance_id), + std::env::var("HOME").unwrap_or_default(), + ) + } else { + let exe_path = std::env::current_exe().map_err(|e| e.to_string())?; + let s = exe_path.to_string_lossy().to_string(); + let dir = exe_path.parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default(); + (s, format!("\"{}\"", instance_id), dir) + }; let app_id_32 = steam_shortcuts_util::app_id_generator::calculate_app_id(&exe_str, &name); //let app_id_64 = ((app_id_32 as u64) << 32) | 0x02000000; //neo: just in case we'll need later. let mut userdata_dirs: Vec = Vec::new(); @@ -2353,15 +2363,19 @@ async fn start_direct_proxy( target_port: u16, ) -> Result { let cancel = CancellationToken::new(); + let session_id = "__direct__".to_string(); { - let mut token = proxy_state.cancel_token.lock().await; - if let Some(old) = token.take() { - old.cancel(); - } - *token = Some(cancel.clone()); + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.insert(session_id.clone(), cancel.clone()); } let local_port = run_direct_proxy(&proxy_state, &target_ip, target_port, cancel).await?; + + { + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.remove(&session_id); + } + Ok(local_port) } @@ -2377,14 +2391,17 @@ async fn start_relay_proxy( let cancel = CancellationToken::new(); { - let mut token = proxy_state.cancel_token.lock().await; - if let Some(old) = token.take() { - old.cancel(); - } - *token = Some(cancel.clone()); + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.insert(session_id.clone(), cancel.clone()); } let local_port = run_relay_proxy(&proxy_state, &ws_url, &access_token, cancel).await?; + + { + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.remove(&session_id); + } + Ok(local_port) } @@ -2401,21 +2418,36 @@ async fn start_host_relay( let cancel = CancellationToken::new(); { - let mut token = proxy_state.cancel_token.lock().await; - if let Some(old) = token.take() { - old.cancel(); - } - *token = Some(cancel.clone()); + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.insert(session_id.clone(), cancel.clone()); } - run_host_relay(&proxy_state, &ws_url, &access_token, game_port, cancel).await + let result = run_host_relay(&proxy_state, &ws_url, &access_token, game_port, cancel).await; + + { + let mut tokens = proxy_state.cancel_tokens.lock().await; + tokens.remove(&session_id); + } + + result } #[tauri::command] -async fn stop_proxy(proxy_state: State<'_, ProxyGuard>) -> Result<(), String> { - let mut token = proxy_state.cancel_token.lock().await; - if let Some(t) = token.take() { - t.cancel(); +async fn stop_proxy(proxy_state: State<'_, ProxyGuard>, session_id: String) -> Result<(), String> { + let mut tokens = proxy_state.cancel_tokens.lock().await; + if let Some(token) = tokens.remove(&session_id) { + token.cancel(); + } + let mut port = proxy_state.local_port.lock().await; + *port = None; + Ok(()) +} + +#[tauri::command] +async fn stop_all_proxies(proxy_state: State<'_, ProxyGuard>) -> Result<(), String> { + let mut tokens = proxy_state.cancel_tokens.lock().await; + for (_, token) in tokens.drain() { + token.cancel(); } let mut port = proxy_state.local_port.lock().await; *port = None; @@ -2455,11 +2487,11 @@ pub fn run() { .plugin(tauri_plugin_updater::Builder::new().build()) .manage(DownloadState { token: Arc::new(Mutex::new(None)) }) .manage(GameState { child: Arc::new(Mutex::new(None)) }) - .manage(ProxyGuard { cancel_token: Arc::new(Mutex::new(None)), local_port: Arc::new(Mutex::new(None)) }) + .manage(ProxyGuard { cancel_tokens: Arc::new(Mutex::new(HashMap::new())), local_port: Arc::new(Mutex::new(None)) }) .plugin(tauri_plugin_gamepad::init()) .plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_drpc::init()) - .invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, pick_folder, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, open_screenshot_folder, save_global_skin_pck, check_game_update, check_macos_runtime_installed, check_macos_runtime_installed_fast, download_logo, pick_file, save_file_dialog, write_binary_file, read_binary_file, read_screenshot_as_data_url, add_to_steam, http_proxy_request, get_instance_path, stun_discover, start_direct_proxy, start_relay_proxy, start_host_relay, stop_proxy, join_game]) + .invoke_handler(tauri::generate_handler![setup_macos_runtime, launch_game, stop_game, check_game_installed, save_config, load_config, download_and_install, open_instance_folder, cancel_download, get_available_runners, get_external_palettes, import_theme, pick_folder, download_runner, delete_instance, sync_dlc, fetch_skin, workshop_install, workshop_uninstall, workshop_list_installed, get_screenshots, delete_screenshot, open_screenshot_folder, save_global_skin_pck, check_game_update, check_macos_runtime_installed, check_macos_runtime_installed_fast, download_logo, pick_file, save_file_dialog, write_binary_file, read_binary_file, read_screenshot_as_data_url, add_to_steam, http_proxy_request, get_instance_path, stun_discover, start_direct_proxy, start_relay_proxy, start_host_relay, stop_proxy, stop_all_proxies, join_game]) .setup(|app| { let args: Vec = std::env::args().collect(); if args.len() > 1 && !args[1].starts_with('-') { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f9d702e..364cd54 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -58,8 +58,8 @@ "entitlements": null, "exceptionDomain": "", "frameworks": [], - "signingIdentity": null, - "providerShortName": "Emerald" + "minimumSystemVersion": "10.15", + "signingIdentity": null } } } diff --git a/src/components/modals/ChooseInstanceModal.tsx b/src/components/modals/ChooseInstanceModal.tsx index cd53994..722aef5 100644 --- a/src/components/modals/ChooseInstanceModal.tsx +++ b/src/components/modals/ChooseInstanceModal.tsx @@ -69,7 +69,7 @@ export default function ChooseInstanceModal({ ]); } else { setStatus("Launching game..."); - await TauriService.stopProxy(); + await TauriService.stopAllProxies(); await TauriService.launchGame(selectedInstance, [ { name: invite.hostName || "LCELive Game", ip: hostIp, port: hostPort } ]); diff --git a/src/components/views/LceLiveView.tsx b/src/components/views/LceLiveView.tsx index 602b3a3..12f6340 100644 --- a/src/components/views/LceLiveView.tsx +++ b/src/components/views/LceLiveView.tsx @@ -40,8 +40,6 @@ const LceLiveView = memo(function LceLiveView() { const [isDiscovering, setIsDiscovering] = useState(false); const [invitedFriends, setInvitedFriends] = useState>(new Set()); const [showHostMethodPicker, setShowHostMethodPicker] = useState(false); - const [hostSessionId, setHostSessionId] = useState(""); - const [relayStarted, setRelayStarted] = useState(false); const [isAddingFriend, setIsAddingFriend] = useState(false); const [addFriendUsername, setAddFriendUsername] = useState(""); const addFriendInputRef = useRef(null); @@ -160,7 +158,6 @@ const LceLiveView = memo(function LceLiveView() { const handleStartHosting = () => { playPressSound(); - setRelayStarted(false); setShowHostMethodPicker(true); setFocusIndex(0); }; @@ -177,7 +174,6 @@ const LceLiveView = memo(function LceLiveView() { setIsHosting(true); setHostStatus(`Hosting at ${endpoint.ip}:25565`); setInvitedFriends(new Set()); - setHostSessionId(""); } catch (e: any) { const msg = typeof e === "string" ? e : e?.message || "Unknown error"; setErrorModal("STUN discovery failed: " + msg); @@ -192,8 +188,6 @@ const LceLiveView = memo(function LceLiveView() { setFocusIndex(0); setIsDiscovering(true); setHostStatus("Discovering external IP for invite..."); - const sessionId = crypto.randomUUID(); - setHostSessionId(sessionId); try { const endpoint = await TauriService.stunDiscover(); setHostIp(endpoint.ip); @@ -202,7 +196,6 @@ const LceLiveView = memo(function LceLiveView() { setHostIp("127.0.0.1"); setHostPort(25565); } - setRelayStarted(false); setIsHosting(true); setHostStatus("Relay ready - invite friends to activate"); setInvitedFriends(new Set()); @@ -212,7 +205,7 @@ const LceLiveView = memo(function LceLiveView() { const handleStopHosting = async () => { playPressSound(); try { - await TauriService.stopProxy(); + await TauriService.stopAllProxies(); await lceLiveService.deactivateGameInvites(); } catch (e: any) { console.warn("Stop hosting failed", e); @@ -221,38 +214,37 @@ const LceLiveView = memo(function LceLiveView() { setHostStatus(""); setHostIp(""); setInvitedFriends(new Set()); - setHostSessionId(""); - setRelayStarted(false); }; const handleInviteFriend = async (friend: LceLiveAccount) => { playPressSound(); const name = lceLiveService.displayUsername; + const sessionId = crypto.randomUUID(); try { await lceLiveService.sendGameInvite( friend.accountId, hostIp, hostPort, name, - hostSessionId || undefined, + sessionId, ); setInvitedFriends((prev) => new Set(prev).add(friend.accountId)); - if (hostSessionId && !relayStarted) { - setRelayStarted(true); - setHostStatus("Connecting relay..."); - const baseUrl = lceLiveService.apiBaseUrl; - const accessToken = lceLiveService.accessToken ?? ""; - TauriService.startHostRelay(baseUrl, accessToken, hostSessionId, 25565) - .then(() => setHostStatus("Relay active")) - .catch((relayErr: any) => { - const relayMsg = - typeof relayErr === "string" - ? relayErr - : relayErr?.message || "Unknown error"; - console.warn("Relay failed:", relayMsg); - setHostStatus("Relay disconnected"); - }); - } + setHostStatus("Connecting relay..."); + TauriService.startHostRelay( + lceLiveService.apiBaseUrl, + lceLiveService.accessToken ?? "", + sessionId, + 25565, + ) + .then(() => setHostStatus("Relay active")) + .catch((relayErr: any) => { + const relayMsg = + typeof relayErr === "string" + ? relayErr + : relayErr?.message || "Unknown error"; + console.warn("Relay failed:", relayMsg); + setHostStatus("Relay disconnected"); + }); } catch (e: any) { const msg = typeof e === "string" ? e : e?.message || "Unknown error"; setErrorModal("Failed to send invite: " + msg); diff --git a/src/pages/App.tsx b/src/pages/App.tsx index fab368f..82f6d72 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -132,7 +132,7 @@ export default function App() { {...backgroundFade} > diff --git a/src/services/TauriService.ts b/src/services/TauriService.ts index 23cd592..e7327ef 100644 --- a/src/services/TauriService.ts +++ b/src/services/TauriService.ts @@ -253,8 +253,12 @@ export class TauriService { return invoke("start_host_relay", { apiBaseUrl, accessToken, sessionId, gamePort }); } - static async stopProxy(): Promise { - return invoke("stop_proxy"); + static async stopProxy(sessionId: string): Promise { + return invoke("stop_proxy", { sessionId }); + } + + static async stopAllProxies(): Promise { + return invoke("stop_all_proxies"); } static async joinGame(