merge experimental into main (#42)

This commit is contained in:
/home/neo 2026-05-16 20:23:15 +03:00 committed by GitHub
parent e29fd4da6f
commit 46e04a7c4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 217 additions and 147 deletions

View file

@ -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

View file

@ -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

View file

@ -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 }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -8,7 +8,7 @@ sdk-extensions:
command: emerald-legacy-launcher
finish-args:
- --share=network
- --share=ipc
- --share=ipc
- --device=input
- --socket=x11
- --socket=wayland

View file

@ -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"

View file

@ -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<Mutex<Option<CancellationToken>>> }
pub struct GameState { pub child: Arc<Mutex<Option<tokio::process::Child>>> }
pub struct ProxyGuard {
pub cancel_token: Arc<Mutex<Option<CancellationToken>>>,
pub cancel_tokens: Arc<Mutex<HashMap<String, CancellationToken>>>,
pub local_port: Arc<Mutex<Option<u16>>>,
}
@ -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<PathBuf> = Vec::new();
@ -2353,15 +2363,19 @@ async fn start_direct_proxy(
target_port: u16,
) -> Result<u16, String> {
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<String> = std::env::args().collect();
if args.len() > 1 && !args[1].starts_with('-') {

View file

@ -58,8 +58,8 @@
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"signingIdentity": null,
"providerShortName": "Emerald"
"minimumSystemVersion": "10.15",
"signingIdentity": null
}
}
}

View file

@ -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 }
]);

View file

@ -40,8 +40,6 @@ const LceLiveView = memo(function LceLiveView() {
const [isDiscovering, setIsDiscovering] = useState(false);
const [invitedFriends, setInvitedFriends] = useState<Set<string>>(new Set());
const [showHostMethodPicker, setShowHostMethodPicker] = useState(false);
const [hostSessionId, setHostSessionId] = useState<string>("");
const [relayStarted, setRelayStarted] = useState(false);
const [isAddingFriend, setIsAddingFriend] = useState(false);
const [addFriendUsername, setAddFriendUsername] = useState("");
const addFriendInputRef = useRef<HTMLInputElement>(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);

View file

@ -132,7 +132,7 @@ export default function App() {
{...backgroundFade}
>
<PanoramaBackground
profile={selectedEdition.panorama}
profile={selectedEdition?.panorama}
isDay={displayIsDay}
/>
</motion.div>

View file

@ -253,8 +253,12 @@ export class TauriService {
return invoke("start_host_relay", { apiBaseUrl, accessToken, sessionId, gamePort });
}
static async stopProxy(): Promise<void> {
return invoke("stop_proxy");
static async stopProxy(sessionId: string): Promise<void> {
return invoke("stop_proxy", { sessionId });
}
static async stopAllProxies(): Promise<void> {
return invoke("stop_all_proxies");
}
static async joinGame(