refactor: store state

This commit is contained in:
pa
2025-10-11 15:30:44 +09:00
committed by Natsumi
parent 1e18d89b61
commit 86f7847c46
31 changed files with 2719 additions and 4029 deletions

View File

@@ -1,6 +1,6 @@
import Noty from 'noty';
import { defineStore } from 'pinia';
import { computed, reactive, watch } from 'vue';
import { ref, reactive, watch } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import { authRequest } from '../api';
import { useI18n } from 'vue-i18n';
@@ -28,129 +28,60 @@ export const useAuthStore = defineStore('Auth', () => {
const { t } = useI18n();
const state = reactive({
attemptingAutoLogin: false,
autoLoginAttempts: new Set(),
loginForm: {
loading: false,
username: '',
password: '',
endpoint: '',
websocket: '',
saveCredentials: false,
savedCredentials: {},
lastUserLoggedIn: '',
rules: {
username: [
{
required: true,
trigger: 'blur'
}
],
password: [
{
required: true,
trigger: 'blur'
}
]
}
},
enablePrimaryPasswordDialog: {
visible: false,
password: '',
rePassword: '',
beforeClose(done) {
// $app._data.enablePrimaryPassword = false;
done();
}
},
saveCredentials: null,
// it's a flag
twoFactorAuthDialogVisible: false,
enableCustomEndpoint: false,
cachedConfig: {}
});
async function init() {
const [savedCredentials, lastUserLoggedIn, enableCustomEndpoint] =
await Promise.all([
configRepository.getString('savedCredentials'),
configRepository.getString('lastUserLoggedIn'),
configRepository.getBool('VRCX_enableCustomEndpoint', false)
]);
try {
state.loginForm = {
...state.loginForm,
savedCredentials: savedCredentials
? JSON.parse(savedCredentials)
: {},
lastUserLoggedIn
};
} catch (error) {
console.error('Failed to parse savedCredentials:', error);
state.loginForm = {
...state.loginForm,
savedCredentials: {},
lastUserLoggedIn
};
}
state.enableCustomEndpoint = enableCustomEndpoint;
}
init();
const loginForm = computed({
get: () => state.loginForm,
set: (value) => {
state.loginForm = value;
const loginForm = ref({
loading: false,
username: '',
password: '',
endpoint: '',
websocket: '',
saveCredentials: false,
savedCredentials: {},
lastUserLoggedIn: '',
rules: {
username: [
{
required: true,
trigger: 'blur'
}
],
password: [
{
required: true,
trigger: 'blur'
}
]
}
});
const enablePrimaryPasswordDialog = computed({
get: () => state.enablePrimaryPasswordDialog,
set: (value) => {
state.enablePrimaryPasswordDialog = value;
const enablePrimaryPasswordDialog = ref({
visible: false,
password: '',
rePassword: '',
beforeClose(done) {
// $app._data.enablePrimaryPassword = false;
done();
}
});
const saveCredentials = computed({
get: () => state.saveCredentials,
set: (value) => {
state.saveCredentials = value;
}
});
const saveCredentials = ref(null);
const twoFactorAuthDialogVisible = computed({
get: () => state.twoFactorAuthDialogVisible,
set: (value) => {
state.twoFactorAuthDialogVisible = value;
}
});
const twoFactorAuthDialogVisible = ref(false);
const cachedConfig = computed({
get: () => state.cachedConfig,
set: (value) => {
state.cachedConfig = value;
}
});
const cachedConfig = ref({});
const enableCustomEndpoint = computed({
get: () => state.enableCustomEndpoint,
set: (value) => {
state.enableCustomEndpoint = value;
}
});
const enableCustomEndpoint = ref(false);
const attemptingAutoLogin = computed({
get: () => state.attemptingAutoLogin,
set: (value) => {
state.attemptingAutoLogin = value;
}
});
const attemptingAutoLogin = ref(false);
watch(
[() => watchState.isLoggedIn, () => userStore.currentUser],
([isLoggedIn, currentUser]) => {
state.twoFactorAuthDialogVisible = false;
twoFactorAuthDialogVisible.value = false;
if (isLoggedIn) {
updateStoredUser(currentUser);
new Noty({
@@ -175,6 +106,34 @@ export const useAuthStore = defineStore('Auth', () => {
{ flush: 'sync' }
);
async function init() {
const [savedCredentials, lastUserLoggedIn, enableCustomEndpoint] =
await Promise.all([
configRepository.getString('savedCredentials'),
configRepository.getString('lastUserLoggedIn'),
configRepository.getBool('VRCX_enableCustomEndpoint', false)
]);
try {
loginForm.value = {
...loginForm.value,
savedCredentials: savedCredentials
? JSON.parse(savedCredentials)
: {},
lastUserLoggedIn
};
} catch (error) {
console.error('Failed to parse savedCredentials:', error);
loginForm.value = {
...loginForm.value,
savedCredentials: {},
lastUserLoggedIn
};
}
state.enableCustomEndpoint = enableCustomEndpoint;
}
init();
async function handleLogoutEvent() {
if (watchState.isLoggedIn) {
new Noty({
@@ -190,10 +149,10 @@ export const useAuthStore = defineStore('Auth', () => {
notificationStore.notificationInitStatus = false;
await updateStoredUser(userStore.currentUser);
webApiService.clearCookies();
state.loginForm.lastUserLoggedIn = '';
loginForm.value.lastUserLoggedIn = '';
await configRepository.remove('lastUserLoggedIn');
// workerTimers.setTimeout(() => location.reload(), 500);
state.attemptingAutoLogin = false;
attemptingAutoLogin.value = false;
state.autoLoginAttempts.clear();
closeWebSocket();
}
@@ -208,26 +167,26 @@ export const useAuthStore = defineStore('Auth', () => {
(await configRepository.getString('lastUserLoggedIn')) !== null
) {
const user =
state.loginForm.savedCredentials[
state.loginForm.lastUserLoggedIn
loginForm.value.savedCredentials[
loginForm.value.lastUserLoggedIn
];
if (user?.loginParmas?.endpoint) {
AppDebug.endpointDomain = user.loginParmas.endpoint;
AppDebug.websocketDomain = user.loginParmas.websocket;
}
// login at startup
state.loginForm.loading = true;
loginForm.value.loading = true;
authRequest
.getConfig()
.catch((err) => {
state.loginForm.loading = false;
loginForm.value.loading = false;
throw err;
})
.then(() => {
userStore
.getCurrentUser()
.finally(() => {
state.loginForm.loading = false;
loginForm.value.loading = false;
})
.catch((err) => {
updateLoopStore.nextCurrentUserRefresh = 60; // 1min
@@ -239,10 +198,10 @@ export const useAuthStore = defineStore('Auth', () => {
async function clearCookiesTryLogin() {
await webApiService.clearCookies();
if (state.loginForm.lastUserLoggedIn) {
if (loginForm.value.lastUserLoggedIn) {
const user =
state.loginForm.savedCredentials[
state.loginForm.lastUserLoggedIn
loginForm.value.savedCredentials[
loginForm.value.lastUserLoggedIn
];
if (typeof user !== 'undefined') {
delete user.cookies;
@@ -252,10 +211,10 @@ export const useAuthStore = defineStore('Auth', () => {
}
async function resendEmail2fa() {
if (state.loginForm.lastUserLoggedIn) {
if (loginForm.value.lastUserLoggedIn) {
const user =
state.loginForm.savedCredentials[
state.loginForm.lastUserLoggedIn
loginForm.value.savedCredentials[
loginForm.value.lastUserLoggedIn
];
if (typeof user !== 'undefined') {
await webApiService.clearCookies();
@@ -279,10 +238,10 @@ export const useAuthStore = defineStore('Auth', () => {
advancedSettingsStore.enablePrimaryPassword =
!advancedSettingsStore.enablePrimaryPassword;
state.enablePrimaryPasswordDialog.password = '';
state.enablePrimaryPasswordDialog.rePassword = '';
enablePrimaryPasswordDialog.value.password = '';
enablePrimaryPasswordDialog.value.rePassword = '';
if (advancedSettingsStore.enablePrimaryPassword) {
state.enablePrimaryPasswordDialog.visible = true;
enablePrimaryPasswordDialog.value.visible = true;
} else {
ElMessageBox.prompt(
t('prompt.primary_password.description'),
@@ -293,22 +252,22 @@ export const useAuthStore = defineStore('Auth', () => {
}
)
.then(({ value }) => {
for (const userId in state.loginForm.savedCredentials) {
for (const userId in loginForm.value.savedCredentials) {
security
.decrypt(
state.loginForm.savedCredentials[userId]
loginForm.value.savedCredentials[userId]
.loginParmas.password,
value
)
.then(async (pt) => {
state.saveCredentials = {
saveCredentials.value = {
username:
state.loginForm.savedCredentials[userId]
loginForm.value.savedCredentials[userId]
.loginParmas.username,
password: pt
};
await updateStoredUser(
state.loginForm.savedCredentials[userId]
loginForm.value.savedCredentials[userId]
.user
);
await configRepository.setBool(
@@ -337,25 +296,25 @@ export const useAuthStore = defineStore('Auth', () => {
'enablePrimaryPassword',
advancedSettingsStore.enablePrimaryPassword
);
state.enablePrimaryPasswordDialog.visible = false;
enablePrimaryPasswordDialog.value.visible = false;
if (advancedSettingsStore.enablePrimaryPassword) {
const key = state.enablePrimaryPasswordDialog.password;
for (const userId in state.loginForm.savedCredentials) {
const key = enablePrimaryPasswordDialog.value.password;
for (const userId in loginForm.value.savedCredentials) {
security
.encrypt(
state.loginForm.savedCredentials[userId].loginParmas
loginForm.value.savedCredentials[userId].loginParmas
.password,
key
)
.then((ct) => {
state.saveCredentials = {
saveCredentials.value = {
username:
state.loginForm.savedCredentials[userId]
loginForm.value.savedCredentials[userId]
.loginParmas.username,
password: ct
};
updateStoredUser(
state.loginForm.savedCredentials[userId].user
loginForm.value.savedCredentials[userId].user
);
});
}
@@ -369,25 +328,25 @@ export const useAuthStore = defineStore('Auth', () => {
await configRepository.getString('savedCredentials')
);
}
if (state.saveCredentials) {
if (saveCredentials.value) {
const credentialsToSave = {
user,
loginParmas: state.saveCredentials
loginParmas: saveCredentials.value
};
savedCredentials[user.id] = credentialsToSave;
state.saveCredentials = null;
saveCredentials.value = null;
} else if (typeof savedCredentials[user.id] !== 'undefined') {
savedCredentials[user.id].user = user;
savedCredentials[user.id].cookies =
await webApiService.getCookies();
}
state.loginForm.savedCredentials = savedCredentials;
loginForm.value.savedCredentials = savedCredentials;
const jsonCredentialsArray = JSON.stringify(savedCredentials);
await configRepository.setString(
'savedCredentials',
jsonCredentialsArray
);
state.loginForm.lastUserLoggedIn = user.id;
loginForm.value.lastUserLoggedIn = user.id;
await configRepository.setString('lastUserLoggedIn', user.id);
}
@@ -439,8 +398,8 @@ export const useAuthStore = defineStore('Auth', () => {
'VRCX_enableCustomEndpoint',
state.enableCustomEndpoint
);
state.loginForm.endpoint = '';
state.loginForm.websocket = '';
loginForm.value.endpoint = '';
loginForm.value.websocket = '';
}
function logout() {
@@ -468,7 +427,7 @@ export const useAuthStore = defineStore('Auth', () => {
if (user.cookies) {
await webApiService.setCookies(user.cookies);
}
state.loginForm.lastUserLoggedIn = user.user.id; // for resend email 2fa
loginForm.value.lastUserLoggedIn = user.user.id; // for resend email 2fa
if (loginParmas.endpoint) {
AppDebug.endpointDomain = loginParmas.endpoint;
AppDebug.websocketDomain = loginParmas.websocket;
@@ -477,7 +436,7 @@ export const useAuthStore = defineStore('Auth', () => {
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
}
return new Promise((resolve, reject) => {
state.loginForm.loading = true;
loginForm.value.loading = true;
if (advancedSettingsStore.enablePrimaryPassword) {
checkPrimaryPassword(loginParmas)
.then((pwd) => {
@@ -531,7 +490,7 @@ export const useAuthStore = defineStore('Auth', () => {
});
});
}
}).finally(() => (state.loginForm.loading = false));
}).finally(() => (loginForm.value.loading = false));
}
async function deleteSavedLogin(userId) {
@@ -546,7 +505,7 @@ export const useAuthStore = defineStore('Auth', () => {
false
);
}
state.loginForm.savedCredentials = savedCredentials;
loginForm.value.savedCredentials = savedCredentials;
const jsonCredentials = JSON.stringify(savedCredentials);
await configRepository.setString('savedCredentials', jsonCredentials);
new Noty({
@@ -558,11 +517,11 @@ export const useAuthStore = defineStore('Auth', () => {
async function login() {
// TODO: remove/refactor saveCredentials & primaryPassword (security)
await webApiService.clearCookies();
if (!state.loginForm.loading) {
state.loginForm.loading = true;
if (state.loginForm.endpoint) {
AppDebug.endpointDomain = state.loginForm.endpoint;
AppDebug.websocketDomain = state.loginForm.websocket;
if (!loginForm.value.loading) {
loginForm.value.loading = true;
if (loginForm.value.endpoint) {
AppDebug.endpointDomain = loginForm.value.endpoint;
AppDebug.websocketDomain = loginForm.value.websocket;
} else {
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
@@ -570,12 +529,12 @@ export const useAuthStore = defineStore('Auth', () => {
authRequest
.getConfig()
.catch((err) => {
state.loginForm.loading = false;
loginForm.value.loading = false;
throw err;
})
.then((args) => {
if (
state.loginForm.saveCredentials &&
loginForm.value.saveCredentials &&
advancedSettingsStore.enablePrimaryPassword
) {
ElMessageBox.prompt(
@@ -588,9 +547,9 @@ export const useAuthStore = defineStore('Auth', () => {
)
.then(({ value }) => {
const saveCredential =
state.loginForm.savedCredentials[
loginForm.value.savedCredentials[
Object.keys(
state.loginForm.savedCredentials
loginForm.value.savedCredentials
)[0]
];
security
@@ -601,25 +560,25 @@ export const useAuthStore = defineStore('Auth', () => {
.then(() => {
security
.encrypt(
state.loginForm.password,
loginForm.value.password,
value
)
.then((pwd) => {
authLogin({
username:
state.loginForm
loginForm.value
.username,
password:
state.loginForm
loginForm.value
.password,
endpoint:
state.loginForm
loginForm.value
.endpoint,
websocket:
state.loginForm
loginForm.value
.websocket,
saveCredentials:
state.loginForm
loginForm.value
.saveCredentials,
cipher: pwd
});
@@ -627,18 +586,18 @@ export const useAuthStore = defineStore('Auth', () => {
});
})
.finally(() => {
state.loginForm.loading = false;
loginForm.value.loading = false;
});
return args;
}
authLogin({
username: state.loginForm.username,
password: state.loginForm.password,
endpoint: state.loginForm.endpoint,
websocket: state.loginForm.websocket,
saveCredentials: state.loginForm.saveCredentials
username: loginForm.value.username,
password: loginForm.value.password,
endpoint: loginForm.value.endpoint,
websocket: loginForm.value.websocket,
saveCredentials: loginForm.value.saveCredentials
}).finally(() => {
state.loginForm.loading = false;
loginForm.value.loading = false;
});
return args;
});
@@ -646,11 +605,11 @@ export const useAuthStore = defineStore('Auth', () => {
}
function promptTOTP() {
if (state.twoFactorAuthDialogVisible) {
if (twoFactorAuthDialogVisible.value) {
return;
}
AppApi.FlashWindow();
state.twoFactorAuthDialogVisible = true;
twoFactorAuthDialogVisible.value = true;
ElMessageBox.prompt(
t('prompt.totp.description'),
t('prompt.totp.header'),
@@ -662,7 +621,7 @@ export const useAuthStore = defineStore('Auth', () => {
inputPattern: /^[0-9]{6}$/,
inputErrorMessage: t('prompt.totp.input_error'),
beforeClose: (action, instance, done) => {
state.twoFactorAuthDialogVisible = false;
twoFactorAuthDialogVisible.value = false;
if (action === 'cancel') {
promptOTP();
}
@@ -687,10 +646,10 @@ export const useAuthStore = defineStore('Auth', () => {
}
function promptOTP() {
if (state.twoFactorAuthDialogVisible) {
if (twoFactorAuthDialogVisible.value) {
return;
}
state.twoFactorAuthDialogVisible = true;
twoFactorAuthDialogVisible.value = true;
ElMessageBox.prompt(
t('prompt.otp.description'),
t('prompt.otp.header'),
@@ -702,7 +661,7 @@ export const useAuthStore = defineStore('Auth', () => {
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
inputErrorMessage: t('prompt.otp.input_error'),
beforeClose: (action, instance, done) => {
state.twoFactorAuthDialogVisible = false;
twoFactorAuthDialogVisible.value = false;
if (action === 'cancel') {
promptTOTP();
}
@@ -727,11 +686,11 @@ export const useAuthStore = defineStore('Auth', () => {
}
function promptEmailOTP() {
if (state.twoFactorAuthDialogVisible) {
if (twoFactorAuthDialogVisible.value) {
return;
}
AppApi.FlashWindow();
state.twoFactorAuthDialogVisible = true;
twoFactorAuthDialogVisible.value = true;
ElMessageBox.prompt(
t('prompt.email_otp.description'),
t('prompt.email_otp.header'),
@@ -743,7 +702,7 @@ export const useAuthStore = defineStore('Auth', () => {
inputPattern: /^[0-9]{6}$/,
inputErrorMessage: t('prompt.email_otp.input_error'),
beforeClose: (action, instance, done) => {
state.twoFactorAuthDialogVisible = false;
twoFactorAuthDialogVisible.value = false;
if (action === 'cancel') {
resendEmail2fa();
return;
@@ -783,7 +742,7 @@ export const useAuthStore = defineStore('Auth', () => {
params.password = cipher;
delete params.cipher;
}
state.saveCredentials = params;
saveCredentials.value = params;
}
return request('auth/user', {
method: 'GET',
@@ -816,21 +775,21 @@ export const useAuthStore = defineStore('Auth', () => {
}
function handleAutoLogin() {
if (state.attemptingAutoLogin) {
if (attemptingAutoLogin.value) {
return;
}
state.attemptingAutoLogin = true;
attemptingAutoLogin.value = true;
const user =
state.loginForm.savedCredentials[state.loginForm.lastUserLoggedIn];
loginForm.value.savedCredentials[loginForm.value.lastUserLoggedIn];
if (typeof user === 'undefined') {
state.attemptingAutoLogin = false;
attemptingAutoLogin.value = false;
return;
}
if (advancedSettingsStore.enablePrimaryPassword) {
console.error(
'Primary password is enabled, this disables auto login.'
);
state.attemptingAutoLogin = false;
attemptingAutoLogin.value = false;
handleLogoutEvent();
return;
}
@@ -841,7 +800,7 @@ export const useAuthStore = defineStore('Auth', () => {
console.error(
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
);
state.attemptingAutoLogin = false;
attemptingAutoLogin.value = false;
handleLogoutEvent();
return;
}