make discord rpc more stable

This commit is contained in:
Wyvest 2026-02-25 06:36:31 -05:00
parent d6aba6ab45
commit 6190411635
6 changed files with 75 additions and 24 deletions

View file

@ -313,13 +313,8 @@ export type VersionType =
*/
"old_beta"
const ARGS_MAP = { 'debug':'{"getBuildTimestamp":[],"getPackageVersion":[],"getPlatform":[],"isInDev":[],"getArch":[],"getFamily":[],"openDevTools":[],"getGitCommitHash":[],"getType":[],"getLocale":[],"getOsVersion":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'events':'{"process":["event"],"ingress":["event"],"message":["event"]}', 'oneclient':'{"getVersions":[],"getBundlesFor":["cluster_id"],"extractBundleOverrides":["bundle_path","cluster_id"],"installUpdate":[],"updateBundlePackages":["cluster_id"],"isBundleSyncing":[],"refreshArt":["path"],"cacheArt":["path"],"checkForUpdate":[],"getClustersGroupedByMajor":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"]}', 'core':'{"createSettingsProfile":["name"],"getPackageBody":["provider","body"],"getUsers":[],"launchCluster":["id","uuid","search_for_java"],"getUsersFromAuthor":["provider","author"],"syncCluster":["cluster_id"],"getLinkedPackages":["cluster_id"],"changeSkin":["access_token","skin_url","skin_variant"],"getMultiplePackages":["provider","slugs"],"removeCluster":["id"],"togglePackage":["cluster_id","package_hash"],"installModpack":["modpack","cluster_id"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"fetchMinecraftProfile":["uuid"],"getScreenshots":["id"],"updateClusterProfile":["name","profile"],"getRunningProcessesByClusterId":["cluster_id"],"getProfileOrDefault":["name"],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getProcessLogTail":["id","max_lines"],"getClusterById":["id"],"isClusterRunning":["cluster_id"],"open":["input"],"killProcess":["pid"],"fetchLoggedInProfile":["access_token"],"openMsaLogin":[],"writeSettings":["setting"],"changeCape":["access_token","cape_uuid"],"createCluster":["options"],"setClusterStage":["id","stage"],"searchPackages":["provider","query"],"setDiscordRPCMessage":["message"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"removePackage":["cluster_id","package_hash"],"getClusters":[],"getWorlds":["id"],"getDefaultUser":["fallback"],"getPackage":["provider","slug"],"setDefaultUser":["uuid"],"getRunningProcesses":[],"getGlobalProfile":[],"updateClusterById":["id","request"],"refreshAccounts":[],"getGameVersions":[],"getUser":["uuid"],"convertUsernameUUID":["username_uuid"],"refreshAccount":["uuid"],"removeCape":["access_token"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"removeUser":["uuid"],"getLogs":["id"],"getLogByName":["id","name"],"getLoadersForVersion":["mc_version"],"readSettings":[]}' }
export type Router = { 'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> },
'debug': { openDevTools: () => Promise<void>,
const ARGS_MAP = { 'oneclient':'{"checkForUpdate":[],"installUpdate":[],"downloadPackageFromBundle":["package","cluster_id","bundle_name","skip_compatibility"],"getClustersGroupedByMajor":[],"getBundlesFor":["cluster_id"],"getVersions":[],"isBundleSyncing":[],"cacheArt":["path"],"updateBundlePackages":["cluster_id"],"refreshArt":["path"],"extractBundleOverrides":["bundle_path","cluster_id"]}', 'events':'{"message":["event"],"ingress":["event"],"process":["event"]}', 'debug':'{"getBuildTimestamp":[],"getLocale":[],"openDevTools":[],"isInDev":[],"getFamily":[],"getArch":[],"getOsVersion":[],"getPlatform":[],"getGitCommitHash":[],"getPackageVersion":[],"getType":[]}', 'folders':'{"fromCluster":["folder_name"],"openCluster":["folder_name"]}', 'core':'{"getDefaultUser":["fallback"],"getUser":["uuid"],"removeCluster":["id"],"launchCluster":["id","uuid","search_for_java"],"searchPackages":["provider","query"],"getPackageBody":["provider","body"],"getRunningProcessesByClusterId":["cluster_id"],"setDefaultUser":["uuid"],"getProfileOrDefault":["name"],"refreshAccount":["uuid"],"readSettings":[],"getPackageVersions":["provider","slug","mc_version","loader","offset","limit"],"getUsersFromAuthor":["provider","author"],"removePackage":["cluster_id","package_hash"],"installModpack":["modpack","cluster_id"],"getLoadersForVersion":["mc_version"],"changeSkin":["access_token","skin_url","skin_variant"],"open":["input"],"downloadExternalPackage":["package","cluster_id","force","skip_compatibility"],"getClusterById":["id"],"uploadSkinBytes":["access_token","skin_data","image_format","skin_variant"],"getUsers":[],"fetchMinecraftProfile":["uuid"],"setClusterStage":["id","stage"],"changeCape":["access_token","cape_uuid"],"getMultiplePackages":["provider","slugs"],"isClusterRunning":["cluster_id"],"openMsaLogin":[],"getClusters":[],"getLogByName":["id","name"],"killProcess":["pid"],"getScreenshots":["id"],"getLinkedPackages":["cluster_id"],"updateClusterProfile":["name","profile"],"getPackage":["provider","slug"],"fetchLoggedInProfile":["access_token"],"removeUser":["uuid"],"getProcessLogTail":["id","max_lines"],"downloadPackage":["provider","package_id","version_id","cluster_id","skip_compatibility"],"createCluster":["options"],"createSettingsProfile":["name"],"getRunningProcesses":[],"updateClusterById":["id","request"],"setDiscordRPCMessage":["message"],"togglePackage":["cluster_id","package_hash"],"refreshAccounts":[],"getWorlds":["id"],"writeSettings":["setting"],"getLogs":["id"],"removeCape":["access_token"],"convertUsernameUUID":["username_uuid"],"getGameVersions":[],"syncCluster":["cluster_id"],"getGlobalProfile":[]}' }
export type Router = { 'debug': { openDevTools: () => Promise<void>,
isInDev: () => Promise<boolean>,
getArch: () => Promise<string>,
getFamily: () => Promise<string>,
@ -330,6 +325,11 @@ getOsVersion: () => Promise<string>,
getGitCommitHash: () => Promise<string>,
getBuildTimestamp: () => Promise<string>,
getPackageVersion: () => Promise<string> },
'events': { ingress: (event: IngressPayload) => Promise<void>,
message: (event: MessagePayload) => Promise<void>,
process: (event: ProcessPayload) => Promise<void> },
'folders': { fromCluster: (folderName: string) => Promise<string>,
openCluster: (folderName: string) => Promise<null> },
'oneclient': { getClustersGroupedByMajor: () => Promise<Partial<{ [key in number]: ClusterModel[] }>>,
getBundlesFor: (clusterId: number) => Promise<ModpackArchive[]>,
getVersions: () => Promise<OnlineClusterManifest>,

View file

@ -35,7 +35,7 @@ const ResolvedPathNames: Record<URLPath, string> = {
'/app': 'Viewing Homepage',
'/app/account': 'Viewing Account',
'/app/account/skins': 'Viewing Skin Manager',
'/app/cluster': 'Viewing Clusters',
'/app/cluster': 'Viewing Versions',
'/app/cluster/browser': 'Viewing {clusterName}\'s mods',
'/app/cluster/browser/package': 'Browsing {packageName}',
'/app/cluster/logs': 'Viewing {clusterName}\'s logs',
@ -51,7 +51,7 @@ const ResolvedPathNames: Record<URLPath, string> = {
'/app/settings/minecraft': 'Viewing Settings',
'/app/settings/changelog': 'Viewing Settings',
'/app/accounts': 'Viewing Accounts',
'/app/clusters': 'Viewing Clusters',
'/app/clusters': 'Viewing Versions',
'/onboarding': 'Preparing OneClient',
'/onboarding/account': 'Preparing OneClient',
'/onboarding/finished': 'Preparing OneClient',

View file

@ -196,13 +196,16 @@ pub async fn launch_minecraft(
let censors = log::create_censors(&creds);
if let Some(discord) = &state.rpc {
discord
.set_message(
&format!("Playing {}", cluster.name),
Some(Timestamps::new().start(Utc::now().timestamp())),
)
.await;
if state.ensure_rpc().await {
let rpc = state.rpc.read().await;
if let Some(discord) = rpc.as_ref() {
discord
.set_message(
&format!("Playing {}", cluster.name),
Some(Timestamps::new().start(Utc::now().timestamp())),
)
.await;
}
}
state

View file

@ -945,12 +945,15 @@ impl TauriLauncherApi for TauriLauncherApiImpl {
// region: discord RPC
async fn set_discord_rpc_message(self, message: String) -> LauncherResult<()> {
let state = State::get().await?;
if let Some(discord) = &state.rpc {
if !state.settings.read().await.discord_enabled {
discord.clear_activity().await;
return Ok(());
if state.ensure_rpc().await {
let rpc = state.rpc.read().await;
if let Some(discord) = rpc.as_ref() {
if !state.settings.read().await.discord_enabled {
discord.clear_activity().await;
return Ok(());
}
discord.set_message(&message, None).await;
}
discord.set_message(&message, None).await;
}
Ok(())

View file

@ -27,6 +27,12 @@ impl DiscordRPC {
let connected = client.connect().is_ok();
if connected {
tracing::info!("Discord RPC connected successfully");
} else {
tracing::warn!("Discord RPC failed to connect (Discord may not be running)");
}
Ok(Self {
started_at: chrono::Utc::now().timestamp(),
client: Arc::new(RwLock::new(client)),
@ -50,13 +56,17 @@ impl DiscordRPC {
}
pub async fn set_activity(&self, activity: Activity<'_>) -> bool {
if !self.is_connected() {
// If we know we're disconnected, try to reconnect first
if !self.is_connected() && !self.reconnect().await {
return false;
}
let mut client = self.client.write().await;
if client.set_activity(activity).is_err() {
// Connection dropped — mark as disconnected so next call will reconnect
self.set_connected(false);
tracing::warn!("Discord RPC set_activity failed; connection may have dropped");
return false;
}
@ -71,6 +81,7 @@ impl DiscordRPC {
let mut client = self.client.write().await;
if client.clear_activity().is_err() {
self.set_connected(false);
return false;
}
@ -78,11 +89,19 @@ impl DiscordRPC {
}
pub async fn reconnect(&self) -> bool {
tracing::info!("Attempting Discord RPC reconnect...");
let mut client = self.client.write().await;
let connected = client.reconnect().is_ok();
self.set_connected(connected);
if connected {
tracing::info!("Discord RPC reconnected successfully");
} else {
tracing::warn!("Discord RPC reconnect failed");
}
connected
}

View file

@ -23,7 +23,9 @@ pub struct State {
pub credentials: RwLock<CredentialsStore>,
pub metadata: RwLock<Metadata>,
pub processes: ProcessStore,
pub rpc: Option<DiscordRPC>,
/// Discord RPC client. Wrapped in `RwLock` so it can be lazily initialized
/// after startup if Discord wasn't running when the launcher launched.
pub rpc: RwLock<Option<DiscordRPC>>,
}
impl State {
@ -64,7 +66,31 @@ impl State {
credentials: RwLock::new(credentials),
metadata: RwLock::new(metadata),
processes,
rpc,
rpc: RwLock::new(rpc),
}))
}
/// Get or lazily initialize the Discord RPC.
///
/// If RPC was `None` at startup (Discord wasn't running), this will attempt
/// to initialize it now. Returns `true` if RPC is available after the call.
pub async fn ensure_rpc(&self) -> bool {
// Fast path: already have a client
if self.rpc.read().await.is_some() {
return true;
}
// Try to initialize for the first time
match DiscordRPC::initialize() {
Ok(rpc) => {
*self.rpc.write().await = Some(rpc);
tracing::info!("Discord RPC lazily initialized");
true
}
Err(err) => {
tracing::debug!("Discord RPC lazy init failed: {err}");
false
}
}
}
}