add Sentry error reporting option

This commit is contained in:
pa
2025-09-14 17:43:09 +09:00
committed by Natsumi
parent 76ca91dcc2
commit 7bd7b4ae71
10 changed files with 267 additions and 26 deletions

121
package-lock.json generated
View File

@@ -15,10 +15,11 @@
"@eslint/js": "^9.35.0",
"@fontsource/noto-sans-jp": "^5.2.7",
"@fontsource/noto-sans-kr": "^5.2.7",
"@fontsource/noto-sans-sc": "^5.2.6",
"@fontsource/noto-sans-sc": "^5.2.7",
"@fontsource/noto-sans-tc": "^5.2.7",
"@sentry/vue": "^10.11.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.3.1",
"@types/node": "^24.3.3",
"@vitejs/plugin-vue": "^6.0.1",
"animate.css": "^4.1.1",
"babel-runtime": "^6.26.0",
@@ -26,21 +27,21 @@
"cross-env": "^10.0.0",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"electron": "^37.4.0",
"electron": "^37.5.0",
"electron-builder": "^26.0.12",
"element-plus": "^2.11.2",
"esbuild-jest": "^0.4.0",
"eslint": "^9.35.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.3.0",
"globals": "^16.4.0",
"jest": "^30.1.3",
"noty": "^3.2.0-beta-deprecated",
"pinia": "^3.0.3",
"prettier": "^3.6.2",
"remixicon": "^4.6.0",
"sass-embedded": "^1.92.1",
"vite": "^7.1.4",
"vite": "^7.1.5",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-marquee-text-component": "^2.0.1",
@@ -3903,6 +3904,110 @@
"win32"
]
},
"node_modules/@sentry-internal/browser-utils": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.11.0.tgz",
"integrity": "sha512-fnMlz5ntap6x4vRsLOHwPqXh7t82StgAiRt+EaqcMX0t9l8C0w0df8qwrONKXvE5GdHWTNFJj5qR15FERSkg3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/feedback": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.11.0.tgz",
"integrity": "sha512-ADey51IIaa29kepb8B7aSgSGSrcyT7QZdRsN1rhitefzrruHzpSUci5c2EPIvmWfKJq8Wnvukm9BHXZXAAIOzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.11.0.tgz",
"integrity": "sha512-t4M2bxMp2rKGK/l7bkVWjN+xVw9H9V12jAeXmO/Fskz2RcG1ZNLQnKSx/W/zCRMk8k7xOQFsfiApq+zDN+ziKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.11.0",
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry-internal/replay-canvas": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.11.0.tgz",
"integrity": "sha512-brWQ90IYQyZr44IpTprlmvbtz4l2ABzLdpP94Egh12Onf/q6n4CjLKaA25N5kX0uggHqX1Rs7dNaG0mP3ETHhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry-internal/replay": "10.11.0",
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/browser": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.11.0.tgz",
"integrity": "sha512-qemaKCJKJHHCyGBpdLq23xL5u9Xvir20XN7YFTnHcEq4Jvj0GoWsslxKi5cQB2JvpYn62WxTiDgVLeQlleZhSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry-internal/browser-utils": "10.11.0",
"@sentry-internal/feedback": "10.11.0",
"@sentry-internal/replay": "10.11.0",
"@sentry-internal/replay-canvas": "10.11.0",
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/core": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.11.0.tgz",
"integrity": "sha512-39Rxn8cDXConx3+SKOCAhW+/hklM7UDaz+U1OFzFMDlT59vXSpfI6bcXtNiFDrbOxlQ2hX8yAqx8YRltgSftoA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@sentry/vue": {
"version": "10.11.0",
"resolved": "https://registry.npmjs.org/@sentry/vue/-/vue-10.11.0.tgz",
"integrity": "sha512-uXPXce4QCEuutG3b7FEcA+fhvXCgVPA/iFyeBMdagtjKGRLWgM0nRgVww/WGrltq8414aq7dAiBTWmPKAoaslw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sentry/browser": "10.11.0",
"@sentry/core": "10.11.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"pinia": "2.x || 3.x",
"vue": "2.x || 3.x"
},
"peerDependenciesMeta": {
"pinia": {
"optional": true
}
}
},
"node_modules/@sinclair/typebox": {
"version": "0.34.41",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz",
@@ -4157,9 +4262,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "24.3.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
"version": "24.3.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.3.tgz",
"integrity": "sha512-GKBNHjoNw3Kra1Qg5UXttsY5kiWMEfoHq2TmXb+b1rcm6N7B3wTrFYIf/oSZ1xNQ+hVVijgLkiDZh7jRRsh+Gw==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -36,10 +36,11 @@
"@eslint/js": "^9.35.0",
"@fontsource/noto-sans-jp": "^5.2.7",
"@fontsource/noto-sans-kr": "^5.2.7",
"@fontsource/noto-sans-sc": "^5.2.6",
"@fontsource/noto-sans-sc": "^5.2.7",
"@fontsource/noto-sans-tc": "^5.2.7",
"@sentry/vue": "^10.11.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.3.1",
"@types/node": "^24.3.3",
"@vitejs/plugin-vue": "^6.0.1",
"animate.css": "^4.1.1",
"babel-runtime": "^6.26.0",
@@ -47,21 +48,21 @@
"cross-env": "^10.0.0",
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"electron": "^37.4.0",
"electron": "^37.5.0",
"electron-builder": "^26.0.12",
"element-plus": "^2.11.2",
"esbuild-jest": "^0.4.0",
"eslint": "^9.35.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-vue": "^9.33.0",
"globals": "^16.3.0",
"globals": "^16.4.0",
"jest": "^30.1.3",
"noty": "^3.2.0-beta-deprecated",
"pinia": "^3.0.3",
"prettier": "^3.6.2",
"remixicon": "^4.6.0",
"sass-embedded": "^1.92.1",
"vite": "^7.1.4",
"vite": "^7.1.5",
"vue": "^3.5.21",
"vue-i18n": "^11.1.12",
"vue-marquee-text-component": "^2.0.1",

View File

@@ -6,9 +6,7 @@
import { createApp } from 'vue';
import { pinia } from './stores';
import { initPlugins } from './plugin';
import { i18n } from './plugin/i18n';
import { initComponents } from './plugin/components';
import { initPlugins, i18n, initComponents, initSentry } from './plugin';
import ElementPlus from 'element-plus';
import App from './App.vue';
@@ -22,6 +20,7 @@ app.use(pinia);
app.use(i18n);
app.use(ElementPlus);
initComponents(app);
initSentry(app);
app.mount('#root');

View File

@@ -8,10 +8,6 @@
// For a copy, see <https://opensource.org/licenses/MIT>.
//
@use './assets/scss/flags.scss';
@use './assets/scss/animated-emoji.scss';
@use 'element-plus/theme-chalk/src/index.scss' as *;
@use '@fontsource/noto-sans-kr/korean.css';
@use '@fontsource/noto-sans-jp/japanese.css';
@use '@fontsource/noto-sans-sc/chinese-simplified.css';
@@ -21,6 +17,10 @@
@use '@fontsource/noto-sans-sc';
@use '@fontsource/noto-sans-tc';
@use './assets/scss/flags.scss';
@use './assets/scss/animated-emoji.scss';
@use 'element-plus/theme-chalk/src/index.scss' as *;
@use 'element-plus/theme-chalk/src/dark/css-vars.scss';
@use 'animate.css/animate.min.css';
@use 'noty/lib/noty.css';

View File

@@ -11,3 +11,7 @@ export async function initPlugins(isVrOverlay = false) {
initDayjs();
initNoty(isVrOverlay);
}
export * from './i18n';
export * from './components';
export * from './sentry';

33
src/plugin/sentry.js Normal file
View File

@@ -0,0 +1,33 @@
import * as Sentry from '@sentry/vue';
import configRepository from '../service/config';
export function initSentry(app) {
configRepository
.getString('VRCX_SentryEnabled', 'false')
.then((enabled) => {
let isNightly = false;
AppApi.GetVersion().then(
(v) => (isNightly = v.includes('Nightly'))
);
if (enabled === 'true' && isNightly) {
Sentry.init({
app,
dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0',
environment: 'nightly',
sampleRate: 0.1,
beforeSend(event) {
if (
event.message &&
(event.message.toLowerCase().includes('password') ||
event.message.toLowerCase().includes('token'))
) {
return null;
}
return event;
},
integrations: []
});
console.log('Sentry initialized');
}
});
}

View File

@@ -32,7 +32,10 @@ import { useVrcxStore } from './vrcx';
import { useVRCXUpdaterStore } from './vrcxUpdater';
import { useWorldStore } from './world';
import { createSentryPiniaPlugin } from '@sentry/vue';
export const pinia = createPinia();
pinia.use(createSentryPiniaPlugin());
export function createGlobalStores() {
return {

View File

@@ -8,11 +8,13 @@ import webApiService from '../../service/webapi';
import { watchState } from '../../service/watchState';
import { useGameStore } from '../game';
import { useVrcxStore } from '../vrcx';
import { useVRCXUpdaterStore } from '../vrcxUpdater';
import { AppDebug } from '../../service/appConfig';
export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
const gameStore = useGameStore();
const vrcxStore = useVrcxStore();
const VRCXUpdaterStore = useVRCXUpdaterStore();
const { t } = useI18n();
@@ -47,7 +49,8 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
isVRChatConfigDialogVisible: false,
saveInstanceEmoji: false,
vrcRegistryAutoBackup: true,
vrcRegistryAskRestore: true
vrcRegistryAskRestore: true,
sentryErrorReporting: false
});
async function initAdvancedSettings() {
@@ -78,7 +81,8 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
notificationOpacity,
saveInstanceEmoji,
vrcRegistryAutoBackup,
vrcRegistryAskRestore
vrcRegistryAskRestore,
sentryErrorReporting
] = await Promise.all([
configRepository.getBool('enablePrimaryPassword', false),
configRepository.getBool('VRCX_relaunchVRChatAfterCrash', false),
@@ -118,7 +122,8 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
configRepository.getFloat('VRCX_notificationOpacity', 100),
configRepository.getBool('VRCX_saveInstanceEmoji', false),
configRepository.getBool('VRCX_vrcRegistryAutoBackup', true),
configRepository.getBool('VRCX_vrcRegistryAskRestore', true)
configRepository.getBool('VRCX_vrcRegistryAskRestore', true),
configRepository.getString('VRCX_SentryEnabled', 'false')
]);
state.enablePrimaryPassword = enablePrimaryPassword;
@@ -148,8 +153,18 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
state.saveInstanceEmoji = saveInstanceEmoji;
state.vrcRegistryAutoBackup = vrcRegistryAutoBackup;
state.vrcRegistryAskRestore = vrcRegistryAskRestore;
state.sentryErrorReporting = sentryErrorReporting === 'true';
handleSetAppLauncherSettings();
setTimeout(() => {
if (
VRCXUpdaterStore.branch === 'Nightly' &&
sentryErrorReporting === ''
) {
checkSentryConsent();
}
}, 2000);
}
initAdvancedSettings();
@@ -225,6 +240,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
});
const vrcRegistryAutoBackup = computed(() => state.vrcRegistryAutoBackup);
const vrcRegistryAskRestore = computed(() => state.vrcRegistryAskRestore);
const sentryErrorReporting = computed(() => state.sentryErrorReporting);
/**
* @param {boolean} value
@@ -422,6 +438,69 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
);
}
async function checkSentryConsent() {
ElMessageBox.confirm(
'Help improve VRCX by allowing anonymous error reporting?\n\n' +
'• Only collects crash and error information\n' +
'• No personal data or VRChat information is collected\n' +
'• Only enabled in nightly builds\n' +
'• Can be disabled anytime in Advanced Settings',
'Anonymous Error Reporting',
{
type: 'info',
center: true
}
)
.then(() => {
state.sentryErrorReporting = true;
configRepository.setString('VRCX_SentryEnabled', 'true');
ElMessageBox.confirm(
'Error reporting setting has been enabled. Would you like to restart VRCX now for the change to take effect?',
'Restart Required',
{
confirmButtonText: 'Restart Now',
cancelButtonText: 'Later',
type: 'info',
center: true
}
).then(() => {
VRCXUpdaterStore.restartVRCX(false);
});
})
.catch(() => {
state.sentryErrorReporting = false;
configRepository.setString('VRCX_SentryEnabled', 'false');
});
}
async function setSentryErrorReporting() {
if (VRCXUpdaterStore.branch !== 'Nightly') {
return;
}
state.sentryErrorReporting = !state.sentryErrorReporting;
await configRepository.setString(
'VRCX_SentryEnabled',
state.sentryErrorReporting ? 'true' : 'false'
);
ElMessageBox.confirm(
'Error reporting setting has been disabled. Would you like to restart VRCX now for the change to take effect?',
'Restart Required',
{
confirmButtonText: 'Restart Now',
cancelButtonText: 'Later',
type: 'info',
center: true
}
)
.then(() => {
VRCXUpdaterStore.restartVRCX(false);
})
.catch(() => {});
}
async function getSqliteTableSizes() {
const [
gps,
@@ -726,6 +805,7 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
saveInstanceEmoji,
vrcRegistryAutoBackup,
vrcRegistryAskRestore,
sentryErrorReporting,
setEnablePrimaryPasswordConfigRepository,
setRelaunchVRChatAfterCrash,
@@ -764,6 +844,8 @@ export const useAdvancedSettingsStore = defineStore('AdvancedSettings', () => {
setSaveInstanceEmoji,
setVrcRegistryAutoBackup,
setVrcRegistryAskRestore,
setSentryErrorReporting,
checkSentryConsent,
askDeleteAllScreenshotMetadata
};
});

View File

@@ -1486,6 +1486,17 @@
setSelfInviteOverride();
saveOpenVROption();
" />
<!--//- Sentry Error Reporting (Nightly Only)-->
<div v-if="isNightlyBuild">
<span class="sub-header">Anonymous Error Reporting (Nightly Only)</span>
<simple-switch
label="Help improve VRCX by sending anonymous error reports. Only collects crash and error information, no personal data or VRChat information is collected."
:value="sentryErrorReporting"
:long-label="true"
@change="setSentryErrorReporting()" />
</div>
<!--//- Advanced | Disable local world database-->
</div>
@@ -2164,7 +2175,9 @@
ugcFolderPath,
notificationOpacity,
autoDeleteOldPrints,
saveInstanceEmoji
saveInstanceEmoji,
sentryErrorReporting,
isNightlyBuild
} = storeToRefs(advancedSettingsStore);
const {
@@ -2192,6 +2205,7 @@
showVRChatConfig,
promptAutoClearVRCXCacheFrequency,
setSaveInstanceEmoji,
setSentryErrorReporting,
askDeleteAllScreenshotMetadata
} = advancedSettingsStore;

View File

@@ -8,8 +8,6 @@
// For a copy, see <https://opensource.org/licenses/MIT>.
//
@use '../assets/scss/flags.scss';
@use '@fontsource/noto-sans-kr/korean.css';
@use '@fontsource/noto-sans-jp/japanese.css';
@use '@fontsource/noto-sans-sc/chinese-simplified.css';
@@ -19,6 +17,8 @@
@use '@fontsource/noto-sans-sc';
@use '@fontsource/noto-sans-tc';
@use '../assets/scss/flags.scss';
@use 'animate.css/animate.min.css';
@use 'noty/lib/noty.css';
@use 'remixicon/fonts/remixicon.css';