mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-19 14:53:50 +02:00
refactor coordinators
This commit is contained in:
@@ -1,80 +1,82 @@
|
|||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import { AppDebug } from '../service/appConfig';
|
||||||
|
import { useAdvancedSettingsStore } from '../stores/settings/advanced';
|
||||||
|
import { useAuthStore } from '../stores/auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Runs the full auto-login orchestration flow.
|
||||||
* @returns {object} Auto-login flow coordinator methods.
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
|
* @param {function} [options.isOnline] Online-check provider.
|
||||||
*/
|
*/
|
||||||
export function createAuthAutoLoginCoordinator(deps) {
|
export async function runHandleAutoLoginFlow({
|
||||||
const {
|
now = Date.now,
|
||||||
getIsAttemptingAutoLogin,
|
isOnline = () => navigator.onLine
|
||||||
setAttemptingAutoLogin,
|
} = {}) {
|
||||||
getLastUserLoggedIn,
|
const authStore = useAuthStore();
|
||||||
getSavedCredentials,
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||||
isPrimaryPasswordEnabled,
|
const { t } = useI18n();
|
||||||
handleLogoutEvent,
|
|
||||||
autoLoginAttempts,
|
|
||||||
relogin,
|
|
||||||
notifyAutoLoginSuccess,
|
|
||||||
notifyAutoLoginFailed,
|
|
||||||
notifyOffline,
|
|
||||||
flashWindow,
|
|
||||||
isOnline,
|
|
||||||
now
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
if (authStore.attemptingAutoLogin) {
|
||||||
* Runs the full auto-login orchestration flow.
|
return;
|
||||||
*/
|
|
||||||
async function runHandleAutoLoginFlow() {
|
|
||||||
if (getIsAttemptingAutoLogin()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAttemptingAutoLogin(true);
|
|
||||||
const user = await getSavedCredentials(getLastUserLoggedIn());
|
|
||||||
if (!user) {
|
|
||||||
setAttemptingAutoLogin(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isPrimaryPasswordEnabled()) {
|
|
||||||
console.error(
|
|
||||||
'Primary password is enabled, this disables auto login.'
|
|
||||||
);
|
|
||||||
setAttemptingAutoLogin(false);
|
|
||||||
await handleLogoutEvent();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const currentTimestamp = now();
|
|
||||||
const attemptsInLastHour = Array.from(autoLoginAttempts).filter(
|
|
||||||
(timestamp) => timestamp > currentTimestamp - 3600000
|
|
||||||
).length;
|
|
||||||
if (attemptsInLastHour >= 3) {
|
|
||||||
console.error(
|
|
||||||
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
|
|
||||||
);
|
|
||||||
setAttemptingAutoLogin(false);
|
|
||||||
await handleLogoutEvent();
|
|
||||||
flashWindow();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
autoLoginAttempts.add(currentTimestamp);
|
|
||||||
console.log('Attempting automatic login...');
|
|
||||||
relogin(user)
|
|
||||||
.then(() => {
|
|
||||||
notifyAutoLoginSuccess();
|
|
||||||
console.log('Automatically logged in.');
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
notifyAutoLoginFailed();
|
|
||||||
console.error('Failed to login automatically.', err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setAttemptingAutoLogin(false);
|
|
||||||
if (!isOnline()) {
|
|
||||||
notifyOffline();
|
|
||||||
console.error(`You're offline.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
authStore.setAttemptingAutoLogin(true);
|
||||||
return {
|
const user = await authStore.getSavedCredentials(
|
||||||
runHandleAutoLoginFlow
|
authStore.loginForm.lastUserLoggedIn
|
||||||
};
|
);
|
||||||
|
if (!user) {
|
||||||
|
authStore.setAttemptingAutoLogin(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||||
|
console.error('Primary password is enabled, this disables auto login.');
|
||||||
|
authStore.setAttemptingAutoLogin(false);
|
||||||
|
await authStore.handleLogoutEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentTimestamp = now();
|
||||||
|
const autoLoginAttempts = authStore.state.autoLoginAttempts;
|
||||||
|
const attemptsInLastHour = Array.from(autoLoginAttempts).filter(
|
||||||
|
(timestamp) => timestamp > currentTimestamp - 3600000
|
||||||
|
).length;
|
||||||
|
if (attemptsInLastHour >= 3) {
|
||||||
|
console.error(
|
||||||
|
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
|
||||||
|
);
|
||||||
|
authStore.setAttemptingAutoLogin(false);
|
||||||
|
await authStore.handleLogoutEvent();
|
||||||
|
AppApi.FlashWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
autoLoginAttempts.add(currentTimestamp);
|
||||||
|
console.log('Attempting automatic login...');
|
||||||
|
authStore
|
||||||
|
.relogin(user)
|
||||||
|
.then(() => {
|
||||||
|
if (AppDebug.errorNoty) {
|
||||||
|
toast.dismiss(AppDebug.errorNoty);
|
||||||
|
}
|
||||||
|
AppDebug.errorNoty = toast.success(
|
||||||
|
t('message.auth.auto_login_success')
|
||||||
|
);
|
||||||
|
console.log('Automatically logged in.');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (AppDebug.errorNoty) {
|
||||||
|
toast.dismiss(AppDebug.errorNoty);
|
||||||
|
}
|
||||||
|
AppDebug.errorNoty = toast.error(
|
||||||
|
t('message.auth.auto_login_failed')
|
||||||
|
);
|
||||||
|
console.error('Failed to login automatically.', err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
authStore.setAttemptingAutoLogin(false);
|
||||||
|
if (!isOnline()) {
|
||||||
|
AppDebug.errorNoty = toast.error(t('message.auth.offline'));
|
||||||
|
console.error(`You're offline.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +1,61 @@
|
|||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
|
import Noty from 'noty';
|
||||||
|
|
||||||
|
import { closeWebSocket, initWebsocket } from '../service/websocket';
|
||||||
|
import { escapeTag } from '../shared/utils';
|
||||||
|
import { queryClient } from '../queries';
|
||||||
|
import { useAuthStore } from '../stores/auth';
|
||||||
|
import { useNotificationStore } from '../stores/notification';
|
||||||
|
import { useUpdateLoopStore } from '../stores/updateLoop';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
|
import webApiService from '../service/webapi';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Runs the shared logout side effects (including goodbye notification).
|
||||||
* @returns {object} Auth flow coordinator methods.
|
|
||||||
*/
|
*/
|
||||||
export function createAuthCoordinator(deps) {
|
export async function runLogoutFlow() {
|
||||||
const {
|
const authStore = useAuthStore();
|
||||||
userStore,
|
const userStore = useUserStore();
|
||||||
notificationStore,
|
const notificationStore = useNotificationStore();
|
||||||
updateLoopStore,
|
const { t } = useI18n();
|
||||||
initWebsocket,
|
|
||||||
updateStoredUser,
|
|
||||||
webApiService,
|
|
||||||
loginForm,
|
|
||||||
configRepository,
|
|
||||||
setAttemptingAutoLogin,
|
|
||||||
autoLoginAttempts,
|
|
||||||
closeWebSocket,
|
|
||||||
queryClient,
|
|
||||||
watchState
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
if (watchState.isLoggedIn) {
|
||||||
* Runs the shared logout side effects.
|
new Noty({
|
||||||
*/
|
type: 'success',
|
||||||
async function runLogoutFlow() {
|
text: t('message.auth.logout_greeting', {
|
||||||
userStore.setUserDialogVisible(false);
|
name: `<strong>${escapeTag(userStore.currentUser.displayName)}</strong>`
|
||||||
watchState.isLoggedIn = false;
|
})
|
||||||
watchState.isFriendsLoaded = false;
|
}).show();
|
||||||
watchState.isFavoritesLoaded = false;
|
|
||||||
notificationStore.setNotificationInitStatus(false);
|
|
||||||
await updateStoredUser(userStore.currentUser);
|
|
||||||
webApiService.clearCookies();
|
|
||||||
loginForm.value.lastUserLoggedIn = '';
|
|
||||||
// workerTimers.setTimeout(() => location.reload(), 500);
|
|
||||||
await configRepository.remove('lastUserLoggedIn');
|
|
||||||
setAttemptingAutoLogin(false);
|
|
||||||
autoLoginAttempts.clear();
|
|
||||||
closeWebSocket();
|
|
||||||
queryClient.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
userStore.setUserDialogVisible(false);
|
||||||
* Runs post-login side effects after a successful auth response.
|
watchState.isLoggedIn = false;
|
||||||
* @param {object} json Current user payload from auth API.
|
watchState.isFriendsLoaded = false;
|
||||||
*/
|
watchState.isFavoritesLoaded = false;
|
||||||
function runLoginSuccessFlow(json) {
|
notificationStore.setNotificationInitStatus(false);
|
||||||
updateLoopStore.setNextCurrentUserRefresh(420); // 7mins
|
await authStore.updateStoredUser(userStore.currentUser);
|
||||||
userStore.applyCurrentUser(json);
|
webApiService.clearCookies();
|
||||||
initWebsocket();
|
authStore.loginForm.lastUserLoggedIn = '';
|
||||||
}
|
await configRepository.remove('lastUserLoggedIn');
|
||||||
|
authStore.setAttemptingAutoLogin(false);
|
||||||
return {
|
authStore.state.autoLoginAttempts.clear();
|
||||||
runLogoutFlow,
|
closeWebSocket();
|
||||||
runLoginSuccessFlow
|
queryClient.clear();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs post-login side effects after a successful auth response.
|
||||||
|
* @param {object} json Current user payload from auth API.
|
||||||
|
*/
|
||||||
|
export function runLoginSuccessFlow(json) {
|
||||||
|
const updateLoopStore = useUpdateLoopStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
updateLoopStore.setNextCurrentUserRefresh(420); // 7mins
|
||||||
|
userStore.applyCurrentUser(json);
|
||||||
|
initWebsocket();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,289 +1,298 @@
|
|||||||
|
import { getGroupName, getWorldName, isRealInstance } from '../shared/utils';
|
||||||
|
import { AppDebug } from '../service/appConfig';
|
||||||
|
import { database } from '../service/database';
|
||||||
|
import { useFeedStore } from '../stores/feed';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { userRequest } from '../api';
|
||||||
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* @param {object} ctx
|
||||||
* @returns {object} Friend presence coordinator methods.
|
* @param {string} newState
|
||||||
|
* @param {string} location
|
||||||
|
* @param {number} $location_at
|
||||||
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
*/
|
*/
|
||||||
export function createFriendPresenceCoordinator(deps) {
|
export async function runUpdateFriendDelayedCheckFlow(
|
||||||
|
ctx,
|
||||||
|
newState,
|
||||||
|
location,
|
||||||
|
$location_at,
|
||||||
|
{ now = Date.now, nowIso = () => new Date().toJSON() } = {}
|
||||||
|
) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const feedStore = useFeedStore();
|
||||||
|
const { friends, localFavoriteFriends } = friendStore;
|
||||||
|
|
||||||
|
let feed;
|
||||||
|
let groupName;
|
||||||
|
let worldName;
|
||||||
|
const id = ctx.id;
|
||||||
|
if (AppDebug.debugFriendState) {
|
||||||
|
console.log(
|
||||||
|
`${ctx.name} updateFriendState ${ctx.state} -> ${newState}`
|
||||||
|
);
|
||||||
|
if (typeof ctx.ref !== 'undefined' && location !== ctx.ref.location) {
|
||||||
|
console.log(
|
||||||
|
`${ctx.name} pendingOfflineLocation ${location} -> ${ctx.ref.location}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!friends.has(id)) {
|
||||||
|
console.log('Friend not found', id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isVIP = localFavoriteFriends.has(id);
|
||||||
|
const ref = ctx.ref;
|
||||||
|
if (ctx.state !== newState && typeof ctx.ref !== 'undefined') {
|
||||||
|
if (
|
||||||
|
(newState === 'offline' || newState === 'active') &&
|
||||||
|
ctx.state === 'online'
|
||||||
|
) {
|
||||||
|
ctx.ref.$online_for = '';
|
||||||
|
ctx.ref.$offline_for = now();
|
||||||
|
ctx.ref.$active_for = '';
|
||||||
|
if (newState === 'active') {
|
||||||
|
ctx.ref.$active_for = now();
|
||||||
|
}
|
||||||
|
const ts = now();
|
||||||
|
const time = ts - $location_at;
|
||||||
|
worldName = await getWorldName(location);
|
||||||
|
groupName = await getGroupName(location);
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'Offline',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
location,
|
||||||
|
worldName,
|
||||||
|
groupName,
|
||||||
|
time
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addOnlineOfflineToDatabase(feed);
|
||||||
|
} else if (
|
||||||
|
newState === 'online' &&
|
||||||
|
(ctx.state === 'offline' || ctx.state === 'active')
|
||||||
|
) {
|
||||||
|
ctx.ref.$previousLocation = '';
|
||||||
|
ctx.ref.$travelingToTime = now();
|
||||||
|
ctx.ref.$location_at = now();
|
||||||
|
ctx.ref.$online_for = now();
|
||||||
|
ctx.ref.$offline_for = '';
|
||||||
|
ctx.ref.$active_for = '';
|
||||||
|
worldName = await getWorldName(location);
|
||||||
|
groupName = await getGroupName(location);
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'Online',
|
||||||
|
userId: id,
|
||||||
|
displayName: ctx.name,
|
||||||
|
location,
|
||||||
|
worldName,
|
||||||
|
groupName,
|
||||||
|
time: ''
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addOnlineOfflineToDatabase(feed);
|
||||||
|
}
|
||||||
|
if (newState === 'active') {
|
||||||
|
ctx.ref.$active_for = now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx.state !== newState) {
|
||||||
|
ctx.state = newState;
|
||||||
|
friendStore.updateOnlineFriendCounter();
|
||||||
|
}
|
||||||
|
if (ref?.displayName) {
|
||||||
|
ctx.name = ref.displayName;
|
||||||
|
}
|
||||||
|
ctx.isVIP = isVIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles immediate friend presence updates and pending-offline orchestration.
|
||||||
|
* @param {string} id Friend id.
|
||||||
|
* @param {string | undefined} stateInput Optional incoming state.
|
||||||
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
|
*/
|
||||||
|
export async function runUpdateFriendFlow(
|
||||||
|
id,
|
||||||
|
stateInput = undefined,
|
||||||
|
{ now = Date.now, nowIso = () => new Date().toJSON() } = {}
|
||||||
|
) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
const {
|
const {
|
||||||
friends,
|
friends,
|
||||||
localFavoriteFriends,
|
localFavoriteFriends,
|
||||||
pendingOfflineMap,
|
pendingOfflineMap,
|
||||||
pendingOfflineDelay,
|
pendingOfflineDelay
|
||||||
watchState,
|
} = friendStore;
|
||||||
appDebug,
|
|
||||||
getCachedUsers,
|
|
||||||
isRealInstance,
|
|
||||||
requestUser,
|
|
||||||
getWorldName,
|
|
||||||
getGroupName,
|
|
||||||
feedStore,
|
|
||||||
database,
|
|
||||||
updateOnlineFriendCounter,
|
|
||||||
now,
|
|
||||||
nowIso
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
const ctx = friends.get(id);
|
||||||
* @param {object} ctx
|
if (typeof ctx === 'undefined') {
|
||||||
* @param {string} newState
|
return;
|
||||||
* @param {string} location
|
}
|
||||||
* @param {number} $location_at
|
const ref = userStore.cachedUsers.get(id);
|
||||||
*/
|
|
||||||
async function runUpdateFriendDelayedCheckFlow(
|
if (stateInput === 'online') {
|
||||||
ctx,
|
const pendingOffline = pendingOfflineMap.get(id);
|
||||||
newState,
|
if (AppDebug.debugFriendState && pendingOffline) {
|
||||||
location,
|
const time = (now() - pendingOffline.startTime) / 1000;
|
||||||
$location_at
|
console.log(`${ctx.name} pendingOfflineCancelTime ${time}`);
|
||||||
) {
|
}
|
||||||
let feed;
|
ctx.pendingOffline = false;
|
||||||
let groupName;
|
pendingOfflineMap.delete(id);
|
||||||
let worldName;
|
}
|
||||||
const id = ctx.id;
|
const isVIP = localFavoriteFriends.has(id);
|
||||||
if (appDebug.debugFriendState) {
|
let location = '';
|
||||||
|
let $location_at = undefined;
|
||||||
|
if (typeof ref !== 'undefined') {
|
||||||
|
location = ref.location;
|
||||||
|
$location_at = ref.$location_at;
|
||||||
|
|
||||||
|
const currentState = stateInput || ctx.state;
|
||||||
|
// wtf, fetch user if offline in an instance
|
||||||
|
if (
|
||||||
|
currentState !== 'online' &&
|
||||||
|
isRealInstance(ref.location) &&
|
||||||
|
ref.$lastFetch < now() - 10000 // 10 seconds
|
||||||
|
) {
|
||||||
|
console.log(`Fetching offline friend in an instance ${ctx.name}`);
|
||||||
|
userRequest.getUser({ userId: id });
|
||||||
|
}
|
||||||
|
// wtf, fetch user if online in an offline location
|
||||||
|
if (
|
||||||
|
currentState === 'online' &&
|
||||||
|
ref.location === 'offline' &&
|
||||||
|
ref.$lastFetch < now() - 10000 // 10 seconds
|
||||||
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`${ctx.name} updateFriendState ${ctx.state} -> ${newState}`
|
`Fetching online friend in an offline location ${ctx.name}`
|
||||||
);
|
);
|
||||||
if (
|
userRequest.getUser({ userId: id });
|
||||||
typeof ctx.ref !== 'undefined' &&
|
|
||||||
location !== ctx.ref.location
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`${ctx.name} pendingOfflineLocation ${location} -> ${ctx.ref.location}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!friends.has(id)) {
|
}
|
||||||
console.log('Friend not found', id);
|
if (typeof stateInput === 'undefined' || ctx.state === stateInput) {
|
||||||
return;
|
// this is should be: undefined -> user
|
||||||
}
|
if (ctx.ref !== ref) {
|
||||||
const isVIP = localFavoriteFriends.has(id);
|
ctx.ref = ref;
|
||||||
const ref = ctx.ref;
|
// NOTE
|
||||||
if (ctx.state !== newState && typeof ctx.ref !== 'undefined') {
|
// AddFriend (CurrentUser) 이후,
|
||||||
if (
|
// 서버에서 오는 순서라고 보면 될 듯.
|
||||||
(newState === 'offline' || newState === 'active') &&
|
if (ctx.state === 'online') {
|
||||||
ctx.state === 'online'
|
if (watchState.isFriendsLoaded) {
|
||||||
) {
|
userRequest.getUser({ userId: id });
|
||||||
ctx.ref.$online_for = '';
|
|
||||||
ctx.ref.$offline_for = now();
|
|
||||||
ctx.ref.$active_for = '';
|
|
||||||
if (newState === 'active') {
|
|
||||||
ctx.ref.$active_for = now();
|
|
||||||
}
|
}
|
||||||
const ts = now();
|
|
||||||
const time = ts - $location_at;
|
|
||||||
worldName = await getWorldName(location);
|
|
||||||
groupName = await getGroupName(location);
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'Offline',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
location,
|
|
||||||
worldName,
|
|
||||||
groupName,
|
|
||||||
time
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addOnlineOfflineToDatabase(feed);
|
|
||||||
} else if (
|
|
||||||
newState === 'online' &&
|
|
||||||
(ctx.state === 'offline' || ctx.state === 'active')
|
|
||||||
) {
|
|
||||||
ctx.ref.$previousLocation = '';
|
|
||||||
ctx.ref.$travelingToTime = now();
|
|
||||||
ctx.ref.$location_at = now();
|
|
||||||
ctx.ref.$online_for = now();
|
|
||||||
ctx.ref.$offline_for = '';
|
|
||||||
ctx.ref.$active_for = '';
|
|
||||||
worldName = await getWorldName(location);
|
|
||||||
groupName = await getGroupName(location);
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'Online',
|
|
||||||
userId: id,
|
|
||||||
displayName: ctx.name,
|
|
||||||
location,
|
|
||||||
worldName,
|
|
||||||
groupName,
|
|
||||||
time: ''
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addOnlineOfflineToDatabase(feed);
|
|
||||||
}
|
|
||||||
if (newState === 'active') {
|
|
||||||
ctx.ref.$active_for = now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ctx.state !== newState) {
|
if (ctx.isVIP !== isVIP) {
|
||||||
ctx.state = newState;
|
ctx.isVIP = isVIP;
|
||||||
updateOnlineFriendCounter();
|
|
||||||
}
|
}
|
||||||
if (ref?.displayName) {
|
if (typeof ref !== 'undefined' && ctx.name !== ref.displayName) {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
}
|
}
|
||||||
ctx.isVIP = isVIP;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
/**
|
ctx.state === 'online' &&
|
||||||
* Handles immediate friend presence updates and pending-offline orchestration.
|
(stateInput === 'active' || stateInput === 'offline')
|
||||||
* @param {string} id Friend id.
|
) {
|
||||||
* @param {string | undefined} stateInput Optional incoming state.
|
|
||||||
*/
|
|
||||||
async function runUpdateFriendFlow(id, stateInput = undefined) {
|
|
||||||
const ctx = friends.get(id);
|
|
||||||
if (typeof ctx === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ref = getCachedUsers().get(id);
|
|
||||||
|
|
||||||
if (stateInput === 'online') {
|
|
||||||
const pendingOffline = pendingOfflineMap.get(id);
|
|
||||||
if (appDebug.debugFriendState && pendingOffline) {
|
|
||||||
const time = (now() - pendingOffline.startTime) / 1000;
|
|
||||||
console.log(`${ctx.name} pendingOfflineCancelTime ${time}`);
|
|
||||||
}
|
|
||||||
ctx.pendingOffline = false;
|
|
||||||
pendingOfflineMap.delete(id);
|
|
||||||
}
|
|
||||||
const isVIP = localFavoriteFriends.has(id);
|
|
||||||
let location = '';
|
|
||||||
let $location_at = undefined;
|
|
||||||
if (typeof ref !== 'undefined') {
|
|
||||||
location = ref.location;
|
|
||||||
$location_at = ref.$location_at;
|
|
||||||
|
|
||||||
const currentState = stateInput || ctx.state;
|
|
||||||
// wtf, fetch user if offline in an instance
|
|
||||||
if (
|
|
||||||
currentState !== 'online' &&
|
|
||||||
isRealInstance(ref.location) &&
|
|
||||||
ref.$lastFetch < now() - 10000 // 10 seconds
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`Fetching offline friend in an instance ${ctx.name}`
|
|
||||||
);
|
|
||||||
requestUser(id);
|
|
||||||
}
|
|
||||||
// wtf, fetch user if online in an offline location
|
|
||||||
if (
|
|
||||||
currentState === 'online' &&
|
|
||||||
ref.location === 'offline' &&
|
|
||||||
ref.$lastFetch < now() - 10000 // 10 seconds
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`Fetching online friend in an offline location ${ctx.name}`
|
|
||||||
);
|
|
||||||
requestUser(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof stateInput === 'undefined' || ctx.state === stateInput) {
|
|
||||||
// this is should be: undefined -> user
|
|
||||||
if (ctx.ref !== ref) {
|
|
||||||
ctx.ref = ref;
|
|
||||||
// NOTE
|
|
||||||
// AddFriend (CurrentUser) 이후,
|
|
||||||
// 서버에서 오는 순서라고 보면 될 듯.
|
|
||||||
if (ctx.state === 'online') {
|
|
||||||
if (watchState.isFriendsLoaded) {
|
|
||||||
requestUser(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ctx.isVIP !== isVIP) {
|
|
||||||
ctx.isVIP = isVIP;
|
|
||||||
}
|
|
||||||
if (typeof ref !== 'undefined' && ctx.name !== ref.displayName) {
|
|
||||||
ctx.name = ref.displayName;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
ctx.state === 'online' &&
|
|
||||||
(stateInput === 'active' || stateInput === 'offline')
|
|
||||||
) {
|
|
||||||
ctx.ref = ref;
|
|
||||||
ctx.isVIP = isVIP;
|
|
||||||
if (typeof ref !== 'undefined') {
|
|
||||||
ctx.name = ref.displayName;
|
|
||||||
}
|
|
||||||
if (!watchState.isFriendsLoaded) {
|
|
||||||
await runUpdateFriendDelayedCheckFlow(
|
|
||||||
ctx,
|
|
||||||
stateInput,
|
|
||||||
location,
|
|
||||||
$location_at
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// prevent status flapping
|
|
||||||
if (pendingOfflineMap.has(id)) {
|
|
||||||
if (appDebug.debugFriendState) {
|
|
||||||
console.log(ctx.name, 'pendingOfflineAlreadyWaiting');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (appDebug.debugFriendState) {
|
|
||||||
console.log(ctx.name, 'pendingOfflineBegin');
|
|
||||||
}
|
|
||||||
pendingOfflineMap.set(id, {
|
|
||||||
startTime: now(),
|
|
||||||
newState: stateInput,
|
|
||||||
previousLocation: location,
|
|
||||||
previousLocationAt: $location_at
|
|
||||||
});
|
|
||||||
ctx.pendingOffline = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.ref = ref;
|
ctx.ref = ref;
|
||||||
ctx.isVIP = isVIP;
|
ctx.isVIP = isVIP;
|
||||||
if (typeof ref !== 'undefined') {
|
if (typeof ref !== 'undefined') {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
|
}
|
||||||
|
if (!watchState.isFriendsLoaded) {
|
||||||
await runUpdateFriendDelayedCheckFlow(
|
await runUpdateFriendDelayedCheckFlow(
|
||||||
ctx,
|
ctx,
|
||||||
ctx.ref.state,
|
stateInput,
|
||||||
location,
|
location,
|
||||||
$location_at
|
$location_at,
|
||||||
|
{ now, nowIso }
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// prevent status flapping
|
||||||
|
if (pendingOfflineMap.has(id)) {
|
||||||
|
if (AppDebug.debugFriendState) {
|
||||||
|
console.log(ctx.name, 'pendingOfflineAlreadyWaiting');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (AppDebug.debugFriendState) {
|
||||||
|
console.log(ctx.name, 'pendingOfflineBegin');
|
||||||
|
}
|
||||||
|
pendingOfflineMap.set(id, {
|
||||||
|
startTime: now(),
|
||||||
|
newState: stateInput,
|
||||||
|
previousLocation: location,
|
||||||
|
previousLocationAt: $location_at
|
||||||
|
});
|
||||||
|
ctx.pendingOffline = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.ref = ref;
|
||||||
|
ctx.isVIP = isVIP;
|
||||||
|
if (typeof ref !== 'undefined') {
|
||||||
|
ctx.name = ref.displayName;
|
||||||
|
await runUpdateFriendDelayedCheckFlow(
|
||||||
|
ctx,
|
||||||
|
ctx.ref.state,
|
||||||
|
location,
|
||||||
|
$location_at,
|
||||||
|
{ now, nowIso }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes pending-offline entries and executes delayed transitions.
|
||||||
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
|
*/
|
||||||
|
export async function runPendingOfflineTickFlow({
|
||||||
|
now = Date.now,
|
||||||
|
nowIso = () => new Date().toJSON()
|
||||||
|
} = {}) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const { friends, pendingOfflineMap, pendingOfflineDelay } = friendStore;
|
||||||
|
|
||||||
|
const currentTime = now();
|
||||||
|
for (const [id, pending] of pendingOfflineMap.entries()) {
|
||||||
|
if (currentTime - pending.startTime >= pendingOfflineDelay) {
|
||||||
|
const ctx = friends.get(id);
|
||||||
|
if (typeof ctx === 'undefined') {
|
||||||
|
pendingOfflineMap.delete(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ctx.pendingOffline = false;
|
||||||
|
if (pending.newState === ctx.state) {
|
||||||
|
console.error(
|
||||||
|
ctx.name,
|
||||||
|
'pendingOfflineCancelledStateMatched, this should never happen'
|
||||||
|
);
|
||||||
|
pendingOfflineMap.delete(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (AppDebug.debugFriendState) {
|
||||||
|
console.log(ctx.name, 'pendingOfflineEnd');
|
||||||
|
}
|
||||||
|
pendingOfflineMap.delete(id);
|
||||||
|
await runUpdateFriendDelayedCheckFlow(
|
||||||
|
ctx,
|
||||||
|
pending.newState,
|
||||||
|
pending.previousLocation,
|
||||||
|
pending.previousLocationAt,
|
||||||
|
{ now, nowIso }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes pending-offline entries and executes delayed transitions.
|
|
||||||
*/
|
|
||||||
async function runPendingOfflineTickFlow() {
|
|
||||||
const currentTime = now();
|
|
||||||
for (const [id, pending] of pendingOfflineMap.entries()) {
|
|
||||||
if (currentTime - pending.startTime >= pendingOfflineDelay) {
|
|
||||||
const ctx = friends.get(id);
|
|
||||||
if (typeof ctx === 'undefined') {
|
|
||||||
pendingOfflineMap.delete(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ctx.pendingOffline = false;
|
|
||||||
if (pending.newState === ctx.state) {
|
|
||||||
console.error(
|
|
||||||
ctx.name,
|
|
||||||
'pendingOfflineCancelledStateMatched, this should never happen'
|
|
||||||
);
|
|
||||||
pendingOfflineMap.delete(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (appDebug.debugFriendState) {
|
|
||||||
console.log(ctx.name, 'pendingOfflineEnd');
|
|
||||||
}
|
|
||||||
pendingOfflineMap.delete(id);
|
|
||||||
await runUpdateFriendDelayedCheckFlow(
|
|
||||||
ctx,
|
|
||||||
pending.newState,
|
|
||||||
pending.previousLocation,
|
|
||||||
pending.previousLocationAt
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
runUpdateFriendFlow,
|
|
||||||
runUpdateFriendDelayedCheckFlow,
|
|
||||||
runPendingOfflineTickFlow
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,48 @@
|
|||||||
/**
|
import { database } from '../service/database';
|
||||||
* @param {object} deps Coordinator dependencies.
|
import { friendRequest } from '../api';
|
||||||
* @returns {object} Friend relationship coordinator methods.
|
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
||||||
*/
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
export function createFriendRelationshipCoordinator(deps) {
|
import { useFriendStore } from '../stores/friend';
|
||||||
const {
|
import { useNotificationStore } from '../stores/notification';
|
||||||
friendLog,
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
friendLogTable,
|
import { useUiStore } from '../stores/ui';
|
||||||
getCurrentUserId,
|
import { useUserStore } from '../stores/user';
|
||||||
requestFriendStatus,
|
|
||||||
handleFriendStatus,
|
|
||||||
addFriendship,
|
|
||||||
deleteFriend,
|
|
||||||
database,
|
|
||||||
notificationStore,
|
|
||||||
sharedFeedStore,
|
|
||||||
favoriteStore,
|
|
||||||
uiStore,
|
|
||||||
shouldNotifyUnfriend,
|
|
||||||
nowIso
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and applies unfriend transition side effects.
|
* Validates and applies unfriend transition side effects.
|
||||||
* @param {string} id User id.
|
* @param {string} id User id.
|
||||||
*/
|
* @param {object} [options] Test seams.
|
||||||
function runDeleteFriendshipFlow(id) {
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
const ctx = friendLog.get(id);
|
*/
|
||||||
if (typeof ctx === 'undefined') {
|
export function runDeleteFriendshipFlow(
|
||||||
return;
|
id,
|
||||||
}
|
{ nowIso = () => new Date().toJSON() } = {}
|
||||||
requestFriendStatus({
|
) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
|
|
||||||
|
const { friendLog, friendLogTable } = friendStore;
|
||||||
|
|
||||||
|
const ctx = friendLog.get(id);
|
||||||
|
if (typeof ctx === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
friendRequest
|
||||||
|
.getFriendStatus({
|
||||||
userId: id,
|
userId: id,
|
||||||
currentUserId: getCurrentUserId()
|
currentUserId: userStore.currentUser.id
|
||||||
}).then((args) => {
|
})
|
||||||
if (args.params.currentUserId !== getCurrentUserId()) {
|
.then((args) => {
|
||||||
|
if (args.params.currentUserId !== userStore.currentUser.id) {
|
||||||
// safety check for delayed response
|
// safety check for delayed response
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleFriendStatus(args);
|
friendStore.handleFriendStatus(args);
|
||||||
if (!args.json.isFriend && friendLog.has(id)) {
|
if (!args.json.isFriend && friendLog.has(id)) {
|
||||||
const friendLogHistory = {
|
const friendLogHistory = {
|
||||||
created_at: nowIso(),
|
created_at: nowIso(),
|
||||||
@@ -45,44 +50,47 @@ export function createFriendRelationshipCoordinator(deps) {
|
|||||||
userId: id,
|
userId: id,
|
||||||
displayName: ctx.displayName || id
|
displayName: ctx.displayName || id
|
||||||
};
|
};
|
||||||
friendLogTable.value.data.push(friendLogHistory);
|
friendLogTable.data.push(friendLogHistory);
|
||||||
database.addFriendLogHistory(friendLogHistory);
|
database.addFriendLogHistory(friendLogHistory);
|
||||||
notificationStore.queueFriendLogNoty(friendLogHistory);
|
notificationStore.queueFriendLogNoty(friendLogHistory);
|
||||||
sharedFeedStore.addEntry(friendLogHistory);
|
sharedFeedStore.addEntry(friendLogHistory);
|
||||||
friendLog.delete(id);
|
friendLog.delete(id);
|
||||||
database.deleteFriendLogCurrent(id);
|
database.deleteFriendLogCurrent(id);
|
||||||
favoriteStore.handleFavoriteDelete(id);
|
favoriteStore.handleFavoriteDelete(id);
|
||||||
if (shouldNotifyUnfriend()) {
|
if (!appearanceSettingsStore.hideUnfriends) {
|
||||||
uiStore.notifyMenu('friend-log');
|
uiStore.notifyMenu('friend-log');
|
||||||
}
|
}
|
||||||
deleteFriend(id);
|
friendStore.deleteFriend(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconciles current friend list against local friend log.
|
* Reconciles current friend list against local friend log.
|
||||||
* @param {object} ref Current user reference.
|
* @param {object} ref Current user reference.
|
||||||
*/
|
* @param {object} [options] Test seams.
|
||||||
function runUpdateFriendshipsFlow(ref) {
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
let id;
|
*/
|
||||||
const set = new Set();
|
export function runUpdateFriendshipsFlow(
|
||||||
for (id of ref.friends) {
|
ref,
|
||||||
set.add(id);
|
{ nowIso = () => new Date().toJSON() } = {}
|
||||||
addFriendship(id);
|
) {
|
||||||
}
|
const friendStore = useFriendStore();
|
||||||
for (id of friendLog.keys()) {
|
const userStore = useUserStore();
|
||||||
if (id === getCurrentUserId()) {
|
const { friendLog } = friendStore;
|
||||||
friendLog.delete(id);
|
|
||||||
database.deleteFriendLogCurrent(id);
|
let id;
|
||||||
} else if (!set.has(id)) {
|
const set = new Set();
|
||||||
runDeleteFriendshipFlow(id);
|
for (id of ref.friends) {
|
||||||
}
|
set.add(id);
|
||||||
}
|
friendStore.addFriendship(id);
|
||||||
}
|
}
|
||||||
|
for (id of friendLog.keys()) {
|
||||||
return {
|
if (id === userStore.currentUser.id) {
|
||||||
runDeleteFriendshipFlow,
|
friendLog.delete(id);
|
||||||
runUpdateFriendshipsFlow
|
database.deleteFriendLogCurrent(id);
|
||||||
};
|
} else if (!set.has(id)) {
|
||||||
|
runDeleteFriendshipFlow(id, { nowIso });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +1,68 @@
|
|||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
|
import { AppDebug } from '../service/appConfig';
|
||||||
|
import { migrateMemos } from '../shared/utils';
|
||||||
|
import { reconnectWebSocket } from '../service/websocket';
|
||||||
|
import { useAuthStore } from '../stores/auth';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useUpdateLoopStore } from '../stores/updateLoop';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Runs friend list refresh orchestration.
|
||||||
* @returns {object} Friend sync coordinator methods.
|
|
||||||
*/
|
*/
|
||||||
export function createFriendSyncCoordinator(deps) {
|
export async function runRefreshFriendsListFlow() {
|
||||||
const {
|
const updateLoopStore = useUpdateLoopStore();
|
||||||
getNextCurrentUserRefresh,
|
const userStore = useUserStore();
|
||||||
getCurrentUser,
|
const friendStore = useFriendStore();
|
||||||
refreshFriends,
|
|
||||||
reconnectWebSocket,
|
|
||||||
getCurrentUserId,
|
|
||||||
getCurrentUserRef,
|
|
||||||
setRefreshFriendsLoading,
|
|
||||||
setFriendsLoaded,
|
|
||||||
resetFriendLog,
|
|
||||||
isFriendLogInitialized,
|
|
||||||
getFriendLog,
|
|
||||||
initFriendLog,
|
|
||||||
isDontLogMeOut,
|
|
||||||
showLoadFailedToast,
|
|
||||||
handleLogoutEvent,
|
|
||||||
tryApplyFriendOrder,
|
|
||||||
getAllUserStats,
|
|
||||||
hasLegacyFriendLogData,
|
|
||||||
removeLegacyFeedTable,
|
|
||||||
migrateMemos,
|
|
||||||
migrateFriendLog
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
// If we just got user less then 2 min before code call, don't call it again
|
||||||
* Runs friend list refresh orchestration.
|
if (updateLoopStore.nextCurrentUserRefresh < 300) {
|
||||||
*/
|
await userStore.getCurrentUser();
|
||||||
async function runRefreshFriendsListFlow() {
|
}
|
||||||
// If we just got user less then 2 min before code call, don't call it again
|
await friendStore.refreshFriends();
|
||||||
if (getNextCurrentUserRefresh() < 300) {
|
reconnectWebSocket();
|
||||||
await getCurrentUser();
|
}
|
||||||
}
|
|
||||||
await refreshFriends();
|
/**
|
||||||
reconnectWebSocket();
|
* Runs full friend list initialization orchestration.
|
||||||
|
* @param t
|
||||||
|
*/
|
||||||
|
export async function runInitFriendsListFlow(t) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const userId = userStore.currentUser.id;
|
||||||
|
friendStore.isRefreshFriendsLoading = true;
|
||||||
|
watchState.isFriendsLoaded = false;
|
||||||
|
friendStore.resetFriendLog();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentUser = userStore.currentUser;
|
||||||
|
if (await configRepository.getBool(`friendLogInit_${userId}`)) {
|
||||||
|
await friendStore.getFriendLog(currentUser);
|
||||||
|
} else {
|
||||||
|
await friendStore.initFriendLog(currentUser);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!AppDebug.dontLogMeOut) {
|
||||||
|
toast.error(t('message.friend.load_failed'));
|
||||||
|
authStore.handleLogoutEvent();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
friendStore.tryApplyFriendOrder(); // once again
|
||||||
|
friendStore.getAllUserStats(); // joinCount, lastSeen, timeSpent
|
||||||
|
|
||||||
|
// remove old data from json file and migrate to SQLite (July 2021)
|
||||||
|
if (await VRCXStorage.Get(`${userId}_friendLogUpdatedAt`)) {
|
||||||
|
VRCXStorage.Remove(`${userId}_feedTable`);
|
||||||
|
migrateMemos();
|
||||||
|
friendStore.migrateFriendLog(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs full friend list initialization orchestration.
|
|
||||||
*/
|
|
||||||
async function runInitFriendsListFlow() {
|
|
||||||
const userId = getCurrentUserId();
|
|
||||||
setRefreshFriendsLoading(true);
|
|
||||||
setFriendsLoaded(false);
|
|
||||||
resetFriendLog();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const currentUser = getCurrentUserRef();
|
|
||||||
if (await isFriendLogInitialized(userId)) {
|
|
||||||
await getFriendLog(currentUser);
|
|
||||||
} else {
|
|
||||||
await initFriendLog(currentUser);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!isDontLogMeOut()) {
|
|
||||||
showLoadFailedToast();
|
|
||||||
handleLogoutEvent();
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tryApplyFriendOrder(); // once again
|
|
||||||
getAllUserStats(); // joinCount, lastSeen, timeSpent
|
|
||||||
|
|
||||||
// remove old data from json file and migrate to SQLite (July 2021)
|
|
||||||
if (await hasLegacyFriendLogData(userId)) {
|
|
||||||
removeLegacyFeedTable(userId);
|
|
||||||
migrateMemos();
|
|
||||||
migrateFriendLog(userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
runRefreshFriendsListFlow,
|
|
||||||
runInitFriendsListFlow
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +1,45 @@
|
|||||||
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
|
import { useGameLogStore } from '../stores/gameLog';
|
||||||
|
import { useGameStore } from '../stores/game';
|
||||||
|
import { useInstanceStore } from '../stores/instance';
|
||||||
|
import { useLocationStore } from '../stores/location';
|
||||||
|
import { useUpdateLoopStore } from '../stores/updateLoop';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { useVrStore } from '../stores/vr';
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
|
|
||||||
|
import * as workerTimers from 'worker-timers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Runs shared side effects when game running state changes.
|
||||||
* @returns {object} Game flow coordinator methods.
|
* @param {boolean} isGameRunning Whether VRChat is running.
|
||||||
*/
|
*/
|
||||||
export function createGameCoordinator(deps) {
|
export async function runGameRunningChangedFlow(isGameRunning) {
|
||||||
const {
|
const userStore = useUserStore();
|
||||||
userStore,
|
const instanceStore = useInstanceStore();
|
||||||
instanceStore,
|
const updateLoopStore = useUpdateLoopStore();
|
||||||
updateLoopStore,
|
const locationStore = useLocationStore();
|
||||||
locationStore,
|
const gameLogStore = useGameLogStore();
|
||||||
gameLogStore,
|
const vrStore = useVrStore();
|
||||||
vrStore,
|
const avatarStore = useAvatarStore();
|
||||||
avatarStore,
|
const gameStore = useGameStore();
|
||||||
configRepository,
|
|
||||||
workerTimers,
|
|
||||||
checkVRChatDebugLogging,
|
|
||||||
autoVRChatCacheManagement,
|
|
||||||
checkIfGameCrashed,
|
|
||||||
getIsGameNoVR
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
if (isGameRunning) {
|
||||||
* Runs shared side effects when game running state changes.
|
userStore.markCurrentUserGameStarted();
|
||||||
* @param {boolean} isGameRunning Whether VRChat is running.
|
} else {
|
||||||
*/
|
await configRepository.setBool('isGameNoVR', gameStore.isGameNoVR);
|
||||||
async function runGameRunningChangedFlow(isGameRunning) {
|
userStore.markCurrentUserGameStopped();
|
||||||
if (isGameRunning) {
|
instanceStore.removeAllQueuedInstances();
|
||||||
userStore.markCurrentUserGameStarted();
|
gameStore.autoVRChatCacheManagement();
|
||||||
} else {
|
gameStore.checkIfGameCrashed();
|
||||||
await configRepository.setBool('isGameNoVR', getIsGameNoVR());
|
updateLoopStore.setIpcTimeout(0);
|
||||||
userStore.markCurrentUserGameStopped();
|
avatarStore.addAvatarWearTime(userStore.currentUser.currentAvatar);
|
||||||
instanceStore.removeAllQueuedInstances();
|
|
||||||
autoVRChatCacheManagement();
|
|
||||||
checkIfGameCrashed();
|
|
||||||
updateLoopStore.setIpcTimeout(0);
|
|
||||||
avatarStore.addAvatarWearTime(userStore.currentUser.currentAvatar);
|
|
||||||
}
|
|
||||||
|
|
||||||
locationStore.lastLocationReset();
|
|
||||||
gameLogStore.clearNowPlaying();
|
|
||||||
vrStore.updateVRLastLocation();
|
|
||||||
workerTimers.setTimeout(() => checkVRChatDebugLogging(), 60000);
|
|
||||||
updateLoopStore.setNextDiscordUpdate(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
locationStore.lastLocationReset();
|
||||||
runGameRunningChangedFlow
|
gameLogStore.clearNowPlaying();
|
||||||
};
|
vrStore.updateVRLastLocation();
|
||||||
|
workerTimers.setTimeout(() => gameStore.checkVRChatDebugLogging(), 60000);
|
||||||
|
updateLoopStore.setNextDiscordUpdate(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,334 +1,329 @@
|
|||||||
|
import { getGroupName, getWorldName, parseLocation } from '../shared/utils';
|
||||||
|
import { AppDebug } from '../service/appConfig';
|
||||||
|
import { database } from '../service/database';
|
||||||
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
|
import { useFeedStore } from '../stores/feed';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useGeneralSettingsStore } from '../stores/settings/general';
|
||||||
|
import { useGroupStore } from '../stores/group';
|
||||||
|
import { useInstanceStore } from '../stores/instance';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { useWorldStore } from '../stores/world';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Handles user diff events and applies cross-store side effects.
|
||||||
* @returns {object} User event coordinator methods.
|
* @param {object} ref Updated user reference.
|
||||||
|
* @param {object} props Changed props with [new, old] tuples.
|
||||||
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
|
* @param {function} [options.nowIso] ISO timestamp provider.
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
export function createUserEventCoordinator(deps) {
|
export async function runHandleUserUpdateFlow(
|
||||||
const {
|
ref,
|
||||||
friendStore,
|
props,
|
||||||
state,
|
{ now = Date.now, nowIso = () => new Date().toJSON() } = {}
|
||||||
parseLocation,
|
) {
|
||||||
userDialog,
|
const friendStore = useFriendStore();
|
||||||
applyUserDialogLocation,
|
const userStore = useUserStore();
|
||||||
worldStore,
|
const worldStore = useWorldStore();
|
||||||
groupStore,
|
const groupStore = useGroupStore();
|
||||||
instanceStore,
|
const instanceStore = useInstanceStore();
|
||||||
appDebug,
|
const feedStore = useFeedStore();
|
||||||
getWorldName,
|
const avatarStore = useAvatarStore();
|
||||||
getGroupName,
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
feedStore,
|
|
||||||
database,
|
|
||||||
avatarStore,
|
|
||||||
generalSettingsStore,
|
|
||||||
checkNote,
|
|
||||||
now,
|
|
||||||
nowIso
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
const { state, userDialog, applyUserDialogLocation, checkNote } = userStore;
|
||||||
* Handles user diff events and applies cross-store side effects.
|
|
||||||
* @param {object} ref Updated user reference.
|
|
||||||
* @param {object} props Changed props with [new, old] tuples.
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async function runHandleUserUpdateFlow(ref, props) {
|
|
||||||
let feed;
|
|
||||||
let newLocation;
|
|
||||||
let previousLocation;
|
|
||||||
const friend = friendStore.friends.get(ref.id);
|
|
||||||
if (typeof friend === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.location) {
|
|
||||||
// update instancePlayerCount
|
|
||||||
previousLocation = props.location[1];
|
|
||||||
newLocation = props.location[0];
|
|
||||||
let oldCount = state.instancePlayerCount.get(previousLocation);
|
|
||||||
if (typeof oldCount !== 'undefined') {
|
|
||||||
oldCount--;
|
|
||||||
if (oldCount <= 0) {
|
|
||||||
state.instancePlayerCount.delete(previousLocation);
|
|
||||||
} else {
|
|
||||||
state.instancePlayerCount.set(previousLocation, oldCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let newCount = state.instancePlayerCount.get(newLocation);
|
|
||||||
if (typeof newCount === 'undefined') {
|
|
||||||
newCount = 0;
|
|
||||||
}
|
|
||||||
newCount++;
|
|
||||||
state.instancePlayerCount.set(newLocation, newCount);
|
|
||||||
|
|
||||||
const previousLocationL = parseLocation(previousLocation);
|
let feed;
|
||||||
const newLocationL = parseLocation(newLocation);
|
let newLocation;
|
||||||
if (
|
let previousLocation;
|
||||||
previousLocationL.tag === userDialog.value.$location.tag ||
|
const friend = friendStore.friends.get(ref.id);
|
||||||
newLocationL.tag === userDialog.value.$location.tag
|
if (typeof friend === 'undefined') {
|
||||||
) {
|
return;
|
||||||
// update user dialog instance occupants
|
}
|
||||||
applyUserDialogLocation(true);
|
if (props.location) {
|
||||||
}
|
// update instancePlayerCount
|
||||||
if (
|
previousLocation = props.location[1];
|
||||||
previousLocationL.worldId === worldStore.worldDialog.id ||
|
newLocation = props.location[0];
|
||||||
newLocationL.worldId === worldStore.worldDialog.id
|
let oldCount = state.instancePlayerCount.get(previousLocation);
|
||||||
) {
|
if (typeof oldCount !== 'undefined') {
|
||||||
instanceStore.applyWorldDialogInstances();
|
oldCount--;
|
||||||
}
|
if (oldCount <= 0) {
|
||||||
if (
|
state.instancePlayerCount.delete(previousLocation);
|
||||||
previousLocationL.groupId === groupStore.groupDialog.id ||
|
|
||||||
newLocationL.groupId === groupStore.groupDialog.id
|
|
||||||
) {
|
|
||||||
instanceStore.applyGroupDialogInstances();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
!props.state &&
|
|
||||||
props.location &&
|
|
||||||
props.location[0] !== 'offline' &&
|
|
||||||
props.location[0] !== '' &&
|
|
||||||
props.location[1] !== 'offline' &&
|
|
||||||
props.location[1] !== '' &&
|
|
||||||
props.location[0] !== 'traveling'
|
|
||||||
) {
|
|
||||||
// skip GPS if user is offline or traveling
|
|
||||||
previousLocation = props.location[1];
|
|
||||||
newLocation = props.location[0];
|
|
||||||
let time = props.location[2];
|
|
||||||
if (previousLocation === 'traveling' && ref.$previousLocation) {
|
|
||||||
previousLocation = ref.$previousLocation;
|
|
||||||
const travelTime = now() - ref.$travelingToTime;
|
|
||||||
time -= travelTime;
|
|
||||||
if (time < 0) {
|
|
||||||
time = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (appDebug.debugFriendState && previousLocation) {
|
|
||||||
console.log(
|
|
||||||
`${ref.displayName} GPS ${previousLocation} -> ${newLocation}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (previousLocation === 'offline') {
|
|
||||||
previousLocation = '';
|
|
||||||
}
|
|
||||||
if (!previousLocation) {
|
|
||||||
// no previous location
|
|
||||||
if (appDebug.debugFriendState) {
|
|
||||||
console.log(
|
|
||||||
ref.displayName,
|
|
||||||
'Ignoring GPS, no previous location',
|
|
||||||
newLocation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if (ref.$previousLocation === newLocation) {
|
|
||||||
// location traveled to is the same
|
|
||||||
ref.$location_at = now() - time;
|
|
||||||
} else {
|
} else {
|
||||||
const worldName = await getWorldName(newLocation);
|
state.instancePlayerCount.set(previousLocation, oldCount);
|
||||||
const groupName = await getGroupName(newLocation);
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'GPS',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
location: newLocation,
|
|
||||||
worldName,
|
|
||||||
groupName,
|
|
||||||
previousLocation,
|
|
||||||
time
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addGPSToDatabase(feed);
|
|
||||||
// clear previousLocation after GPS
|
|
||||||
ref.$previousLocation = '';
|
|
||||||
ref.$travelingToTime = now();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let newCount = state.instancePlayerCount.get(newLocation);
|
||||||
|
if (typeof newCount === 'undefined') {
|
||||||
|
newCount = 0;
|
||||||
|
}
|
||||||
|
newCount++;
|
||||||
|
state.instancePlayerCount.set(newLocation, newCount);
|
||||||
|
|
||||||
|
const previousLocationL = parseLocation(previousLocation);
|
||||||
|
const newLocationL = parseLocation(newLocation);
|
||||||
|
if (
|
||||||
|
previousLocationL.tag === userDialog.$location.tag ||
|
||||||
|
newLocationL.tag === userDialog.$location.tag
|
||||||
|
) {
|
||||||
|
// update user dialog instance occupants
|
||||||
|
applyUserDialogLocation(true);
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
props.location &&
|
previousLocationL.worldId === worldStore.worldDialog.id ||
|
||||||
props.location[0] === 'traveling' &&
|
newLocationL.worldId === worldStore.worldDialog.id
|
||||||
props.location[1] !== 'traveling'
|
|
||||||
) {
|
) {
|
||||||
// store previous location when user is traveling
|
instanceStore.applyWorldDialogInstances();
|
||||||
ref.$previousLocation = props.location[1];
|
|
||||||
ref.$travelingToTime = now();
|
|
||||||
}
|
|
||||||
let imageMatches = false;
|
|
||||||
if (
|
|
||||||
props.currentAvatarThumbnailImageUrl &&
|
|
||||||
props.currentAvatarThumbnailImageUrl[0] &&
|
|
||||||
props.currentAvatarThumbnailImageUrl[1] &&
|
|
||||||
props.currentAvatarThumbnailImageUrl[0] ===
|
|
||||||
props.currentAvatarThumbnailImageUrl[1]
|
|
||||||
) {
|
|
||||||
imageMatches = true;
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(((props.currentAvatarImageUrl ||
|
previousLocationL.groupId === groupStore.groupDialog.id ||
|
||||||
props.currentAvatarThumbnailImageUrl) &&
|
newLocationL.groupId === groupStore.groupDialog.id
|
||||||
!ref.profilePicOverride) ||
|
|
||||||
props.currentAvatarTags) &&
|
|
||||||
!imageMatches
|
|
||||||
) {
|
) {
|
||||||
let currentAvatarImageUrl = '';
|
instanceStore.applyGroupDialogInstances();
|
||||||
let previousCurrentAvatarImageUrl = '';
|
|
||||||
let currentAvatarThumbnailImageUrl = '';
|
|
||||||
let previousCurrentAvatarThumbnailImageUrl = '';
|
|
||||||
let currentAvatarTags = '';
|
|
||||||
let previousCurrentAvatarTags = '';
|
|
||||||
if (props.currentAvatarImageUrl) {
|
|
||||||
currentAvatarImageUrl = props.currentAvatarImageUrl[0];
|
|
||||||
previousCurrentAvatarImageUrl = props.currentAvatarImageUrl[1];
|
|
||||||
} else {
|
|
||||||
currentAvatarImageUrl = ref.currentAvatarImageUrl;
|
|
||||||
previousCurrentAvatarImageUrl = ref.currentAvatarImageUrl;
|
|
||||||
}
|
|
||||||
if (props.currentAvatarThumbnailImageUrl) {
|
|
||||||
currentAvatarThumbnailImageUrl =
|
|
||||||
props.currentAvatarThumbnailImageUrl[0];
|
|
||||||
previousCurrentAvatarThumbnailImageUrl =
|
|
||||||
props.currentAvatarThumbnailImageUrl[1];
|
|
||||||
} else {
|
|
||||||
currentAvatarThumbnailImageUrl =
|
|
||||||
ref.currentAvatarThumbnailImageUrl;
|
|
||||||
previousCurrentAvatarThumbnailImageUrl =
|
|
||||||
ref.currentAvatarThumbnailImageUrl;
|
|
||||||
}
|
|
||||||
if (props.currentAvatarTags) {
|
|
||||||
currentAvatarTags = props.currentAvatarTags[0];
|
|
||||||
previousCurrentAvatarTags = props.currentAvatarTags[1];
|
|
||||||
if (
|
|
||||||
ref.profilePicOverride &&
|
|
||||||
!props.currentAvatarThumbnailImageUrl
|
|
||||||
) {
|
|
||||||
// forget last seen avatar
|
|
||||||
ref.currentAvatarImageUrl = '';
|
|
||||||
ref.currentAvatarThumbnailImageUrl = '';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
currentAvatarTags = ref.currentAvatarTags;
|
|
||||||
previousCurrentAvatarTags = ref.currentAvatarTags;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
generalSettingsStore.logEmptyAvatars ||
|
|
||||||
ref.currentAvatarImageUrl
|
|
||||||
) {
|
|
||||||
let avatarInfo = {
|
|
||||||
ownerId: '',
|
|
||||||
avatarName: ''
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
avatarInfo = await avatarStore.getAvatarName(
|
|
||||||
currentAvatarImageUrl
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
let previousAvatarInfo = {
|
|
||||||
ownerId: '',
|
|
||||||
avatarName: ''
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
previousAvatarInfo = await avatarStore.getAvatarName(
|
|
||||||
previousCurrentAvatarImageUrl
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'Avatar',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
ownerId: avatarInfo.ownerId,
|
|
||||||
previousOwnerId: previousAvatarInfo.ownerId,
|
|
||||||
avatarName: avatarInfo.avatarName,
|
|
||||||
previousAvatarName: previousAvatarInfo.avatarName,
|
|
||||||
currentAvatarImageUrl,
|
|
||||||
currentAvatarThumbnailImageUrl,
|
|
||||||
previousCurrentAvatarImageUrl,
|
|
||||||
previousCurrentAvatarThumbnailImageUrl,
|
|
||||||
currentAvatarTags,
|
|
||||||
previousCurrentAvatarTags
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addAvatarToDatabase(feed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if status is offline, ignore status and statusDescription
|
|
||||||
if (
|
|
||||||
(props.status &&
|
|
||||||
props.status[0] !== 'offline' &&
|
|
||||||
props.status[1] !== 'offline') ||
|
|
||||||
(!props.status && props.statusDescription)
|
|
||||||
) {
|
|
||||||
let status = '';
|
|
||||||
let previousStatus = '';
|
|
||||||
let statusDescription = '';
|
|
||||||
let previousStatusDescription = '';
|
|
||||||
if (props.status) {
|
|
||||||
if (props.status[0]) {
|
|
||||||
status = props.status[0];
|
|
||||||
}
|
|
||||||
if (props.status[1]) {
|
|
||||||
previousStatus = props.status[1];
|
|
||||||
}
|
|
||||||
} else if (ref.status) {
|
|
||||||
status = ref.status;
|
|
||||||
previousStatus = ref.status;
|
|
||||||
}
|
|
||||||
if (props.statusDescription) {
|
|
||||||
if (props.statusDescription[0]) {
|
|
||||||
statusDescription = props.statusDescription[0];
|
|
||||||
}
|
|
||||||
if (props.statusDescription[1]) {
|
|
||||||
previousStatusDescription = props.statusDescription[1];
|
|
||||||
}
|
|
||||||
} else if (ref.statusDescription) {
|
|
||||||
statusDescription = ref.statusDescription;
|
|
||||||
previousStatusDescription = ref.statusDescription;
|
|
||||||
}
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'Status',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
status,
|
|
||||||
statusDescription,
|
|
||||||
previousStatus,
|
|
||||||
previousStatusDescription
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addStatusToDatabase(feed);
|
|
||||||
}
|
|
||||||
if (props.bio && props.bio[0] && props.bio[1]) {
|
|
||||||
let bio = '';
|
|
||||||
let previousBio = '';
|
|
||||||
if (props.bio[0]) {
|
|
||||||
bio = props.bio[0];
|
|
||||||
}
|
|
||||||
if (props.bio[1]) {
|
|
||||||
previousBio = props.bio[1];
|
|
||||||
}
|
|
||||||
feed = {
|
|
||||||
created_at: nowIso(),
|
|
||||||
type: 'Bio',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
bio,
|
|
||||||
previousBio
|
|
||||||
};
|
|
||||||
feedStore.addFeed(feed);
|
|
||||||
database.addBioToDatabase(feed);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
props.note &&
|
|
||||||
props.note[0] !== null &&
|
|
||||||
props.note[0] !== props.note[1]
|
|
||||||
) {
|
|
||||||
checkNote(ref.id, props.note[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
return {
|
!props.state &&
|
||||||
runHandleUserUpdateFlow
|
props.location &&
|
||||||
};
|
props.location[0] !== 'offline' &&
|
||||||
|
props.location[0] !== '' &&
|
||||||
|
props.location[1] !== 'offline' &&
|
||||||
|
props.location[1] !== '' &&
|
||||||
|
props.location[0] !== 'traveling'
|
||||||
|
) {
|
||||||
|
// skip GPS if user is offline or traveling
|
||||||
|
previousLocation = props.location[1];
|
||||||
|
newLocation = props.location[0];
|
||||||
|
let time = props.location[2];
|
||||||
|
if (previousLocation === 'traveling' && ref.$previousLocation) {
|
||||||
|
previousLocation = ref.$previousLocation;
|
||||||
|
const travelTime = now() - ref.$travelingToTime;
|
||||||
|
time -= travelTime;
|
||||||
|
if (time < 0) {
|
||||||
|
time = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (AppDebug.debugFriendState && previousLocation) {
|
||||||
|
console.log(
|
||||||
|
`${ref.displayName} GPS ${previousLocation} -> ${newLocation}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (previousLocation === 'offline') {
|
||||||
|
previousLocation = '';
|
||||||
|
}
|
||||||
|
if (!previousLocation) {
|
||||||
|
// no previous location
|
||||||
|
if (AppDebug.debugFriendState) {
|
||||||
|
console.log(
|
||||||
|
ref.displayName,
|
||||||
|
'Ignoring GPS, no previous location',
|
||||||
|
newLocation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (ref.$previousLocation === newLocation) {
|
||||||
|
// location traveled to is the same
|
||||||
|
ref.$location_at = now() - time;
|
||||||
|
} else {
|
||||||
|
const worldName = await getWorldName(newLocation);
|
||||||
|
const groupName = await getGroupName(newLocation);
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'GPS',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
location: newLocation,
|
||||||
|
worldName,
|
||||||
|
groupName,
|
||||||
|
previousLocation,
|
||||||
|
time
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addGPSToDatabase(feed);
|
||||||
|
// clear previousLocation after GPS
|
||||||
|
ref.$previousLocation = '';
|
||||||
|
ref.$travelingToTime = now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
props.location &&
|
||||||
|
props.location[0] === 'traveling' &&
|
||||||
|
props.location[1] !== 'traveling'
|
||||||
|
) {
|
||||||
|
// store previous location when user is traveling
|
||||||
|
ref.$previousLocation = props.location[1];
|
||||||
|
ref.$travelingToTime = now();
|
||||||
|
}
|
||||||
|
let imageMatches = false;
|
||||||
|
if (
|
||||||
|
props.currentAvatarThumbnailImageUrl &&
|
||||||
|
props.currentAvatarThumbnailImageUrl[0] &&
|
||||||
|
props.currentAvatarThumbnailImageUrl[1] &&
|
||||||
|
props.currentAvatarThumbnailImageUrl[0] ===
|
||||||
|
props.currentAvatarThumbnailImageUrl[1]
|
||||||
|
) {
|
||||||
|
imageMatches = true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(((props.currentAvatarImageUrl ||
|
||||||
|
props.currentAvatarThumbnailImageUrl) &&
|
||||||
|
!ref.profilePicOverride) ||
|
||||||
|
props.currentAvatarTags) &&
|
||||||
|
!imageMatches
|
||||||
|
) {
|
||||||
|
let currentAvatarImageUrl = '';
|
||||||
|
let previousCurrentAvatarImageUrl = '';
|
||||||
|
let currentAvatarThumbnailImageUrl = '';
|
||||||
|
let previousCurrentAvatarThumbnailImageUrl = '';
|
||||||
|
let currentAvatarTags = '';
|
||||||
|
let previousCurrentAvatarTags = '';
|
||||||
|
if (props.currentAvatarImageUrl) {
|
||||||
|
currentAvatarImageUrl = props.currentAvatarImageUrl[0];
|
||||||
|
previousCurrentAvatarImageUrl = props.currentAvatarImageUrl[1];
|
||||||
|
} else {
|
||||||
|
currentAvatarImageUrl = ref.currentAvatarImageUrl;
|
||||||
|
previousCurrentAvatarImageUrl = ref.currentAvatarImageUrl;
|
||||||
|
}
|
||||||
|
if (props.currentAvatarThumbnailImageUrl) {
|
||||||
|
currentAvatarThumbnailImageUrl =
|
||||||
|
props.currentAvatarThumbnailImageUrl[0];
|
||||||
|
previousCurrentAvatarThumbnailImageUrl =
|
||||||
|
props.currentAvatarThumbnailImageUrl[1];
|
||||||
|
} else {
|
||||||
|
currentAvatarThumbnailImageUrl = ref.currentAvatarThumbnailImageUrl;
|
||||||
|
previousCurrentAvatarThumbnailImageUrl =
|
||||||
|
ref.currentAvatarThumbnailImageUrl;
|
||||||
|
}
|
||||||
|
if (props.currentAvatarTags) {
|
||||||
|
currentAvatarTags = props.currentAvatarTags[0];
|
||||||
|
previousCurrentAvatarTags = props.currentAvatarTags[1];
|
||||||
|
if (
|
||||||
|
ref.profilePicOverride &&
|
||||||
|
!props.currentAvatarThumbnailImageUrl
|
||||||
|
) {
|
||||||
|
// forget last seen avatar
|
||||||
|
ref.currentAvatarImageUrl = '';
|
||||||
|
ref.currentAvatarThumbnailImageUrl = '';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentAvatarTags = ref.currentAvatarTags;
|
||||||
|
previousCurrentAvatarTags = ref.currentAvatarTags;
|
||||||
|
}
|
||||||
|
if (generalSettingsStore.logEmptyAvatars || ref.currentAvatarImageUrl) {
|
||||||
|
let avatarInfo = {
|
||||||
|
ownerId: '',
|
||||||
|
avatarName: ''
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
avatarInfo = await avatarStore.getAvatarName(
|
||||||
|
currentAvatarImageUrl
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
let previousAvatarInfo = {
|
||||||
|
ownerId: '',
|
||||||
|
avatarName: ''
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
previousAvatarInfo = await avatarStore.getAvatarName(
|
||||||
|
previousCurrentAvatarImageUrl
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'Avatar',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
ownerId: avatarInfo.ownerId,
|
||||||
|
previousOwnerId: previousAvatarInfo.ownerId,
|
||||||
|
avatarName: avatarInfo.avatarName,
|
||||||
|
previousAvatarName: previousAvatarInfo.avatarName,
|
||||||
|
currentAvatarImageUrl,
|
||||||
|
currentAvatarThumbnailImageUrl,
|
||||||
|
previousCurrentAvatarImageUrl,
|
||||||
|
previousCurrentAvatarThumbnailImageUrl,
|
||||||
|
currentAvatarTags,
|
||||||
|
previousCurrentAvatarTags
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addAvatarToDatabase(feed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if status is offline, ignore status and statusDescription
|
||||||
|
if (
|
||||||
|
(props.status &&
|
||||||
|
props.status[0] !== 'offline' &&
|
||||||
|
props.status[1] !== 'offline') ||
|
||||||
|
(!props.status && props.statusDescription)
|
||||||
|
) {
|
||||||
|
let status = '';
|
||||||
|
let previousStatus = '';
|
||||||
|
let statusDescription = '';
|
||||||
|
let previousStatusDescription = '';
|
||||||
|
if (props.status) {
|
||||||
|
if (props.status[0]) {
|
||||||
|
status = props.status[0];
|
||||||
|
}
|
||||||
|
if (props.status[1]) {
|
||||||
|
previousStatus = props.status[1];
|
||||||
|
}
|
||||||
|
} else if (ref.status) {
|
||||||
|
status = ref.status;
|
||||||
|
previousStatus = ref.status;
|
||||||
|
}
|
||||||
|
if (props.statusDescription) {
|
||||||
|
if (props.statusDescription[0]) {
|
||||||
|
statusDescription = props.statusDescription[0];
|
||||||
|
}
|
||||||
|
if (props.statusDescription[1]) {
|
||||||
|
previousStatusDescription = props.statusDescription[1];
|
||||||
|
}
|
||||||
|
} else if (ref.statusDescription) {
|
||||||
|
statusDescription = ref.statusDescription;
|
||||||
|
previousStatusDescription = ref.statusDescription;
|
||||||
|
}
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'Status',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
status,
|
||||||
|
statusDescription,
|
||||||
|
previousStatus,
|
||||||
|
previousStatusDescription
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addStatusToDatabase(feed);
|
||||||
|
}
|
||||||
|
if (props.bio && props.bio[0] && props.bio[1]) {
|
||||||
|
let bio = '';
|
||||||
|
let previousBio = '';
|
||||||
|
if (props.bio[0]) {
|
||||||
|
bio = props.bio[0];
|
||||||
|
}
|
||||||
|
if (props.bio[1]) {
|
||||||
|
previousBio = props.bio[1];
|
||||||
|
}
|
||||||
|
feed = {
|
||||||
|
created_at: nowIso(),
|
||||||
|
type: 'Bio',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
bio,
|
||||||
|
previousBio
|
||||||
|
};
|
||||||
|
feedStore.addFeed(feed);
|
||||||
|
database.addBioToDatabase(feed);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
props.note &&
|
||||||
|
props.note[0] !== null &&
|
||||||
|
props.note[0] !== props.note[1]
|
||||||
|
) {
|
||||||
|
checkNote(ref.id, props.note[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,92 @@
|
|||||||
|
import { getWorldName, parseLocation } from '../shared/utils';
|
||||||
|
import { runUpdateFriendshipsFlow } from './friendRelationshipCoordinator';
|
||||||
|
import { useAuthStore } from '../stores/auth';
|
||||||
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useGameStore } from '../stores/game';
|
||||||
|
import { useGroupStore } from '../stores/group';
|
||||||
|
import { useInstanceStore } from '../stores/instance';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} deps Coordinator dependencies.
|
* Runs avatar transition side effects for current user updates.
|
||||||
* @returns {object} User session coordinator methods.
|
* @param {object} args Avatar transition context.
|
||||||
|
* @param {object} args.json Current user payload.
|
||||||
|
* @param {object} args.ref Current user state reference.
|
||||||
|
* @param {boolean} args.isLoggedIn Whether current user is already logged in.
|
||||||
|
* @param {object} [options] Test seams.
|
||||||
|
* @param {function} [options.now] Timestamp provider.
|
||||||
*/
|
*/
|
||||||
export function createUserSessionCoordinator(deps) {
|
export function runAvatarSwapFlow(
|
||||||
const {
|
{ json, ref, isLoggedIn },
|
||||||
avatarStore,
|
{ now = Date.now } = {}
|
||||||
gameStore,
|
) {
|
||||||
groupStore,
|
const avatarStore = useAvatarStore();
|
||||||
instanceStore,
|
const gameStore = useGameStore();
|
||||||
friendStore,
|
|
||||||
authStore,
|
|
||||||
cachedUsers,
|
|
||||||
currentUser,
|
|
||||||
userDialog,
|
|
||||||
getWorldName,
|
|
||||||
parseLocation,
|
|
||||||
now
|
|
||||||
} = deps;
|
|
||||||
|
|
||||||
/**
|
if (!isLoggedIn) {
|
||||||
* Runs avatar transition side effects for current user updates.
|
return;
|
||||||
* @param {object} args Avatar transition context.
|
|
||||||
* @param {object} args.json Current user payload.
|
|
||||||
* @param {object} args.ref Current user state reference.
|
|
||||||
* @param {boolean} args.isLoggedIn Whether current user is already logged in.
|
|
||||||
*/
|
|
||||||
function runAvatarSwapFlow({ json, ref, isLoggedIn }) {
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (json.currentAvatar !== ref.currentAvatar) {
|
|
||||||
avatarStore.addAvatarToHistory(json.currentAvatar);
|
|
||||||
if (gameStore.isGameRunning) {
|
|
||||||
avatarStore.addAvatarWearTime(ref.currentAvatar);
|
|
||||||
ref.$previousAvatarSwapTime = now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (json.currentAvatar !== ref.currentAvatar) {
|
||||||
/**
|
avatarStore.addAvatarToHistory(json.currentAvatar);
|
||||||
* Runs one-time side effects for first current-user hydration after login.
|
|
||||||
* @param {object} ref Current user state reference.
|
|
||||||
*/
|
|
||||||
function runFirstLoginFlow(ref) {
|
|
||||||
if (gameStore.isGameRunning) {
|
if (gameStore.isGameRunning) {
|
||||||
|
avatarStore.addAvatarWearTime(ref.currentAvatar);
|
||||||
ref.$previousAvatarSwapTime = now();
|
ref.$previousAvatarSwapTime = now();
|
||||||
}
|
}
|
||||||
cachedUsers.clear(); // clear before running applyUser
|
|
||||||
currentUser.value = ref;
|
|
||||||
authStore.loginComplete();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Runs cross-store synchronization after current-user data is applied.
|
/**
|
||||||
* @param {object} ref Current user state reference.
|
* Runs one-time side effects for first current-user hydration after login.
|
||||||
*/
|
* @param {object} ref Current user state reference.
|
||||||
function runPostApplySyncFlow(ref) {
|
* @param {object} [options] Test seams.
|
||||||
groupStore.applyPresenceGroups(ref);
|
* @param {function} [options.now] Timestamp provider.
|
||||||
instanceStore.applyQueuedInstance(ref.queuedInstance);
|
*/
|
||||||
friendStore.updateUserCurrentStatus(ref);
|
export function runFirstLoginFlow(ref, { now = Date.now } = {}) {
|
||||||
friendStore.updateFriendships(ref);
|
const gameStore = useGameStore();
|
||||||
}
|
const authStore = useAuthStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
/**
|
|
||||||
* Syncs home location derived state and visible dialog display name.
|
if (gameStore.isGameRunning) {
|
||||||
* @param {object} ref Current user state reference.
|
ref.$previousAvatarSwapTime = now();
|
||||||
*/
|
}
|
||||||
function runHomeLocationSyncFlow(ref) {
|
userStore.cachedUsers.clear(); // clear before running applyUser
|
||||||
if (ref.homeLocation === ref.$homeLocation?.tag) {
|
userStore.currentUser = ref;
|
||||||
return;
|
authStore.loginComplete();
|
||||||
}
|
}
|
||||||
ref.$homeLocation = parseLocation(ref.homeLocation);
|
|
||||||
// apply home location name to user dialog
|
/**
|
||||||
if (userDialog.value.visible && userDialog.value.id === ref.id) {
|
* Runs cross-store synchronization after current-user data is applied.
|
||||||
getWorldName(currentUser.value.homeLocation).then((worldName) => {
|
* @param {object} ref Current user state reference.
|
||||||
userDialog.value.$homeLocationName = worldName;
|
*/
|
||||||
});
|
export function runPostApplySyncFlow(ref) {
|
||||||
}
|
const groupStore = useGroupStore();
|
||||||
}
|
const instanceStore = useInstanceStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
return {
|
|
||||||
runAvatarSwapFlow,
|
groupStore.applyPresenceGroups(ref);
|
||||||
runFirstLoginFlow,
|
instanceStore.applyQueuedInstance(ref.queuedInstance);
|
||||||
runPostApplySyncFlow,
|
friendStore.updateUserCurrentStatus(ref);
|
||||||
runHomeLocationSyncFlow
|
if (typeof ref.friends !== 'undefined') {
|
||||||
};
|
runUpdateFriendshipsFlow(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs home location derived state and visible dialog display name.
|
||||||
|
* @param {object} ref Current user state reference.
|
||||||
|
*/
|
||||||
|
export function runHomeLocationSyncFlow(ref) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
if (ref.homeLocation === ref.$homeLocation?.tag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ref.$homeLocation = parseLocation(ref.homeLocation);
|
||||||
|
// apply home location name to user dialog
|
||||||
|
if (userStore.userDialog.visible && userStore.userDialog.id === ref.id) {
|
||||||
|
getWorldName(userStore.currentUser.homeLocation).then((worldName) => {
|
||||||
|
userStore.userDialog.$homeLocationName = worldName;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +16,7 @@ import { escapeTag, parseLocation } from '../shared/utils';
|
|||||||
import { AppDebug } from './appConfig';
|
import { AppDebug } from './appConfig';
|
||||||
import { groupRequest } from '../api';
|
import { groupRequest } from '../api';
|
||||||
import { request } from './request';
|
import { request } from './request';
|
||||||
|
import { runUpdateFriendFlow } from '../coordinators/friendPresenceCoordinator';
|
||||||
import { watchState } from './watchState';
|
import { watchState } from './watchState';
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
@@ -300,7 +300,7 @@ function handlePipeline(args) {
|
|||||||
userStore.applyUser(onlineJson);
|
userStore.applyUser(onlineJson);
|
||||||
} else {
|
} else {
|
||||||
console.error('friend-online missing user id', content);
|
console.error('friend-online missing user id', content);
|
||||||
friendStore.updateFriend(content.userId, 'online');
|
runUpdateFriendFlow(content.userId, 'online');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ function handlePipeline(args) {
|
|||||||
userStore.applyUser(activeJson);
|
userStore.applyUser(activeJson);
|
||||||
} else {
|
} else {
|
||||||
console.error('friend-active missing user id', content);
|
console.error('friend-active missing user id', content);
|
||||||
friendStore.updateFriend(content.userId, 'active');
|
runUpdateFriendFlow(content.userId, 'active');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ import { useI18n } from 'vue-i18n';
|
|||||||
|
|
||||||
import Noty from 'noty';
|
import Noty from 'noty';
|
||||||
|
|
||||||
import { closeWebSocket, initWebsocket } from '../service/websocket';
|
import {
|
||||||
|
runLoginSuccessFlow,
|
||||||
|
runLogoutFlow
|
||||||
|
} from '../coordinators/authCoordinator';
|
||||||
import { AppDebug } from '../service/appConfig';
|
import { AppDebug } from '../service/appConfig';
|
||||||
import { authRequest } from '../api';
|
import { authRequest } from '../api';
|
||||||
import { createAuthAutoLoginCoordinator } from '../coordinators/authAutoLoginCoordinator';
|
|
||||||
import { createAuthCoordinator } from '../coordinators/authCoordinator';
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { escapeTag } from '../shared/utils';
|
import { escapeTag } from '../shared/utils';
|
||||||
import { queryClient } from '../queries';
|
import { initWebsocket } from '../service/websocket';
|
||||||
import { request } from '../service/request';
|
import { request } from '../service/request';
|
||||||
|
import { runHandleAutoLoginFlow } from '../coordinators/authAutoLoginCoordinator';
|
||||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||||
import { useGeneralSettingsStore } from './settings/general';
|
import { useGeneralSettingsStore } from './settings/general';
|
||||||
import { useModalStore } from './modal';
|
import { useModalStore } from './modal';
|
||||||
import { useNotificationStore } from './notification';
|
|
||||||
import { useUpdateLoopStore } from './updateLoop';
|
import { useUpdateLoopStore } from './updateLoop';
|
||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
@@ -31,7 +32,6 @@ import * as workerTimers from 'worker-timers';
|
|||||||
export const useAuthStore = defineStore('Auth', () => {
|
export const useAuthStore = defineStore('Auth', () => {
|
||||||
const advancedSettingsStore = useAdvancedSettingsStore();
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||||
const generalSettingsStore = useGeneralSettingsStore();
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
const notificationStore = useNotificationStore();
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const updateLoopStore = useUpdateLoopStore();
|
const updateLoopStore = useUpdateLoopStore();
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
@@ -168,15 +168,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function handleLogoutEvent() {
|
async function handleLogoutEvent() {
|
||||||
if (watchState.isLoggedIn) {
|
await runLogoutFlow();
|
||||||
new Noty({
|
|
||||||
type: 'success',
|
|
||||||
text: t('message.auth.logout_greeting', {
|
|
||||||
name: `<strong>${escapeTag(userStore.currentUser.displayName)}</strong>`
|
|
||||||
})
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
await authCoordinator.runLogoutFlow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -828,7 +820,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
} else if (json.requiresTwoFactorAuth) {
|
} else if (json.requiresTwoFactorAuth) {
|
||||||
promptTOTP();
|
promptTOTP();
|
||||||
} else {
|
} else {
|
||||||
authCoordinator.runLoginSuccessFlow(json);
|
runLoginSuccessFlow(json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,7 +828,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function handleAutoLogin() {
|
async function handleAutoLogin() {
|
||||||
await authAutoLoginCoordinator.runHandleAutoLoginFlow();
|
await runHandleAutoLoginFlow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -894,56 +886,6 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
attemptingAutoLogin.value = value;
|
attemptingAutoLogin.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const authAutoLoginCoordinator = createAuthAutoLoginCoordinator({
|
|
||||||
getIsAttemptingAutoLogin: () => attemptingAutoLogin.value,
|
|
||||||
setAttemptingAutoLogin,
|
|
||||||
getLastUserLoggedIn: () => loginForm.value.lastUserLoggedIn,
|
|
||||||
getSavedCredentials,
|
|
||||||
isPrimaryPasswordEnabled: () =>
|
|
||||||
advancedSettingsStore.enablePrimaryPassword,
|
|
||||||
handleLogoutEvent,
|
|
||||||
autoLoginAttempts: state.autoLoginAttempts,
|
|
||||||
relogin,
|
|
||||||
notifyAutoLoginSuccess: () => {
|
|
||||||
if (AppDebug.errorNoty) {
|
|
||||||
toast.dismiss(AppDebug.errorNoty);
|
|
||||||
}
|
|
||||||
AppDebug.errorNoty = toast.success(
|
|
||||||
t('message.auth.auto_login_success')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
notifyAutoLoginFailed: () => {
|
|
||||||
if (AppDebug.errorNoty) {
|
|
||||||
toast.dismiss(AppDebug.errorNoty);
|
|
||||||
}
|
|
||||||
AppDebug.errorNoty = toast.error(
|
|
||||||
t('message.auth.auto_login_failed')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
notifyOffline: () => {
|
|
||||||
AppDebug.errorNoty = toast.error(t('message.auth.offline'));
|
|
||||||
},
|
|
||||||
flashWindow: () => AppApi.FlashWindow(),
|
|
||||||
isOnline: () => navigator.onLine,
|
|
||||||
now: () => Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
const authCoordinator = createAuthCoordinator({
|
|
||||||
userStore,
|
|
||||||
notificationStore,
|
|
||||||
updateLoopStore,
|
|
||||||
initWebsocket,
|
|
||||||
updateStoredUser,
|
|
||||||
webApiService,
|
|
||||||
loginForm,
|
|
||||||
configRepository,
|
|
||||||
setAttemptingAutoLogin,
|
|
||||||
autoLoginAttempts: state.autoLoginAttempts,
|
|
||||||
closeWebSocket,
|
|
||||||
queryClient,
|
|
||||||
watchState
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
|
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { processBulk } from '../service/request';
|
import { processBulk } from '../service/request';
|
||||||
|
import { runUpdateFriendFlow } from '../coordinators/friendPresenceCoordinator';
|
||||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||||
import { useAvatarStore } from './avatar';
|
import { useAvatarStore } from './avatar';
|
||||||
import { useFriendStore } from './friend';
|
import { useFriendStore } from './friend';
|
||||||
@@ -339,7 +340,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
|||||||
function handleFavorite(args) {
|
function handleFavorite(args) {
|
||||||
args.ref = applyFavoriteCached(args.json);
|
args.ref = applyFavoriteCached(args.json);
|
||||||
applyFavorite(args.ref.type, args.ref.favoriteId);
|
applyFavorite(args.ref.type, args.ref.favoriteId);
|
||||||
friendStore.updateFriend(args.ref.favoriteId);
|
runUpdateFriendFlow(args.ref.favoriteId);
|
||||||
const { ref } = args;
|
const { ref } = args;
|
||||||
const userDialog = userStore.userDialog;
|
const userDialog = userStore.userDialog;
|
||||||
if (userDialog.visible && ref.favoriteId === userDialog.id) {
|
if (userDialog.visible && ref.favoriteId === userDialog.id) {
|
||||||
@@ -437,7 +438,7 @@ export const useFavoriteStore = defineStore('Favorite', () => {
|
|||||||
(id) => id !== ref.favoriteId
|
(id) => id !== ref.favoriteId
|
||||||
);
|
);
|
||||||
|
|
||||||
friendStore.updateFriend(ref.favoriteId);
|
runUpdateFriendFlow(ref.favoriteId);
|
||||||
friendStore.updateSidebarFavorites();
|
friendStore.updateSidebarFavorites();
|
||||||
const userDialog = userStore.userDialog;
|
const userDialog = userStore.userDialog;
|
||||||
if (userDialog.visible && userDialog.id === ref.favoriteId) {
|
if (userDialog.visible && userDialog.id === ref.favoriteId) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
@@ -9,24 +8,27 @@ import {
|
|||||||
createRateLimiter,
|
createRateLimiter,
|
||||||
executeWithBackoff,
|
executeWithBackoff,
|
||||||
getFriendsSortFunction,
|
getFriendsSortFunction,
|
||||||
getGroupName,
|
|
||||||
getNameColour,
|
getNameColour,
|
||||||
getUserMemo,
|
getUserMemo,
|
||||||
getWorldName,
|
isRealInstance
|
||||||
isRealInstance,
|
|
||||||
migrateMemos
|
|
||||||
} from '../shared/utils';
|
} from '../shared/utils';
|
||||||
import { friendRequest, userRequest } from '../api';
|
import { friendRequest, userRequest } from '../api';
|
||||||
|
import {
|
||||||
|
runDeleteFriendshipFlow,
|
||||||
|
runUpdateFriendshipsFlow
|
||||||
|
} from '../coordinators/friendRelationshipCoordinator';
|
||||||
|
import {
|
||||||
|
runInitFriendsListFlow,
|
||||||
|
runRefreshFriendsListFlow
|
||||||
|
} from '../coordinators/friendSyncCoordinator';
|
||||||
|
import {
|
||||||
|
runPendingOfflineTickFlow,
|
||||||
|
runUpdateFriendFlow
|
||||||
|
} from '../coordinators/friendPresenceCoordinator';
|
||||||
import { AppDebug } from '../service/appConfig';
|
import { AppDebug } from '../service/appConfig';
|
||||||
import { createFriendPresenceCoordinator } from '../coordinators/friendPresenceCoordinator';
|
|
||||||
import { createFriendRelationshipCoordinator } from '../coordinators/friendRelationshipCoordinator';
|
|
||||||
import { createFriendSyncCoordinator } from '../coordinators/friendSyncCoordinator';
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { reconnectWebSocket } from '../service/websocket';
|
|
||||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||||
import { useAuthStore } from './auth';
|
|
||||||
import { useFavoriteStore } from './favorite';
|
import { useFavoriteStore } from './favorite';
|
||||||
import { useFeedStore } from './feed';
|
|
||||||
import { useGeneralSettingsStore } from './settings/general';
|
import { useGeneralSettingsStore } from './settings/general';
|
||||||
import { useGroupStore } from './group';
|
import { useGroupStore } from './group';
|
||||||
import { useLocationStore } from './location';
|
import { useLocationStore } from './location';
|
||||||
@@ -34,7 +36,6 @@ import { useModalStore } from './modal';
|
|||||||
import { useNotificationStore } from './notification';
|
import { useNotificationStore } from './notification';
|
||||||
import { useSharedFeedStore } from './sharedFeed';
|
import { useSharedFeedStore } from './sharedFeed';
|
||||||
import { useUiStore } from './ui';
|
import { useUiStore } from './ui';
|
||||||
import { useUpdateLoopStore } from './updateLoop';
|
|
||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
@@ -47,12 +48,9 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
const generalSettingsStore = useGeneralSettingsStore();
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
const feedStore = useFeedStore();
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const groupStore = useGroupStore();
|
const groupStore = useGroupStore();
|
||||||
const sharedFeedStore = useSharedFeedStore();
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
const updateLoopStore = useUpdateLoopStore();
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const locationStore = useLocationStore();
|
const locationStore = useLocationStore();
|
||||||
const favoriteStore = useFavoriteStore();
|
const favoriteStore = useFavoriteStore();
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
@@ -64,7 +62,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
friendNumber: 0
|
friendNumber: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
let friendLog = new Map();
|
const friendLog = new Map();
|
||||||
|
|
||||||
const friends = reactive(new Map());
|
const friends = reactive(new Map());
|
||||||
|
|
||||||
@@ -237,7 +235,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
onlineFriendCount.value = 0;
|
onlineFriendCount.value = 0;
|
||||||
pendingOfflineMap.clear();
|
pendingOfflineMap.clear();
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
initFriendsList();
|
runInitFriendsListFlow(t);
|
||||||
pendingOfflineWorkerFunction();
|
pendingOfflineWorkerFunction();
|
||||||
} else {
|
} else {
|
||||||
if (pendingOfflineWorker !== null) {
|
if (pendingOfflineWorker !== null) {
|
||||||
@@ -313,7 +311,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
D.isFriend = false;
|
D.isFriend = false;
|
||||||
deleteFriendship(args.params.userId);
|
runDeleteFriendshipFlow(args.params.userId);
|
||||||
deleteFriend(args.params.userId);
|
deleteFriend(args.params.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,20 +402,12 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} id
|
|
||||||
* @param {string?} stateInput
|
|
||||||
*/
|
|
||||||
function updateFriend(id, stateInput = undefined) {
|
|
||||||
friendPresenceCoordinator.runUpdateFriendFlow(id, stateInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function pendingOfflineWorkerFunction() {
|
async function pendingOfflineWorkerFunction() {
|
||||||
pendingOfflineWorker = workerTimers.setInterval(() => {
|
pendingOfflineWorker = workerTimers.setInterval(() => {
|
||||||
friendPresenceCoordinator.runPendingOfflineTickFlow();
|
runPendingOfflineTickFlow();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,7 +444,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
for (const friend of map) {
|
for (const friend of map) {
|
||||||
const [id, state_input] = friend;
|
const [id, state_input] = friend;
|
||||||
if (friends.has(id)) {
|
if (friends.has(id)) {
|
||||||
updateFriend(id, state_input);
|
runUpdateFriendFlow(id, state_input);
|
||||||
} else {
|
} else {
|
||||||
addFriend(id, state_input);
|
addFriend(id, state_input);
|
||||||
}
|
}
|
||||||
@@ -698,10 +688,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
/**
|
/**
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function refreshFriendsList() {
|
|
||||||
await friendSyncCoordinator.runRefreshFriendsListFlow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param forceUpdate
|
* @param forceUpdate
|
||||||
@@ -895,22 +881,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
*/
|
|
||||||
function deleteFriendship(id) {
|
|
||||||
friendRelationshipCoordinator.runDeleteFriendshipFlow(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {object} ref
|
|
||||||
*/
|
|
||||||
function updateFriendships(ref) {
|
|
||||||
friendRelationshipCoordinator.runUpdateFriendshipsFlow(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} ref
|
* @param {object} ref
|
||||||
@@ -1075,7 +1045,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof currentUser.friends !== 'undefined') {
|
if (typeof currentUser.friends !== 'undefined') {
|
||||||
updateFriendships(currentUser);
|
runUpdateFriendshipsFlow(currentUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1440,91 +1410,14 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Clears all entries in friendLog.
|
||||||
|
* Uses .clear() instead of reassignment to keep the same Map reference,
|
||||||
|
* so that coordinators reading friendStore.friendLog stay in sync.
|
||||||
*/
|
*/
|
||||||
async function initFriendsList() {
|
function resetFriendLog() {
|
||||||
await friendSyncCoordinator.runInitFriendsListFlow();
|
friendLog.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
function setRefreshFriendsLoading(value) {
|
|
||||||
isRefreshFriendsLoading.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const friendPresenceCoordinator = createFriendPresenceCoordinator({
|
|
||||||
friends,
|
|
||||||
localFavoriteFriends,
|
|
||||||
pendingOfflineMap,
|
|
||||||
pendingOfflineDelay,
|
|
||||||
watchState,
|
|
||||||
appDebug: AppDebug,
|
|
||||||
getCachedUsers: () => userStore.cachedUsers,
|
|
||||||
isRealInstance,
|
|
||||||
requestUser: (userId) =>
|
|
||||||
userRequest.getUser({
|
|
||||||
userId
|
|
||||||
}),
|
|
||||||
getWorldName,
|
|
||||||
getGroupName,
|
|
||||||
feedStore,
|
|
||||||
database,
|
|
||||||
updateOnlineFriendCounter,
|
|
||||||
now: () => Date.now(),
|
|
||||||
nowIso: () => new Date().toJSON()
|
|
||||||
});
|
|
||||||
|
|
||||||
const friendRelationshipCoordinator = createFriendRelationshipCoordinator({
|
|
||||||
friendLog,
|
|
||||||
friendLogTable,
|
|
||||||
getCurrentUserId: () => userStore.currentUser.id,
|
|
||||||
requestFriendStatus: (params) => friendRequest.getFriendStatus(params),
|
|
||||||
handleFriendStatus,
|
|
||||||
addFriendship,
|
|
||||||
deleteFriend,
|
|
||||||
database,
|
|
||||||
notificationStore,
|
|
||||||
sharedFeedStore,
|
|
||||||
favoriteStore,
|
|
||||||
uiStore,
|
|
||||||
shouldNotifyUnfriend: () => !appearanceSettingsStore.hideUnfriends,
|
|
||||||
nowIso: () => new Date().toJSON()
|
|
||||||
});
|
|
||||||
|
|
||||||
const friendSyncCoordinator = createFriendSyncCoordinator({
|
|
||||||
getNextCurrentUserRefresh: () => updateLoopStore.nextCurrentUserRefresh,
|
|
||||||
getCurrentUser: () => userStore.getCurrentUser(),
|
|
||||||
refreshFriends,
|
|
||||||
reconnectWebSocket,
|
|
||||||
getCurrentUserId: () => userStore.currentUser.id,
|
|
||||||
getCurrentUserRef: () => userStore.currentUser,
|
|
||||||
setRefreshFriendsLoading: (value) => {
|
|
||||||
isRefreshFriendsLoading.value = value;
|
|
||||||
},
|
|
||||||
setFriendsLoaded: (value) => {
|
|
||||||
watchState.isFriendsLoaded = value;
|
|
||||||
},
|
|
||||||
resetFriendLog: () => {
|
|
||||||
friendLog = new Map();
|
|
||||||
},
|
|
||||||
isFriendLogInitialized: (userId) =>
|
|
||||||
configRepository.getBool(`friendLogInit_${userId}`),
|
|
||||||
getFriendLog,
|
|
||||||
initFriendLog,
|
|
||||||
isDontLogMeOut: () => AppDebug.dontLogMeOut,
|
|
||||||
showLoadFailedToast: () => toast.error(t('message.friend.load_failed')),
|
|
||||||
handleLogoutEvent: () => authStore.handleLogoutEvent(),
|
|
||||||
tryApplyFriendOrder,
|
|
||||||
getAllUserStats,
|
|
||||||
hasLegacyFriendLogData: (userId) =>
|
|
||||||
VRCXStorage.Get(`${userId}_friendLogUpdatedAt`),
|
|
||||||
removeLegacyFeedTable: (userId) =>
|
|
||||||
VRCXStorage.Remove(`${userId}_feedTable`),
|
|
||||||
migrateMemos,
|
|
||||||
migrateFriendLog
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
|
|
||||||
@@ -1543,16 +1436,15 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
onlineFriendCount,
|
onlineFriendCount,
|
||||||
friendLog,
|
friendLog,
|
||||||
friendLogTable,
|
friendLogTable,
|
||||||
|
pendingOfflineMap,
|
||||||
|
pendingOfflineDelay,
|
||||||
|
|
||||||
initFriendsList,
|
|
||||||
updateLocalFavoriteFriends,
|
updateLocalFavoriteFriends,
|
||||||
updateSidebarFavorites,
|
updateSidebarFavorites,
|
||||||
updateFriend,
|
|
||||||
deleteFriend,
|
deleteFriend,
|
||||||
refreshFriendsStatus,
|
refreshFriendsStatus,
|
||||||
addFriend,
|
addFriend,
|
||||||
refreshFriends,
|
refreshFriends,
|
||||||
refreshFriendsList,
|
|
||||||
updateOnlineFriendCounter,
|
updateOnlineFriendCounter,
|
||||||
getAllUserStats,
|
getAllUserStats,
|
||||||
getAllUserMutualCount,
|
getAllUserMutualCount,
|
||||||
@@ -1562,11 +1454,13 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
getFriendRequest,
|
getFriendRequest,
|
||||||
userOnFriend,
|
userOnFriend,
|
||||||
confirmDeleteFriend,
|
confirmDeleteFriend,
|
||||||
updateFriendships,
|
|
||||||
updateUserCurrentStatus,
|
updateUserCurrentStatus,
|
||||||
handleFriendAdd,
|
handleFriendAdd,
|
||||||
handleFriendDelete,
|
handleFriendDelete,
|
||||||
initFriendLogHistoryTable,
|
handleFriendStatus,
|
||||||
setRefreshFriendsLoading
|
addFriendship,
|
||||||
|
tryApplyFriendOrder,
|
||||||
|
resetFriendLog,
|
||||||
|
initFriendLogHistoryTable
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,18 +6,15 @@ import {
|
|||||||
deleteVRChatCache as _deleteVRChatCache,
|
deleteVRChatCache as _deleteVRChatCache,
|
||||||
isRealInstance
|
isRealInstance
|
||||||
} from '../shared/utils';
|
} from '../shared/utils';
|
||||||
import { createGameCoordinator } from '../coordinators/gameCoordinator';
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
|
import { runGameRunningChangedFlow } from '../coordinators/gameCoordinator';
|
||||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||||
import { useAvatarStore } from './avatar';
|
import { useAvatarStore } from './avatar';
|
||||||
import { useGameLogStore } from './gameLog';
|
import { useGameLogStore } from './gameLog';
|
||||||
import { useInstanceStore } from './instance';
|
|
||||||
import { useLaunchStore } from './launch';
|
import { useLaunchStore } from './launch';
|
||||||
import { useLocationStore } from './location';
|
import { useLocationStore } from './location';
|
||||||
import { useModalStore } from './modal';
|
import { useModalStore } from './modal';
|
||||||
import { useNotificationStore } from './notification';
|
import { useNotificationStore } from './notification';
|
||||||
import { useUpdateLoopStore } from './updateLoop';
|
|
||||||
import { useUserStore } from './user';
|
|
||||||
import { useVrStore } from './vr';
|
import { useVrStore } from './vr';
|
||||||
import { useWorldStore } from './world';
|
import { useWorldStore } from './world';
|
||||||
|
|
||||||
@@ -32,11 +29,8 @@ export const useGameStore = defineStore('Game', () => {
|
|||||||
const avatarStore = useAvatarStore();
|
const avatarStore = useAvatarStore();
|
||||||
const launchStore = useLaunchStore();
|
const launchStore = useLaunchStore();
|
||||||
const worldStore = useWorldStore();
|
const worldStore = useWorldStore();
|
||||||
const instanceStore = useInstanceStore();
|
|
||||||
const gameLogStore = useGameLogStore();
|
const gameLogStore = useGameLogStore();
|
||||||
const vrStore = useVrStore();
|
const vrStore = useVrStore();
|
||||||
const userStore = useUserStore();
|
|
||||||
const updateLoopStore = useUpdateLoopStore();
|
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -168,22 +162,6 @@ export const useGameStore = defineStore('Game', () => {
|
|||||||
VRChatCacheSizeLoading.value = false;
|
VRChatCacheSizeLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameCoordinator = createGameCoordinator({
|
|
||||||
userStore,
|
|
||||||
instanceStore,
|
|
||||||
updateLoopStore,
|
|
||||||
locationStore,
|
|
||||||
gameLogStore,
|
|
||||||
vrStore,
|
|
||||||
avatarStore,
|
|
||||||
configRepository,
|
|
||||||
workerTimers,
|
|
||||||
checkVRChatDebugLogging,
|
|
||||||
autoVRChatCacheManagement,
|
|
||||||
checkIfGameCrashed,
|
|
||||||
getIsGameNoVR: () => isGameNoVR.value
|
|
||||||
});
|
|
||||||
|
|
||||||
// use in C#
|
// use in C#
|
||||||
/**
|
/**
|
||||||
* @param {boolean} isGameRunningArg Game running flag from IPC.
|
* @param {boolean} isGameRunningArg Game running flag from IPC.
|
||||||
@@ -195,7 +173,7 @@ export const useGameStore = defineStore('Game', () => {
|
|||||||
}
|
}
|
||||||
if (isGameRunningArg !== isGameRunning.value) {
|
if (isGameRunningArg !== isGameRunning.value) {
|
||||||
isGameRunning.value = isGameRunningArg;
|
isGameRunning.value = isGameRunningArg;
|
||||||
await gameCoordinator.runGameRunningChangedFlow(isGameRunningArg);
|
await runGameRunningChangedFlow(isGameRunningArg);
|
||||||
console.log(new Date(), 'isGameRunning', isGameRunningArg);
|
console.log(new Date(), 'isGameRunning', isGameRunningArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +278,8 @@ export const useGameStore = defineStore('Game', () => {
|
|||||||
setIsGameNoVR,
|
setIsGameNoVR,
|
||||||
getVRChatRegistryKey,
|
getVRChatRegistryKey,
|
||||||
checkVRChatDebugLogging,
|
checkVRChatDebugLogging,
|
||||||
|
autoVRChatCacheManagement,
|
||||||
|
checkIfGameCrashed,
|
||||||
updateIsHmdAfk
|
updateIsHmdAfk
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { watch } from 'vue';
|
|||||||
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { groupRequest } from '../api';
|
import { groupRequest } from '../api';
|
||||||
|
import { runRefreshFriendsListFlow } from '../coordinators/friendSyncCoordinator';
|
||||||
import { useAuthStore } from './auth';
|
import { useAuthStore } from './auth';
|
||||||
import { useDiscordPresenceSettingsStore } from './settings/discordPresence';
|
import { useDiscordPresenceSettingsStore } from './settings/discordPresence';
|
||||||
import { useFriendStore } from './friend';
|
import { useFriendStore } from './friend';
|
||||||
@@ -62,6 +63,9 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
|
|
||||||
const ipcTimeout = state.ipcTimeout;
|
const ipcTimeout = state.ipcTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
async function updateLoop() {
|
async function updateLoop() {
|
||||||
try {
|
try {
|
||||||
if (watchState.isLoggedIn) {
|
if (watchState.isLoggedIn) {
|
||||||
@@ -71,7 +75,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
}
|
}
|
||||||
if (--state.nextFriendsRefresh <= 0) {
|
if (--state.nextFriendsRefresh <= 0) {
|
||||||
state.nextFriendsRefresh = 3600; // 1hour
|
state.nextFriendsRefresh = 3600; // 1hour
|
||||||
friendStore.refreshFriendsList();
|
runRefreshFriendsListFlow();
|
||||||
authStore.updateStoredUser(userStore.currentUser);
|
authStore.updateStoredUser(userStore.currentUser);
|
||||||
if (
|
if (
|
||||||
userStore.currentUser.last_activity &&
|
userStore.currentUser.last_activity &&
|
||||||
@@ -141,28 +145,48 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
friendStore.setRefreshFriendsLoading(false);
|
friendStore.isRefreshFriendsLoading = false;
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
workerTimers.setTimeout(() => updateLoop(), 1000);
|
workerTimers.setTimeout(() => updateLoop(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
function setNextClearVRCXCacheCheck(value) {
|
function setNextClearVRCXCacheCheck(value) {
|
||||||
state.nextClearVRCXCacheCheck = value;
|
state.nextClearVRCXCacheCheck = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
function setNextGroupInstanceRefresh(value) {
|
function setNextGroupInstanceRefresh(value) {
|
||||||
state.nextGroupInstanceRefresh = value;
|
state.nextGroupInstanceRefresh = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
function setNextDiscordUpdate(value) {
|
function setNextDiscordUpdate(value) {
|
||||||
state.nextDiscordUpdate = value;
|
state.nextDiscordUpdate = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
function setIpcTimeout(value) {
|
function setIpcTimeout(value) {
|
||||||
state.ipcTimeout = value;
|
state.ipcTimeout = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
function setNextCurrentUserRefresh(value) {
|
function setNextCurrentUserRefresh(value) {
|
||||||
state.nextCurrentUserRefresh = value;
|
state.nextCurrentUserRefresh = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import {
|
|||||||
extractFileId,
|
extractFileId,
|
||||||
findUserByDisplayName,
|
findUserByDisplayName,
|
||||||
getAllUserMemos,
|
getAllUserMemos,
|
||||||
getGroupName,
|
|
||||||
getUserMemo,
|
getUserMemo,
|
||||||
getWorldName,
|
getWorldName,
|
||||||
isRealInstance,
|
isRealInstance,
|
||||||
@@ -27,22 +26,26 @@ import {
|
|||||||
} from '../shared/utils';
|
} from '../shared/utils';
|
||||||
import {
|
import {
|
||||||
avatarRequest,
|
avatarRequest,
|
||||||
groupRequest,
|
|
||||||
instanceRequest,
|
instanceRequest,
|
||||||
queryRequest,
|
queryRequest,
|
||||||
userRequest
|
userRequest
|
||||||
} from '../api';
|
} from '../api';
|
||||||
|
import {
|
||||||
|
runAvatarSwapFlow,
|
||||||
|
runFirstLoginFlow,
|
||||||
|
runHomeLocationSyncFlow,
|
||||||
|
runPostApplySyncFlow
|
||||||
|
} from '../coordinators/userSessionCoordinator';
|
||||||
import { processBulk, request } from '../service/request';
|
import { processBulk, request } from '../service/request';
|
||||||
import { AppDebug } from '../service/appConfig';
|
import { AppDebug } from '../service/appConfig';
|
||||||
import { createUserEventCoordinator } from '../coordinators/userEventCoordinator';
|
|
||||||
import { createUserSessionCoordinator } from '../coordinators/userSessionCoordinator';
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { patchUserFromEvent } from '../queries';
|
import { patchUserFromEvent } from '../queries';
|
||||||
|
import { runHandleUserUpdateFlow } from '../coordinators/userEventCoordinator';
|
||||||
|
import { runUpdateFriendFlow } from '../coordinators/friendPresenceCoordinator';
|
||||||
import { useAppearanceSettingsStore } from './settings/appearance';
|
import { useAppearanceSettingsStore } from './settings/appearance';
|
||||||
import { useAuthStore } from './auth';
|
import { useAuthStore } from './auth';
|
||||||
import { useAvatarStore } from './avatar';
|
import { useAvatarStore } from './avatar';
|
||||||
import { useFavoriteStore } from './favorite';
|
import { useFavoriteStore } from './favorite';
|
||||||
import { useFeedStore } from './feed';
|
|
||||||
import { useFriendStore } from './friend';
|
import { useFriendStore } from './friend';
|
||||||
import { useGameStore } from './game';
|
import { useGameStore } from './game';
|
||||||
import { useGeneralSettingsStore } from './settings/general';
|
import { useGeneralSettingsStore } from './settings/general';
|
||||||
@@ -55,7 +58,6 @@ import { usePhotonStore } from './photon';
|
|||||||
import { useSearchStore } from './search';
|
import { useSearchStore } from './search';
|
||||||
import { useSharedFeedStore } from './sharedFeed';
|
import { useSharedFeedStore } from './sharedFeed';
|
||||||
import { useUiStore } from './ui';
|
import { useUiStore } from './ui';
|
||||||
import { useWorldStore } from './world';
|
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
@@ -73,8 +75,6 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const groupStore = useGroupStore();
|
const groupStore = useGroupStore();
|
||||||
const feedStore = useFeedStore();
|
|
||||||
const worldStore = useWorldStore();
|
|
||||||
const uiStore = useUiStore();
|
const uiStore = useUiStore();
|
||||||
const moderationStore = useModerationStore();
|
const moderationStore = useModerationStore();
|
||||||
const photonStore = usePhotonStore();
|
const photonStore = usePhotonStore();
|
||||||
@@ -472,11 +472,11 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
{ logLabel: 'User cache cleanup' }
|
{ logLabel: 'User cache cleanup' }
|
||||||
);
|
);
|
||||||
cachedUsers.set(ref.id, ref);
|
cachedUsers.set(ref.id, ref);
|
||||||
friendStore.updateFriend(ref.id);
|
runUpdateFriendFlow(ref.id);
|
||||||
} else {
|
} else {
|
||||||
if (json.state !== 'online') {
|
if (json.state !== 'online') {
|
||||||
// offline event before GPS to offline location
|
// offline event before GPS to offline location
|
||||||
friendStore.updateFriend(ref.id, json.state);
|
runUpdateFriendFlow(ref.id, json.state);
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
hasPropChanged: _hasPropChanged,
|
hasPropChanged: _hasPropChanged,
|
||||||
@@ -584,7 +584,7 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
instanceStore.getCurrentInstanceUserList();
|
instanceStore.getCurrentInstanceUserList();
|
||||||
}
|
}
|
||||||
if (ref.state === 'online') {
|
if (ref.state === 'online') {
|
||||||
friendStore.updateFriend(ref.id, ref.state); // online/offline
|
runUpdateFriendFlow(ref.id, ref.state); // online/offline
|
||||||
}
|
}
|
||||||
favoriteStore.applyFavorite('friend', ref.id);
|
favoriteStore.applyFavorite('friend', ref.id);
|
||||||
friendStore.userOnFriend(ref);
|
friendStore.userOnFriend(ref);
|
||||||
@@ -1145,7 +1145,7 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function handleUserUpdate(ref, props) {
|
async function handleUserUpdate(ref, props) {
|
||||||
await userEventCoordinator.runHandleUserUpdateFlow(ref, props);
|
await runHandleUserUpdateFlow(ref, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1421,7 +1421,7 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
function applyCurrentUser(json) {
|
function applyCurrentUser(json) {
|
||||||
authStore.setAttemptingAutoLogin(false);
|
authStore.setAttemptingAutoLogin(false);
|
||||||
let ref = currentUser.value;
|
let ref = currentUser.value;
|
||||||
userSessionCoordinator.runAvatarSwapFlow({
|
runAvatarSwapFlow({
|
||||||
json,
|
json,
|
||||||
ref,
|
ref,
|
||||||
isLoggedIn: watchState.isLoggedIn
|
isLoggedIn: watchState.isLoggedIn
|
||||||
@@ -1545,15 +1545,15 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
$travelingToLocation: '',
|
$travelingToLocation: '',
|
||||||
...json
|
...json
|
||||||
};
|
};
|
||||||
userSessionCoordinator.runFirstLoginFlow(ref);
|
runFirstLoginFlow(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
ref.$isVRCPlus = ref.tags.includes('system_supporter');
|
||||||
appearanceSettingsStore.applyUserTrustLevel(ref);
|
appearanceSettingsStore.applyUserTrustLevel(ref);
|
||||||
applyUserLanguage(ref);
|
applyUserLanguage(ref);
|
||||||
applyPresenceLocation(ref);
|
applyPresenceLocation(ref);
|
||||||
userSessionCoordinator.runPostApplySyncFlow(ref);
|
runPostApplySyncFlow(ref);
|
||||||
userSessionCoordinator.runHomeLocationSyncFlow(ref);
|
runHomeLocationSyncFlow(ref);
|
||||||
|
|
||||||
// when isGameRunning use gameLog instead of API
|
// when isGameRunning use gameLog instead of API
|
||||||
const $location = parseLocation(locationStore.lastLocation.location);
|
const $location = parseLocation(locationStore.lastLocation.location);
|
||||||
@@ -1744,42 +1744,6 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const userSessionCoordinator = createUserSessionCoordinator({
|
|
||||||
avatarStore,
|
|
||||||
gameStore,
|
|
||||||
groupStore,
|
|
||||||
instanceStore,
|
|
||||||
friendStore,
|
|
||||||
authStore,
|
|
||||||
cachedUsers,
|
|
||||||
currentUser,
|
|
||||||
userDialog,
|
|
||||||
getWorldName,
|
|
||||||
parseLocation,
|
|
||||||
now: () => Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
const userEventCoordinator = createUserEventCoordinator({
|
|
||||||
friendStore,
|
|
||||||
state,
|
|
||||||
parseLocation,
|
|
||||||
userDialog,
|
|
||||||
applyUserDialogLocation,
|
|
||||||
worldStore,
|
|
||||||
groupStore,
|
|
||||||
instanceStore,
|
|
||||||
appDebug: AppDebug,
|
|
||||||
getWorldName,
|
|
||||||
getGroupName,
|
|
||||||
feedStore,
|
|
||||||
database,
|
|
||||||
avatarStore,
|
|
||||||
generalSettingsStore,
|
|
||||||
checkNote,
|
|
||||||
now: () => Date.now(),
|
|
||||||
nowIso: () => new Date().toJSON()
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon-sm"
|
size="icon-sm"
|
||||||
:disabled="isRefreshFriendsLoading"
|
:disabled="isRefreshFriendsLoading"
|
||||||
@click="refreshFriendsList">
|
@click="runRefreshFriendsListFlow">
|
||||||
<Spinner v-if="isRefreshFriendsLoading" />
|
<Spinner v-if="isRefreshFriendsLoading" />
|
||||||
<RefreshCw v-else />
|
<RefreshCw v-else />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -267,6 +267,7 @@
|
|||||||
useGroupStore,
|
useGroupStore,
|
||||||
useNotificationStore
|
useNotificationStore
|
||||||
} from '../../stores';
|
} from '../../stores';
|
||||||
|
import { runRefreshFriendsListFlow } from '../../coordinators/friendSyncCoordinator';
|
||||||
import { normalizeFavoriteGroupsChange, resolveFavoriteGroups } from './sidebarSettingsUtils';
|
import { normalizeFavoriteGroupsChange, resolveFavoriteGroups } from './sidebarSettingsUtils';
|
||||||
import { useGlobalSearchStore } from '../../stores/globalSearch';
|
import { useGlobalSearchStore } from '../../stores/globalSearch';
|
||||||
|
|
||||||
@@ -277,7 +278,6 @@
|
|||||||
import NotificationCenterSheet from './components/NotificationCenterSheet.vue';
|
import NotificationCenterSheet from './components/NotificationCenterSheet.vue';
|
||||||
|
|
||||||
const { friends, isRefreshFriendsLoading, onlineFriendCount } = storeToRefs(useFriendStore());
|
const { friends, isRefreshFriendsLoading, onlineFriendCount } = storeToRefs(useFriendStore());
|
||||||
const { refreshFriendsList } = useFriendStore();
|
|
||||||
const { groupInstances } = storeToRefs(useGroupStore());
|
const { groupInstances } = storeToRefs(useGroupStore());
|
||||||
const notificationStore = useNotificationStore();
|
const notificationStore = useNotificationStore();
|
||||||
const { isNotificationCenterOpen, hasUnseenNotifications } = storeToRefs(notificationStore);
|
const { isNotificationCenterOpen, hasUnseenNotifications } = storeToRefs(notificationStore);
|
||||||
|
|||||||
Reference in New Issue
Block a user