From cac7bf6f17e90427b947a95444835ef36f4d6cbc Mon Sep 17 00:00:00 2001 From: Natsumi Date: Thu, 2 Oct 2025 14:13:16 +1300 Subject: [PATCH] VRC status checking --- Dotnet/Cef/MainForm.cs | 5 +++ src-electron/main.js | 4 +++ src-electron/preload.js | 1 + src/App.vue | 3 ++ src/components/VrcStatus.vue | 20 ++++++++++++ src/stores/index.js | 7 +++-- src/stores/vrcStatus.js | 59 ++++++++++++++++++++++++++++++++++++ src/stores/vrcx.js | 6 ++++ src/types/globals.d.ts | 1 + src/views/Login/Login.vue | 34 +++++++++------------ 10 files changed, 118 insertions(+), 22 deletions(-) create mode 100644 src/components/VrcStatus.vue create mode 100644 src/stores/vrcStatus.js diff --git a/Dotnet/Cef/MainForm.cs b/Dotnet/Cef/MainForm.cs index 3b521eb3..d1ed4e49 100644 --- a/Dotnet/Cef/MainForm.cs +++ b/Dotnet/Cef/MainForm.cs @@ -83,6 +83,11 @@ namespace VRCX { logger.Debug(consoleMessageEventArgs.Message + " (" + consoleMessageEventArgs.Source + ":" + consoleMessageEventArgs.Line + ")"); }; + Browser.GotFocus += (_, _) => + { + if (Browser != null && !Browser.IsLoading && Browser.CanExecuteJavascriptInMainFrame) + Browser.ExecuteScriptAsync("$pinia.vrcStatus.onBrowserFocus()"); + }; JavascriptBindings.ApplyAppJavascriptBindings(Browser.JavascriptObjectRepository); Controls.Add(Browser); diff --git a/src-electron/main.js b/src-electron/main.js index e2d9b46c..10081ed2 100644 --- a/src-electron/main.js +++ b/src-electron/main.js @@ -396,6 +396,10 @@ function createWindow() { mainWindow.on('restore', () => { mainWindow.webContents.send('setWindowState', '0'); }); + + mainWindow.on('focus', () => { + mainWindow.webContents.send('onBrowserFocus'); + }); } let wristOverlayWindow = undefined; diff --git a/src-electron/preload.js b/src-electron/preload.js index 8c3ee126..6ac5e35f 100644 --- a/src-electron/preload.js +++ b/src-electron/preload.js @@ -31,6 +31,7 @@ contextBridge.exposeInMainWorld('electron', { ipcRenderer.on('setWindowSize', callback), onWindowStateChange: (callback) => ipcRenderer.on('setWindowState', callback), + onBrowserFocus: (callback) => ipcRenderer.on('onBrowserFocus', callback), desktopNotification: (title, body, icon) => ipcRenderer.invoke('notification:showNotification', title, body, icon), restartApp: () => ipcRenderer.invoke('app:restart'), diff --git a/src/App.vue b/src/App.vue index 736ec1fe..bf87655a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,7 @@ + diff --git a/src/stores/index.js b/src/stores/index.js index d1b30d16..ea819dcc 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -31,6 +31,7 @@ import { useVrStore } from './vr'; import { useVrcxStore } from './vrcx'; import { useVRCXUpdaterStore } from './vrcxUpdater'; import { useWorldStore } from './world'; +import { useVrcStatusStore } from './vrcStatus'; import { createSentryPiniaPlugin } from '@sentry/vue'; @@ -70,7 +71,8 @@ export function createGlobalStores() { vrcx: useVrcxStore(), sharedFeed: useSharedFeedStore(), updateLoop: useUpdateLoopStore(), - auth: useAuthStore() + auth: useAuthStore(), + vrcStatus: useVrcStatusStore() }; } @@ -106,5 +108,6 @@ export { useVRCXUpdaterStore, useWorldStore, useSharedFeedStore, - useUpdateLoopStore + useUpdateLoopStore, + useVrcStatusStore }; diff --git a/src/stores/vrcStatus.js b/src/stores/vrcStatus.js new file mode 100644 index 00000000..24da3ade --- /dev/null +++ b/src/stores/vrcStatus.js @@ -0,0 +1,59 @@ +import { defineStore } from 'pinia'; +import webApiService from '../service/webapi'; +import { ref } from 'vue'; + +export const useVrcStatusStore = defineStore('VrcStatus', () => { + const vrcStatusApiUrl = 'https://status.vrchat.com/api/v2'; + + const lastStatus = ref(''); + const lastTimeFetched = ref(0); + const isAlertClosed = ref(false); + const pollingInterval = ref(0); + + async function getVrcStatus() { + const response = await webApiService.execute({ + url: `${vrcStatusApiUrl}/status.json`, + method: 'GET', + headers: { + Referer: 'https://vrcx.app' + } + }); + lastTimeFetched.value = Date.now(); + const data = JSON.parse(response.data); + if (data.status.description === 'All Systems Operational') { + lastStatus.value = ''; + pollingInterval.value = 15 * 60 * 1000; // 15 minutes + return; + } + lastStatus.value = data.status.description; + pollingInterval.value = 2 * 60 * 1000; // 2 minutes + } + + // ran from Cef and Electron when browser is focused + function onBrowserFocus() { + if (Date.now() - lastTimeFetched.value > 60 * 1000) { + getVrcStatus(); + } + } + + function init() { + getVrcStatus(); + setInterval(() => { + if (isAlertClosed.value) { + return; + } + if (Date.now() - lastTimeFetched.value > pollingInterval.value) { + getVrcStatus(); + } + }, 60 * 1000); + } + + init(); + + return { + lastStatus, + isAlertClosed, + onBrowserFocus, + getVrcStatus + }; +}); diff --git a/src/stores/vrcx.js b/src/stores/vrcx.js index 6dcc49a8..3435fbab 100644 --- a/src/stores/vrcx.js +++ b/src/stores/vrcx.js @@ -25,6 +25,7 @@ import { useAdvancedSettingsStore } from './settings/advanced'; import { useUpdateLoopStore } from './updateLoop'; import { useUserStore } from './user'; import { useWorldStore } from './world'; +import { useVrcStatusStore } from './vrcStatus'; import { useI18n } from 'vue-i18n'; import Noty from 'noty'; @@ -45,6 +46,7 @@ export const useVrcxStore = defineStore('Vrcx', () => { const avatarProviderStore = useAvatarProviderStore(); const gameLogStore = useGameLogStore(); const updateLoopStore = useUpdateLoopStore(); + const vrcStatusStore = useVrcStatusStore(); const { t } = useI18n(); const state = reactive({ @@ -87,6 +89,10 @@ export const useVrcxStore = defineStore('Vrcx', () => { state.windowState = newState.windowState; debounce(saveVRCXWindowOption, 300)(); }); + + window.electron.onBrowserFocus(() => { + vrcStatusStore.onBrowserFocus(); + }); } state.databaseVersion = await configRepository.getInt( diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 20773262..3c9664af 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -58,6 +58,7 @@ declare global { onWindowStateChange: ( Function: (event: any, state: { windowState: any }) => void ) => void; + onBrowserFocus: (Function: (event: any) => void) => void; restartApp: () => Promise; getWristOverlayWindow: () => Promise; getHmdOverlayWindow: () => Promise; diff --git a/src/views/Login/Login.vue b/src/views/Login/Login.vue index 2b6e815c..bfe55c70 100644 --- a/src/views/Login/Login.vue +++ b/src/views/Login/Login.vue @@ -1,26 +1,20 @@