From dfb2a3eea7406ac8801de24481b20cb4fd8df406 Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Mon, 10 Jul 2023 12:40:02 +0100 Subject: [PATCH] Show presence update errors in the main window --- src/app/browser/main/discord.tsx | 51 ++++++++++++++++++++++++++++-- src/app/common/types.ts | 4 +++ src/app/main/ipc.ts | 5 ++- src/app/main/monitor.ts | 54 +++++++++++++++++++++++++++++++- src/app/preload/index.ts | 5 ++- src/common/presence.ts | 24 ++++++++++---- 6 files changed, 132 insertions(+), 11 deletions(-) diff --git a/src/app/browser/main/discord.tsx b/src/app/browser/main/discord.tsx index 513a2bf..d2c2717 100644 --- a/src/app/browser/main/discord.tsx +++ b/src/app/browser/main/discord.tsx @@ -1,23 +1,39 @@ -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Image, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { User } from 'discord-rpc'; import ipc, { events } from '../ipc.js'; import { RequestState, useAsync, useEventListener } from '../util.js'; -import { DiscordPresenceSource, DiscordPresenceSourceUrl, DiscordPresenceSourceCoral } from '../../common/types.js'; +import { DiscordPresenceSource, DiscordPresenceSourceUrl, DiscordPresenceSourceCoral, DiscordStatus } from '../../common/types.js'; import { DiscordPresence } from '../../../discord/types.js'; import { DISCORD_COLOUR, TEXT_COLOUR_DARK } from '../constants.js'; +import Warning from '../components/icons/warning.js'; export default function DiscordPresenceSource(props: { source: DiscordPresenceSource | null; presence: DiscordPresence | null; user: User | null; }) { + const [status, setStatus] = useState(null); + + useEffect(() => { + ipc.getDiscordStatus().then(setStatus); + }, [ipc]); + + useEventListener(events, 'update-discord-status', setStatus, []); + + const showErrorDetails = useCallback(() => { + ipc.showDiscordLastUpdateError(); + }, [ipc]); + if (!props.source) return null; return ipc.showDiscordModal()}> {renderDiscordPresenceSource(props.source)} {props.presence || props.user ? : null} + + {status?.error_message ? + : null} ; } @@ -111,6 +127,18 @@ function DiscordPresence(props: { ; } +function DiscordPresenceError(props: { + message: string; + onPress?: () => void; +}) { + return + + + {props.message} + + ; +} + const styles = StyleSheet.create({ discord: { backgroundColor: DISCORD_COLOUR, @@ -168,4 +196,23 @@ const styles = StyleSheet.create({ discordUserDiscriminator: { opacity: 0.7, }, + + errorTouchable: { + marginVertical: -16, + marginHorizontal: -20, + marginTop: 6, + paddingVertical: 16, + paddingHorizontal: 20, + paddingTop: 10, + }, + error: { + flexDirection: 'row', + }, + icon: { + marginRight: 10, + color: TEXT_COLOUR_DARK, + }, + errorText: { + color: TEXT_COLOUR_DARK, + }, }); diff --git a/src/app/common/types.ts b/src/app/common/types.ts index 8902bac..e5fe0e6 100644 --- a/src/app/common/types.ts +++ b/src/app/common/types.ts @@ -48,6 +48,10 @@ export interface DiscordPresenceExternalMonitorsConfiguration { enable_splatnet3_monitoring?: boolean; } +export interface DiscordStatus { + error_message: string | null; +} + export interface LoginItem { supported: boolean; startup_enabled: boolean; diff --git a/src/app/main/ipc.ts b/src/app/main/ipc.ts index 6441dd4..595ae11 100644 --- a/src/app/main/ipc.ts +++ b/src/app/main/ipc.ts @@ -5,7 +5,7 @@ import { createModalWindow, getWindowConfiguration, setWindowHeight } from './wi import { askAddNsoAccount, askAddPctlAccount } from './na-auth.js'; import { App } from './index.js'; import { EmbeddedPresenceMonitor } from './monitor.js'; -import { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItemOptions, WindowType } from '../common/types.js'; +import { DiscordPresenceConfiguration, DiscordPresenceSource, DiscordStatus, LoginItemOptions, WindowType } from '../common/types.js'; import { CurrentUser, Friend, Game, PresenceState, WebService } from '../../api/coral-types.js'; import { NintendoAccountUser } from '../../api/na.js'; import createDebug from '../../util/debug.js'; @@ -109,6 +109,8 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { handle('discord:source', () => appinstance.monitors.getDiscordPresenceSource()); handle('discord:setsource', (e, source: DiscordPresenceSource | null) => appinstance.monitors.setDiscordPresenceSource(source)); handle('discord:presence', () => appinstance.monitors.getDiscordPresence()); + handle('discord:status', () => appinstance.monitors.getDiscordStatus()); + handle('discord:showerror', () => appinstance.monitors.showDiscordPresenceLastUpdateError()); handle('discord:user', () => appinstance.monitors.getActiveDiscordPresenceMonitor()?.discord.rpc?.client.user ?? null); handle('discord:users', async () => { const users: User[] = []; @@ -173,6 +175,7 @@ export function setupIpc(appinstance: App, ipcMain: IpcMain) { store.on('update-discord-presence-source', () => sendToAllWindows('nxapi:discord:shouldrefresh')); store.on('update-discord-presence', (p: DiscordPresence) => sendToAllWindows('nxapi:discord:presence', p)); store.on('update-discord-user', (u: User) => sendToAllWindows('nxapi:discord:user', u)); + store.on('update-discord-status', (s: DiscordStatus | null) => sendToAllWindows('nxapi:discord:status', s)); } function sendToAllWindows(channel: string, ...args: any[]) { diff --git a/src/app/main/monitor.ts b/src/app/main/monitor.ts index 11f3ee3..5eb6218 100644 --- a/src/app/main/monitor.ts +++ b/src/app/main/monitor.ts @@ -1,7 +1,7 @@ import { Notification } from './electron.js'; import { App } from './index.js'; import { showErrorDialog, tryGetNativeImageFromUrl } from './util.js'; -import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource } from '../common/types.js'; +import { DiscordPresenceConfiguration, DiscordPresenceExternalMonitorsConfiguration, DiscordPresenceSource, DiscordStatus } from '../common/types.js'; import { CurrentUser, Friend, Game, CoralError } from '../../api/coral-types.js'; import { ErrorResponse } from '../../api/util.js'; import { ZncDiscordPresence, ZncProxyDiscordPresence } from '../../common/presence.js'; @@ -11,6 +11,7 @@ import { LoopResult } from '../../util/loop.js'; import { DiscordPresence, DiscordPresencePlayTime, ErrorResult } from '../../discord/types.js'; import { DiscordRpcClient } from '../../discord/rpc.js'; import SplatNet3Monitor, { getConfigFromAppConfig as getSplatNet3MonitorConfigFromAppConfig } from '../../discord/monitor/splatoon3.js'; +import { ErrorDescription } from '../../util/errors.js'; const debug = createDebug('app:main:monitor'); @@ -68,6 +69,19 @@ export class PresenceMonitorManager { return ErrorResult.IGNORE; }; + i.discord.onUpdateError = err => { + const status: DiscordStatus = { + error_message: err instanceof Error ? + err.name + ': ' + err.message : + ErrorDescription.getErrorDescription(err), + }; + this.app.store.emit('update-discord-status', status); + }; + i.discord.onUpdateSuccess = () => { + const status: DiscordStatus = {error_message: null}; + this.app.store.emit('update-discord-status', status); + }; + i.onError = err => this.handleError(i, err); this.monitors.push(i); @@ -97,6 +111,19 @@ export class PresenceMonitorManager { this.app.store.emit('update-discord-user', client?.user ?? null); }; + i.discord.onUpdateError = err => { + const status: DiscordStatus = { + error_message: err instanceof Error ? + err.name + ': ' + err.message : + ErrorDescription.getErrorDescription(err), + }; + this.app.store.emit('update-discord-status', status); + }; + i.discord.onUpdateSuccess = () => { + const status: DiscordStatus = {error_message: null}; + this.app.store.emit('update-discord-status', status); + }; + i.onError = err => this.handleError(i, err); this.monitors.push(i); @@ -317,6 +344,8 @@ export class PresenceMonitorManager { this.app.store.saveMonitorState(this); this.app.menu?.updateMenu(); this.app.store.emit('update-discord-presence-source', source); + } else { + this.app.store.emit('update-discord-status', null); } } @@ -360,6 +389,29 @@ export class PresenceMonitorManager { return LoopResult.OK; } + + async getDiscordStatus(): Promise { + const monitor = this.getActiveDiscordPresenceMonitor(); + if (!monitor) return null; + + return { + error_message: monitor.discord.last_update_error ? + monitor.discord.last_update_error instanceof Error ? + monitor.discord.last_update_error.name + ': ' + monitor.discord.last_update_error.message : + ErrorDescription.getErrorDescription(monitor.discord.last_update_error) : null, + }; + } + + async showDiscordPresenceLastUpdateError() { + const monitor = this.getActiveDiscordPresenceMonitor(); + const error = monitor?.discord.last_update_error; + if (!error) return; + + await showErrorDialog({ + message: error.name + ' updating presence monitor', + error, + }); + } } export class EmbeddedPresenceMonitor extends ZncDiscordPresence { diff --git a/src/app/preload/index.ts b/src/app/preload/index.ts index 5472673..50f3960 100644 --- a/src/app/preload/index.ts +++ b/src/app/preload/index.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'events'; import createDebug from 'debug'; import type { User } from 'discord-rpc'; import type { SharingItem } from '../main/electron.js'; -import type { DiscordPresenceConfiguration, DiscordPresenceSource, LoginItem, LoginItemOptions, WindowConfiguration } from '../common/types.js'; +import type { DiscordPresenceConfiguration, DiscordPresenceSource, DiscordStatus, LoginItem, LoginItemOptions, WindowConfiguration } from '../common/types.js'; import type { SavedToken } from '../../common/auth/coral.js'; import type { SavedMoonToken } from '../../common/auth/moon.js'; import type { UpdateCacheData } from '../../common/update.js'; @@ -74,6 +74,8 @@ const ipc = { getDiscordPresenceSource: () => inv('discord:source'), setDiscordPresenceSource: (source: DiscordPresenceSource | null) => inv('discord:setsource', source), getDiscordPresence: () => inv('discord:presence'), + getDiscordStatus: () => inv('discord:status'), + showDiscordLastUpdateError: () => inv('discord:showerror'), getDiscordUser: () => inv('discord:user'), getDiscordUsers: () => inv('discord:users'), @@ -109,6 +111,7 @@ ipcRenderer.on('nxapi:accounts:shouldrefresh', () => events.emit('update-nintend ipcRenderer.on('nxapi:discord:shouldrefresh', () => events.emit('update-discord-presence-source')); ipcRenderer.on('nxapi:discord:presence', (e, p: DiscordPresence) => events.emit('update-discord-presence', p)); ipcRenderer.on('nxapi:discord:user', (e, u: User) => events.emit('update-discord-user', u)); +ipcRenderer.on('nxapi:discord:status', (e, s: DiscordStatus | null) => events.emit('update-discord-status', s)); let accent_colour: string | undefined = invSync('systemPreferences:accent-colour'); ipcRenderer.on('nxapi:systemPreferences:accent-colour', (event, c) => { diff --git a/src/common/presence.ts b/src/common/presence.ts index 1b847ce..d57fe62 100644 --- a/src/common/presence.ts +++ b/src/common/presence.ts @@ -47,6 +47,11 @@ class ZncDiscordPresenceClient { ErrorResult | Promise) | null = null; update_presence_errors = 0; + last_update_error: Error | null = null; + last_update_error_at: Date | null = null; + onUpdateError: ((error: Error | null) => void) | null = null; + onUpdateSuccess: (() => void) | null = null; + onUpdate: (() => void) | null = null; constructor( readonly m: ZncDiscordPresence | ZncProxyDiscordPresence, @@ -63,6 +68,13 @@ class ZncDiscordPresenceClient { this.last_friendcode = friendcode; this.last_event = activeevent; + this.onUpdate?.call(null); + + if (this.update_presence_errors) { + this.update_presence_errors = 0; + this.onUpdateSuccess?.call(null); + } + const online = presence?.state === PresenceState.ONLINE || presence?.state === PresenceState.PLAYING; const show_presence = @@ -328,15 +340,15 @@ class ZncDiscordPresenceClient { async onError(err: Error) { this.update_presence_errors++; + this.last_update_error = err; + this.last_update_error_at = new Date(); - if (this.update_presence_errors > 2) { + this.onUpdateError?.call(null, err); + + if (this.update_presence_errors > 2 && this.rpc) { // Disconnect from Discord if the last two attempts to update presence failed // This prevents the user's activity on Discord being stuck - if (this.rpc) { - const client = this.rpc.client; - this.rpc = null; - await client.destroy(); - } + this.setActivity(this.m.discord_preconnect ? this.rpc.id : null); } if (this.update_presence_errors > 10) {