mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-07 14:56:06 +02:00
refactor store
This commit is contained in:
+2
-1
@@ -26,6 +26,7 @@
|
|||||||
import { TooltipProvider } from './components/ui/tooltip';
|
import { TooltipProvider } from './components/ui/tooltip';
|
||||||
import { createGlobalStores } from './stores';
|
import { createGlobalStores } from './stores';
|
||||||
import { initNoty } from './plugin/noty';
|
import { initNoty } from './plugin/noty';
|
||||||
|
import { getGameLogTable } from './coordinators/gameLogCoordinator';
|
||||||
import { runCheckVRChatDebugLoggingFlow } from './coordinators/gameCoordinator';
|
import { runCheckVRChatDebugLoggingFlow } from './coordinators/gameCoordinator';
|
||||||
|
|
||||||
import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue';
|
import AlertDialogModal from './components/ui/alert-dialog/AlertDialogModal.vue';
|
||||||
@@ -57,7 +58,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
store.gameLog.getGameLogTable();
|
getGameLogTable();
|
||||||
await store.auth.migrateStoredUsers();
|
await store.auth.migrateStoredUsers();
|
||||||
store.auth.autoLoginAfterMounted();
|
store.auth.autoLoginAfterMounted();
|
||||||
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
store.vrcx.checkAutoBackupRestoreVrcRegistry();
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ vi.mock('../../queries', () => ({
|
|||||||
user: (userId) => ['user', userId],
|
user: (userId) => ['user', userId],
|
||||||
avatar: (avatarId) => ['avatar', avatarId],
|
avatar: (avatarId) => ['avatar', avatarId],
|
||||||
world: (worldId) => ['world', worldId]
|
world: (worldId) => ['world', worldId]
|
||||||
|
},
|
||||||
|
entityQueryPolicies: {
|
||||||
|
user: {},
|
||||||
|
avatar: {},
|
||||||
|
world: {},
|
||||||
|
worldCollection: {}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ vi.mock('../../stores/user', () => ({
|
|||||||
vi.mock('../../queries', () => ({
|
vi.mock('../../queries', () => ({
|
||||||
queryClient: {
|
queryClient: {
|
||||||
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
||||||
|
},
|
||||||
|
entityQueryPolicies: {
|
||||||
|
user: {},
|
||||||
|
avatar: {},
|
||||||
|
world: {},
|
||||||
|
worldCollection: {}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ vi.mock('../../stores', () => ({
|
|||||||
vi.mock('../../queries', () => ({
|
vi.mock('../../queries', () => ({
|
||||||
queryClient: {
|
queryClient: {
|
||||||
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
invalidateQueries: (...args) => mockInvalidateQueries(...args)
|
||||||
|
},
|
||||||
|
entityQueryPolicies: {
|
||||||
|
user: {},
|
||||||
|
avatar: {},
|
||||||
|
world: {},
|
||||||
|
worldCollection: {}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ vi.mock('../../service/watchState', () => ({
|
|||||||
watchState: { isLoggedIn: false }
|
watchState: { isLoggedIn: false }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
import * as avatarCoordinatorModule from '../../coordinators/avatarCoordinator';
|
||||||
|
vi.mock('../../coordinators/avatarCoordinator', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal();
|
||||||
|
return { ...actual, showAvatarAuthorDialog: vi.fn() };
|
||||||
|
});
|
||||||
|
|
||||||
const i18n = createI18n({
|
const i18n = createI18n({
|
||||||
locale: 'en',
|
locale: 'en',
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
@@ -210,9 +216,7 @@ describe('AvatarInfo.vue', () => {
|
|||||||
test('does not call showAvatarAuthorDialog when no imageurl', async () => {
|
test('does not call showAvatarAuthorDialog when no imageurl', async () => {
|
||||||
const wrapper = mountAvatarInfo({});
|
const wrapper = mountAvatarInfo({});
|
||||||
await wrapper.trigger('click');
|
await wrapper.trigger('click');
|
||||||
const { useAvatarStore } = await import('../../stores');
|
expect(avatarCoordinatorModule.showAvatarAuthorDialog).not.toHaveBeenCalled();
|
||||||
const avatarStore = useAvatarStore();
|
|
||||||
expect(avatarStore.showAvatarAuthorDialog).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -142,15 +142,15 @@
|
|||||||
const { userDialog, languageDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
const { userDialog, languageDialog, currentUser, isLocalUserVrcPlusSupporter } = storeToRefs(useUserStore());
|
||||||
const { cachedUsers, showSendBoopDialog } = useUserStore();
|
const { cachedUsers, showSendBoopDialog } = useUserStore();
|
||||||
const { showFavoriteDialog } = useFavoriteStore();
|
const { showFavoriteDialog } = useFavoriteStore();
|
||||||
import { showAvatarDialog, showAvatarAuthorDialog } from '../../../coordinators/avatarCoordinator';
|
import { showAvatarDialog, showAvatarAuthorDialog } from '../../../coordinators/avatarCoordinator';
|
||||||
import { showUserDialog, refreshUserDialogAvatars } from '../../../coordinators/userCoordinator';
|
import { showUserDialog, refreshUserDialogAvatars } from '../../../coordinators/userCoordinator';
|
||||||
|
import { getFriendRequest, handleFriendDelete } from '../../../coordinators/friendRelationshipCoordinator';
|
||||||
|
|
||||||
const { showModerateGroupDialog } = useGroupStore();
|
const { showModerateGroupDialog } = useGroupStore();
|
||||||
const { inviteGroupDialog } = storeToRefs(useGroupStore());
|
const { inviteGroupDialog } = storeToRefs(useGroupStore());
|
||||||
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
|
const { lastLocation, lastLocationDestination } = storeToRefs(useLocationStore());
|
||||||
const { refreshInviteMessageTableData } = useInviteStore();
|
const { refreshInviteMessageTableData } = useInviteStore();
|
||||||
const { friendLogTable } = storeToRefs(useFriendStore());
|
const { friendLogTable } = storeToRefs(useFriendStore());
|
||||||
const { getFriendRequest, handleFriendDelete } = useFriendStore();
|
|
||||||
const { clearInviteImageUpload, showGalleryPage } = useGalleryStore();
|
const { clearInviteImageUpload, showGalleryPage } = useGalleryStore();
|
||||||
|
|
||||||
const { applyPlayerModeration, handlePlayerModerationDelete } = useModerationStore();
|
const { applyPlayerModeration, handlePlayerModerationDelete } = useModerationStore();
|
||||||
|
|||||||
@@ -84,6 +84,12 @@ vi.mock('../../../../service/request', () => ({
|
|||||||
failedGetRequests: new Map()
|
failedGetRequests: new Map()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
import * as userCoordinatorModule from '../../../../coordinators/userCoordinator';
|
||||||
|
vi.mock('../../../../coordinators/userCoordinator', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal();
|
||||||
|
return { ...actual, showUserDialog: vi.fn() };
|
||||||
|
});
|
||||||
|
|
||||||
import UserDialogMutualFriendsTab from '../UserDialogMutualFriendsTab.vue';
|
import UserDialogMutualFriendsTab from '../UserDialogMutualFriendsTab.vue';
|
||||||
import { useUserStore } from '../../../../stores';
|
import { useUserStore } from '../../../../stores';
|
||||||
import { userDialogMutualFriendSortingOptions } from '../../../../shared/constants';
|
import { userDialogMutualFriendSortingOptions } from '../../../../shared/constants';
|
||||||
@@ -219,7 +225,7 @@ describe('UserDialogMutualFriendsTab.vue', () => {
|
|||||||
};
|
};
|
||||||
userStore.currentUser = { id: 'usr_me' };
|
userStore.currentUser = { id: 'usr_me' };
|
||||||
const showUserDialogSpy = vi
|
const showUserDialogSpy = vi
|
||||||
.spyOn(userStore, 'showUserDialog')
|
.spyOn(userCoordinatorModule, 'showUserDialog')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
const wrapper = mount(UserDialogMutualFriendsTab, {
|
const wrapper = mount(UserDialogMutualFriendsTab, {
|
||||||
|
|||||||
@@ -88,6 +88,12 @@ vi.mock('../../../../service/request', () => ({
|
|||||||
failedGetRequests: new Map()
|
failedGetRequests: new Map()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
import * as worldCoordinatorModule from '../../../../coordinators/worldCoordinator';
|
||||||
|
vi.mock('../../../../coordinators/worldCoordinator', async (importOriginal) => {
|
||||||
|
const actual = await importOriginal();
|
||||||
|
return { ...actual, showWorldDialog: vi.fn() };
|
||||||
|
});
|
||||||
|
|
||||||
import UserDialogWorldsTab from '../UserDialogWorldsTab.vue';
|
import UserDialogWorldsTab from '../UserDialogWorldsTab.vue';
|
||||||
import { useUserStore } from '../../../../stores';
|
import { useUserStore } from '../../../../stores';
|
||||||
import {
|
import {
|
||||||
@@ -235,10 +241,8 @@ describe('UserDialogWorldsTab.vue', () => {
|
|||||||
test('calls showWorldDialog when a world is clicked', async () => {
|
test('calls showWorldDialog when a world is clicked', async () => {
|
||||||
const pinia = createTestingPinia({ stubActions: false });
|
const pinia = createTestingPinia({ stubActions: false });
|
||||||
const userStore = useUserStore(pinia);
|
const userStore = useUserStore(pinia);
|
||||||
const { useWorldStore } = await import('../../../../stores');
|
|
||||||
const worldStore = useWorldStore(pinia);
|
|
||||||
const showWorldDialogSpy = vi
|
const showWorldDialogSpy = vi
|
||||||
.spyOn(worldStore, 'showWorldDialog')
|
.spyOn(worldCoordinatorModule, 'showWorldDialog')
|
||||||
.mockImplementation(() => {});
|
.mockImplementation(() => {});
|
||||||
|
|
||||||
userStore.userDialog = {
|
userStore.userDialog = {
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createAuthAutoLoginCoordinator } from '../authAutoLoginCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} Promise flush helper.
|
|
||||||
*/
|
|
||||||
async function flushPromises() {
|
|
||||||
await Promise.resolve();
|
|
||||||
await Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {object} overrides Dependency overrides for a specific test case.
|
|
||||||
* @returns {object} Mock dependencies for auth auto-login coordinator.
|
|
||||||
*/
|
|
||||||
function makeDeps(overrides = {}) {
|
|
||||||
let attemptingAutoLogin = false;
|
|
||||||
|
|
||||||
const deps = {
|
|
||||||
getIsAttemptingAutoLogin: vi.fn(() => attemptingAutoLogin),
|
|
||||||
setAttemptingAutoLogin: vi.fn((value) => {
|
|
||||||
attemptingAutoLogin = value;
|
|
||||||
}),
|
|
||||||
getLastUserLoggedIn: vi.fn(() => 'usr_1'),
|
|
||||||
getSavedCredentials: vi.fn().mockResolvedValue({
|
|
||||||
user: { id: 'usr_1' },
|
|
||||||
loginParams: {}
|
|
||||||
}),
|
|
||||||
isPrimaryPasswordEnabled: vi.fn(() => false),
|
|
||||||
handleLogoutEvent: vi.fn().mockResolvedValue(undefined),
|
|
||||||
autoLoginAttempts: new Set(),
|
|
||||||
relogin: vi.fn().mockResolvedValue(undefined),
|
|
||||||
notifyAutoLoginSuccess: vi.fn(),
|
|
||||||
notifyAutoLoginFailed: vi.fn(),
|
|
||||||
notifyOffline: vi.fn(),
|
|
||||||
flashWindow: vi.fn(),
|
|
||||||
isOnline: vi.fn(() => true),
|
|
||||||
now: vi.fn(() => 1000)
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
...deps,
|
|
||||||
...overrides
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createAuthAutoLoginCoordinator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns early when auto-login is already in progress', async () => {
|
|
||||||
const deps = makeDeps({
|
|
||||||
getIsAttemptingAutoLogin: vi.fn(() => true)
|
|
||||||
});
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
|
|
||||||
expect(deps.setAttemptingAutoLogin).not.toHaveBeenCalled();
|
|
||||||
expect(deps.getSavedCredentials).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stops flow when no saved credentials are found', async () => {
|
|
||||||
const deps = makeDeps({
|
|
||||||
getSavedCredentials: vi.fn().mockResolvedValue(null)
|
|
||||||
});
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenNthCalledWith(1, true);
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenNthCalledWith(2, false);
|
|
||||||
expect(deps.relogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('logs out when primary password is enabled', async () => {
|
|
||||||
const deps = makeDeps({
|
|
||||||
isPrimaryPasswordEnabled: vi.fn(() => true)
|
|
||||||
});
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenNthCalledWith(1, true);
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenNthCalledWith(2, false);
|
|
||||||
expect(deps.handleLogoutEvent).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.relogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('logs out and flashes window when attempts exceed limit', async () => {
|
|
||||||
const deps = makeDeps({
|
|
||||||
autoLoginAttempts: new Set([100, 200, 300]),
|
|
||||||
now: vi.fn(() => 500)
|
|
||||||
});
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
|
|
||||||
expect(deps.handleLogoutEvent).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.flashWindow).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.relogin).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runs relogin and success notify on successful auto-login', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.autoLoginAttempts.has(1000)).toBe(true);
|
|
||||||
expect(deps.relogin).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.notifyAutoLoginSuccess).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.notifyAutoLoginFailed).not.toHaveBeenCalled();
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenLastCalledWith(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runs failure and offline notifications when relogin fails while offline', async () => {
|
|
||||||
const deps = makeDeps({
|
|
||||||
relogin: vi.fn().mockRejectedValue(new Error('failed')),
|
|
||||||
isOnline: vi.fn(() => false)
|
|
||||||
});
|
|
||||||
const coordinator = createAuthAutoLoginCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleAutoLoginFlow();
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.notifyAutoLoginSuccess).not.toHaveBeenCalled();
|
|
||||||
expect(deps.notifyAutoLoginFailed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.notifyOffline).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenLastCalledWith(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createAuthCoordinator } from '../authCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {object} Mock dependencies for auth coordinator.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
return {
|
|
||||||
userStore: {
|
|
||||||
currentUser: { id: 'usr_1' },
|
|
||||||
setUserDialogVisible: vi.fn(),
|
|
||||||
applyCurrentUser: vi.fn()
|
|
||||||
},
|
|
||||||
notificationStore: {
|
|
||||||
setNotificationInitStatus: vi.fn()
|
|
||||||
},
|
|
||||||
updateLoopStore: {
|
|
||||||
setNextCurrentUserRefresh: vi.fn()
|
|
||||||
},
|
|
||||||
initWebsocket: vi.fn(),
|
|
||||||
updateStoredUser: vi.fn().mockResolvedValue(undefined),
|
|
||||||
webApiService: {
|
|
||||||
clearCookies: vi.fn().mockResolvedValue(undefined)
|
|
||||||
},
|
|
||||||
loginForm: {
|
|
||||||
value: {
|
|
||||||
lastUserLoggedIn: 'usr_1'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
configRepository: {
|
|
||||||
remove: vi.fn().mockResolvedValue(undefined)
|
|
||||||
},
|
|
||||||
setAttemptingAutoLogin: vi.fn(),
|
|
||||||
autoLoginAttempts: {
|
|
||||||
clear: vi.fn()
|
|
||||||
},
|
|
||||||
closeWebSocket: vi.fn(),
|
|
||||||
queryClient: {
|
|
||||||
clear: vi.fn()
|
|
||||||
},
|
|
||||||
watchState: {
|
|
||||||
isLoggedIn: true,
|
|
||||||
isFriendsLoaded: true,
|
|
||||||
isFavoritesLoaded: true
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createAuthCoordinator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runLogoutFlow applies all logout side effects', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createAuthCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runLogoutFlow();
|
|
||||||
|
|
||||||
expect(deps.userStore.setUserDialogVisible).toHaveBeenCalledWith(false);
|
|
||||||
expect(deps.watchState.isLoggedIn).toBe(false);
|
|
||||||
expect(deps.watchState.isFriendsLoaded).toBe(false);
|
|
||||||
expect(deps.watchState.isFavoritesLoaded).toBe(false);
|
|
||||||
expect(
|
|
||||||
deps.notificationStore.setNotificationInitStatus
|
|
||||||
).toHaveBeenCalledWith(false);
|
|
||||||
expect(deps.updateStoredUser).toHaveBeenCalledWith(
|
|
||||||
deps.userStore.currentUser
|
|
||||||
);
|
|
||||||
expect(deps.webApiService.clearCookies).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.loginForm.value.lastUserLoggedIn).toBe('');
|
|
||||||
expect(deps.configRepository.remove).toHaveBeenCalledWith(
|
|
||||||
'lastUserLoggedIn'
|
|
||||||
);
|
|
||||||
expect(deps.setAttemptingAutoLogin).toHaveBeenCalledWith(false);
|
|
||||||
expect(deps.autoLoginAttempts.clear).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.closeWebSocket).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.queryClient.clear).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runLoginSuccessFlow applies login success side effects', () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createAuthCoordinator(deps);
|
|
||||||
const json = { id: 'usr_2' };
|
|
||||||
|
|
||||||
coordinator.runLoginSuccessFlow(json);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
deps.updateLoopStore.setNextCurrentUserRefresh
|
|
||||||
).toHaveBeenCalledWith(420);
|
|
||||||
expect(deps.userStore.applyCurrentUser).toHaveBeenCalledWith(json);
|
|
||||||
expect(deps.initWebsocket).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import { describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createFriendPresenceCoordinator } from '../friendPresenceCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {object} Mock dependencies and mutable state for friend presence tests.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
const friends = new Map();
|
|
||||||
const cachedUsers = new Map();
|
|
||||||
const pendingOfflineMap = new Map();
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
state: 'online',
|
|
||||||
displayName: 'User 1',
|
|
||||||
location: 'wrld_1:1',
|
|
||||||
$location_at: 100,
|
|
||||||
$lastFetch: 0,
|
|
||||||
$online_for: 1,
|
|
||||||
$offline_for: '',
|
|
||||||
$active_for: ''
|
|
||||||
};
|
|
||||||
const ctx = {
|
|
||||||
id: 'usr_1',
|
|
||||||
state: 'online',
|
|
||||||
ref,
|
|
||||||
name: 'User 1',
|
|
||||||
isVIP: false,
|
|
||||||
pendingOffline: false
|
|
||||||
};
|
|
||||||
friends.set('usr_1', ctx);
|
|
||||||
cachedUsers.set('usr_1', ref);
|
|
||||||
|
|
||||||
return {
|
|
||||||
deps: {
|
|
||||||
friends,
|
|
||||||
localFavoriteFriends: new Set(),
|
|
||||||
pendingOfflineMap,
|
|
||||||
pendingOfflineDelay: 100,
|
|
||||||
watchState: { isFriendsLoaded: true },
|
|
||||||
appDebug: { debugFriendState: false },
|
|
||||||
getCachedUsers: vi.fn(() => cachedUsers),
|
|
||||||
isRealInstance: vi.fn(() => false),
|
|
||||||
requestUser: vi.fn(),
|
|
||||||
getWorldName: vi.fn().mockResolvedValue('World 1'),
|
|
||||||
getGroupName: vi.fn().mockResolvedValue('Group 1'),
|
|
||||||
feedStore: {
|
|
||||||
addFeed: vi.fn()
|
|
||||||
},
|
|
||||||
database: {
|
|
||||||
addOnlineOfflineToDatabase: vi.fn()
|
|
||||||
},
|
|
||||||
updateOnlineFriendCounter: vi.fn(),
|
|
||||||
now: vi.fn(() => 1000),
|
|
||||||
nowIso: vi.fn(() => '2025-01-01T00:00:00.000Z')
|
|
||||||
},
|
|
||||||
ctx,
|
|
||||||
ref,
|
|
||||||
pendingOfflineMap
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createFriendPresenceCoordinator', () => {
|
|
||||||
test('queues pending offline transition when friend moves from online to offline', async () => {
|
|
||||||
const { deps, ctx, pendingOfflineMap } = makeDeps();
|
|
||||||
const coordinator = createFriendPresenceCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runUpdateFriendFlow('usr_1', 'offline');
|
|
||||||
|
|
||||||
expect(ctx.pendingOffline).toBe(true);
|
|
||||||
expect(pendingOfflineMap.has('usr_1')).toBe(true);
|
|
||||||
expect(deps.updateOnlineFriendCounter).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('processes pending offline queue and applies delayed transition', async () => {
|
|
||||||
const { deps, ctx, pendingOfflineMap, ref } = makeDeps();
|
|
||||||
pendingOfflineMap.set('usr_1', {
|
|
||||||
startTime: 0,
|
|
||||||
newState: 'offline',
|
|
||||||
previousLocation: 'wrld_1:1',
|
|
||||||
previousLocationAt: 500
|
|
||||||
});
|
|
||||||
const coordinator = createFriendPresenceCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runPendingOfflineTickFlow();
|
|
||||||
|
|
||||||
expect(ctx.state).toBe('offline');
|
|
||||||
expect(ctx.pendingOffline).toBe(false);
|
|
||||||
expect(pendingOfflineMap.has('usr_1')).toBe(false);
|
|
||||||
expect(ref.$offline_for).toBe(1000);
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'Offline',
|
|
||||||
userId: 'usr_1',
|
|
||||||
location: 'wrld_1:1',
|
|
||||||
worldName: 'World 1',
|
|
||||||
groupName: 'Group 1',
|
|
||||||
time: 500
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(deps.database.addOnlineOfflineToDatabase).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
);
|
|
||||||
expect(deps.updateOnlineFriendCounter).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('cancels pending offline transition when online state arrives again', async () => {
|
|
||||||
const { deps, ctx, pendingOfflineMap } = makeDeps();
|
|
||||||
pendingOfflineMap.set('usr_1', {
|
|
||||||
startTime: 900,
|
|
||||||
newState: 'offline',
|
|
||||||
previousLocation: 'wrld_1:1',
|
|
||||||
previousLocationAt: 800
|
|
||||||
});
|
|
||||||
const coordinator = createFriendPresenceCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runUpdateFriendFlow('usr_1', 'online');
|
|
||||||
|
|
||||||
expect(ctx.pendingOffline).toBe(false);
|
|
||||||
expect(pendingOfflineMap.has('usr_1')).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('applies offline to online transition contract immediately', async () => {
|
|
||||||
const { deps, ctx, ref } = makeDeps();
|
|
||||||
ctx.state = 'offline';
|
|
||||||
const coordinator = createFriendPresenceCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runUpdateFriendFlow('usr_1', 'online');
|
|
||||||
|
|
||||||
expect(ctx.state).toBe('online');
|
|
||||||
expect(ref.$online_for).toBe(1000);
|
|
||||||
expect(ref.$offline_for).toBe('');
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'Online',
|
|
||||||
userId: 'usr_1',
|
|
||||||
location: 'wrld_1:1',
|
|
||||||
worldName: 'World 1',
|
|
||||||
groupName: 'Group 1'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(deps.database.addOnlineOfflineToDatabase).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
);
|
|
||||||
expect(deps.updateOnlineFriendCounter).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns safely when friend context does not exist', async () => {
|
|
||||||
const { deps } = makeDeps();
|
|
||||||
deps.friends.clear();
|
|
||||||
const coordinator = createFriendPresenceCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runUpdateFriendFlow('usr_404', 'online');
|
|
||||||
|
|
||||||
expect(deps.feedStore.addFeed).not.toHaveBeenCalled();
|
|
||||||
expect(deps.updateOnlineFriendCounter).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import { describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createFriendRelationshipCoordinator } from '../friendRelationshipCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<void>} Promise flush helper.
|
|
||||||
*/
|
|
||||||
async function flushPromises() {
|
|
||||||
await Promise.resolve();
|
|
||||||
await Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {object} Mock dependencies for friend relationship tests.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
return {
|
|
||||||
friendLog: new Map(),
|
|
||||||
friendLogTable: {
|
|
||||||
value: {
|
|
||||||
data: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCurrentUserId: vi.fn(() => 'usr_me'),
|
|
||||||
requestFriendStatus: vi.fn().mockResolvedValue({
|
|
||||||
params: {
|
|
||||||
currentUserId: 'usr_me'
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
isFriend: false
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
handleFriendStatus: vi.fn(),
|
|
||||||
addFriendship: vi.fn(),
|
|
||||||
deleteFriend: vi.fn(),
|
|
||||||
database: {
|
|
||||||
addFriendLogHistory: vi.fn(),
|
|
||||||
deleteFriendLogCurrent: vi.fn()
|
|
||||||
},
|
|
||||||
notificationStore: {
|
|
||||||
queueFriendLogNoty: vi.fn()
|
|
||||||
},
|
|
||||||
sharedFeedStore: {
|
|
||||||
addEntry: vi.fn()
|
|
||||||
},
|
|
||||||
favoriteStore: {
|
|
||||||
handleFavoriteDelete: vi.fn()
|
|
||||||
},
|
|
||||||
uiStore: {
|
|
||||||
notifyMenu: vi.fn()
|
|
||||||
},
|
|
||||||
shouldNotifyUnfriend: vi.fn(() => true),
|
|
||||||
nowIso: vi.fn(() => '2026-03-08T00:00:00.000Z')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createFriendRelationshipCoordinator', () => {
|
|
||||||
test('runDeleteFriendshipFlow applies unfriend side effects after status check', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.friendLog.set('usr_1', {
|
|
||||||
displayName: 'User 1'
|
|
||||||
});
|
|
||||||
const coordinator = createFriendRelationshipCoordinator(deps);
|
|
||||||
|
|
||||||
coordinator.runDeleteFriendshipFlow('usr_1');
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.requestFriendStatus).toHaveBeenCalledWith({
|
|
||||||
userId: 'usr_1',
|
|
||||||
currentUserId: 'usr_me'
|
|
||||||
});
|
|
||||||
expect(deps.handleFriendStatus).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.friendLog.has('usr_1')).toBe(false);
|
|
||||||
expect(deps.database.addFriendLogHistory).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.database.deleteFriendLogCurrent).toHaveBeenCalledWith(
|
|
||||||
'usr_1'
|
|
||||||
);
|
|
||||||
expect(deps.notificationStore.queueFriendLogNoty).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
);
|
|
||||||
expect(deps.sharedFeedStore.addEntry).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.favoriteStore.handleFavoriteDelete).toHaveBeenCalledWith(
|
|
||||||
'usr_1'
|
|
||||||
);
|
|
||||||
expect(deps.uiStore.notifyMenu).toHaveBeenCalledWith('friend-log');
|
|
||||||
expect(deps.deleteFriend).toHaveBeenCalledWith('usr_1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runUpdateFriendshipsFlow syncs additions and stale removals', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.friendLog.set('usr_me', {
|
|
||||||
displayName: 'Me'
|
|
||||||
});
|
|
||||||
deps.friendLog.set('usr_keep', {
|
|
||||||
displayName: 'Keep'
|
|
||||||
});
|
|
||||||
deps.friendLog.set('usr_drop', {
|
|
||||||
displayName: 'Drop'
|
|
||||||
});
|
|
||||||
const coordinator = createFriendRelationshipCoordinator(deps);
|
|
||||||
|
|
||||||
coordinator.runUpdateFriendshipsFlow({
|
|
||||||
friends: ['usr_keep', 'usr_new']
|
|
||||||
});
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.addFriendship).toHaveBeenNthCalledWith(1, 'usr_keep');
|
|
||||||
expect(deps.addFriendship).toHaveBeenNthCalledWith(2, 'usr_new');
|
|
||||||
expect(deps.database.deleteFriendLogCurrent).toHaveBeenCalledWith(
|
|
||||||
'usr_me'
|
|
||||||
);
|
|
||||||
expect(deps.requestFriendStatus).toHaveBeenCalledWith({
|
|
||||||
userId: 'usr_drop',
|
|
||||||
currentUserId: 'usr_me'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores delayed status responses from stale current user', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.friendLog.set('usr_1', {
|
|
||||||
displayName: 'User 1'
|
|
||||||
});
|
|
||||||
deps.requestFriendStatus.mockResolvedValue({
|
|
||||||
params: {
|
|
||||||
currentUserId: 'usr_old'
|
|
||||||
},
|
|
||||||
json: {
|
|
||||||
isFriend: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const coordinator = createFriendRelationshipCoordinator(deps);
|
|
||||||
|
|
||||||
coordinator.runDeleteFriendshipFlow('usr_1');
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.handleFriendStatus).not.toHaveBeenCalled();
|
|
||||||
expect(deps.friendLog.has('usr_1')).toBe(true);
|
|
||||||
expect(deps.database.addFriendLogHistory).not.toHaveBeenCalled();
|
|
||||||
expect(deps.deleteFriend).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('respects unfriend notify switch', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.shouldNotifyUnfriend.mockReturnValue(false);
|
|
||||||
deps.friendLog.set('usr_1', {
|
|
||||||
displayName: 'User 1'
|
|
||||||
});
|
|
||||||
const coordinator = createFriendRelationshipCoordinator(deps);
|
|
||||||
|
|
||||||
coordinator.runDeleteFriendshipFlow('usr_1');
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.uiStore.notifyMenu).not.toHaveBeenCalled();
|
|
||||||
expect(deps.favoriteStore.handleFavoriteDelete).toHaveBeenCalledWith(
|
|
||||||
'usr_1'
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createFriendSyncCoordinator } from '../friendSyncCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {object} Mock dependencies for friend sync coordinator.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
return {
|
|
||||||
getNextCurrentUserRefresh: vi.fn(() => 999),
|
|
||||||
getCurrentUser: vi.fn().mockResolvedValue(undefined),
|
|
||||||
refreshFriends: vi.fn().mockResolvedValue(undefined),
|
|
||||||
reconnectWebSocket: vi.fn(),
|
|
||||||
getCurrentUserId: vi.fn(() => 'usr_1'),
|
|
||||||
getCurrentUserRef: vi.fn(() => ({ id: 'usr_1' })),
|
|
||||||
setRefreshFriendsLoading: vi.fn(),
|
|
||||||
setFriendsLoaded: vi.fn(),
|
|
||||||
resetFriendLog: vi.fn(),
|
|
||||||
isFriendLogInitialized: vi.fn().mockResolvedValue(true),
|
|
||||||
getFriendLog: vi.fn().mockResolvedValue(undefined),
|
|
||||||
initFriendLog: vi.fn().mockResolvedValue(undefined),
|
|
||||||
isDontLogMeOut: vi.fn(() => false),
|
|
||||||
showLoadFailedToast: vi.fn(),
|
|
||||||
handleLogoutEvent: vi.fn(),
|
|
||||||
tryApplyFriendOrder: vi.fn(),
|
|
||||||
getAllUserStats: vi.fn(),
|
|
||||||
hasLegacyFriendLogData: vi.fn().mockResolvedValue(false),
|
|
||||||
removeLegacyFeedTable: vi.fn(),
|
|
||||||
migrateMemos: vi.fn(),
|
|
||||||
migrateFriendLog: vi.fn()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createFriendSyncCoordinator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runRefreshFriendsListFlow refreshes current user before friends when refresh window is small', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.getNextCurrentUserRefresh.mockReturnValue(100);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runRefreshFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.getCurrentUser).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.refreshFriends).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.reconnectWebSocket).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runRefreshFriendsListFlow skips current user refresh when window is large', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.getNextCurrentUserRefresh.mockReturnValue(300);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runRefreshFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.getCurrentUser).not.toHaveBeenCalled();
|
|
||||||
expect(deps.refreshFriends).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.reconnectWebSocket).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runInitFriendsListFlow loads existing friend log when initialized', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.isFriendLogInitialized.mockResolvedValue(true);
|
|
||||||
deps.hasLegacyFriendLogData.mockResolvedValue(false);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runInitFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.setRefreshFriendsLoading).toHaveBeenCalledWith(true);
|
|
||||||
expect(deps.setFriendsLoaded).toHaveBeenCalledWith(false);
|
|
||||||
expect(deps.resetFriendLog).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.isFriendLogInitialized).toHaveBeenCalledWith('usr_1');
|
|
||||||
expect(deps.getFriendLog).toHaveBeenCalledWith({ id: 'usr_1' });
|
|
||||||
expect(deps.initFriendLog).not.toHaveBeenCalled();
|
|
||||||
expect(deps.tryApplyFriendOrder).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.getAllUserStats).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runInitFriendsListFlow initializes new friend log when not initialized', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.isFriendLogInitialized.mockResolvedValue(false);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runInitFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.getFriendLog).not.toHaveBeenCalled();
|
|
||||||
expect(deps.initFriendLog).toHaveBeenCalledWith({ id: 'usr_1' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runInitFriendsListFlow performs legacy migration when old data exists', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.hasLegacyFriendLogData.mockResolvedValue(true);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runInitFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.removeLegacyFeedTable).toHaveBeenCalledWith('usr_1');
|
|
||||||
expect(deps.migrateMemos).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.migrateFriendLog).toHaveBeenCalledWith('usr_1');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runInitFriendsListFlow logs out and rethrows when load fails and dontLogMeOut is false', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const err = new Error('load failed');
|
|
||||||
deps.getFriendLog.mockRejectedValue(err);
|
|
||||||
deps.isDontLogMeOut.mockReturnValue(false);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await expect(coordinator.runInitFriendsListFlow()).rejects.toThrow(
|
|
||||||
'load failed'
|
|
||||||
);
|
|
||||||
expect(deps.showLoadFailedToast).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.handleLogoutEvent).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.tryApplyFriendOrder).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runInitFriendsListFlow continues when load fails and dontLogMeOut is true', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.getFriendLog.mockRejectedValue(new Error('load failed'));
|
|
||||||
deps.isDontLogMeOut.mockReturnValue(true);
|
|
||||||
const coordinator = createFriendSyncCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runInitFriendsListFlow();
|
|
||||||
|
|
||||||
expect(deps.showLoadFailedToast).not.toHaveBeenCalled();
|
|
||||||
expect(deps.handleLogoutEvent).not.toHaveBeenCalled();
|
|
||||||
expect(deps.tryApplyFriendOrder).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.getAllUserStats).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createGameCoordinator } from '../gameCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {object} Mock dependencies for game coordinator.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
return {
|
|
||||||
userStore: {
|
|
||||||
currentUser: {
|
|
||||||
currentAvatar: 'avtr_1'
|
|
||||||
},
|
|
||||||
markCurrentUserGameStarted: vi.fn(),
|
|
||||||
markCurrentUserGameStopped: vi.fn()
|
|
||||||
},
|
|
||||||
instanceStore: {
|
|
||||||
removeAllQueuedInstances: vi.fn()
|
|
||||||
},
|
|
||||||
updateLoopStore: {
|
|
||||||
setIpcTimeout: vi.fn(),
|
|
||||||
setNextDiscordUpdate: vi.fn()
|
|
||||||
},
|
|
||||||
locationStore: {
|
|
||||||
lastLocationReset: vi.fn()
|
|
||||||
},
|
|
||||||
gameLogStore: {
|
|
||||||
clearNowPlaying: vi.fn()
|
|
||||||
},
|
|
||||||
vrStore: {
|
|
||||||
updateVRLastLocation: vi.fn()
|
|
||||||
},
|
|
||||||
avatarStore: {
|
|
||||||
addAvatarWearTime: vi.fn()
|
|
||||||
},
|
|
||||||
configRepository: {
|
|
||||||
setBool: vi.fn().mockResolvedValue(undefined)
|
|
||||||
},
|
|
||||||
workerTimers: {
|
|
||||||
setTimeout: vi.fn()
|
|
||||||
},
|
|
||||||
checkVRChatDebugLogging: vi.fn(),
|
|
||||||
autoVRChatCacheManagement: vi.fn(),
|
|
||||||
checkIfGameCrashed: vi.fn(),
|
|
||||||
getIsGameNoVR: vi.fn(() => true)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createGameCoordinator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runGameRunningChangedFlow(true) runs start + shared side effects', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createGameCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runGameRunningChangedFlow(true);
|
|
||||||
|
|
||||||
expect(deps.userStore.markCurrentUserGameStarted).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
);
|
|
||||||
expect(deps.configRepository.setBool).not.toHaveBeenCalled();
|
|
||||||
expect(
|
|
||||||
deps.userStore.markCurrentUserGameStopped
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
expect(
|
|
||||||
deps.instanceStore.removeAllQueuedInstances
|
|
||||||
).not.toHaveBeenCalled();
|
|
||||||
expect(deps.autoVRChatCacheManagement).not.toHaveBeenCalled();
|
|
||||||
expect(deps.checkIfGameCrashed).not.toHaveBeenCalled();
|
|
||||||
expect(deps.updateLoopStore.setIpcTimeout).not.toHaveBeenCalled();
|
|
||||||
expect(deps.avatarStore.addAvatarWearTime).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(deps.locationStore.lastLocationReset).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.gameLogStore.clearNowPlaying).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.vrStore.updateVRLastLocation).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.updateLoopStore.setNextDiscordUpdate).toHaveBeenCalledWith(
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(deps.workerTimers.setTimeout).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.workerTimers.setTimeout.mock.calls[0][1]).toBe(60000);
|
|
||||||
const timeoutCb = deps.workerTimers.setTimeout.mock.calls[0][0];
|
|
||||||
timeoutCb();
|
|
||||||
expect(deps.checkVRChatDebugLogging).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runGameRunningChangedFlow(false) runs stop + shared side effects', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.getIsGameNoVR.mockReturnValue(false);
|
|
||||||
const coordinator = createGameCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runGameRunningChangedFlow(false);
|
|
||||||
|
|
||||||
expect(deps.getIsGameNoVR).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.configRepository.setBool).toHaveBeenCalledWith(
|
|
||||||
'isGameNoVR',
|
|
||||||
false
|
|
||||||
);
|
|
||||||
expect(deps.userStore.markCurrentUserGameStopped).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
deps.instanceStore.removeAllQueuedInstances
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.autoVRChatCacheManagement).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.checkIfGameCrashed).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.updateLoopStore.setIpcTimeout).toHaveBeenCalledWith(0);
|
|
||||||
expect(deps.avatarStore.addAvatarWearTime).toHaveBeenCalledWith(
|
|
||||||
'avtr_1'
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(deps.locationStore.lastLocationReset).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.gameLogStore.clearNowPlaying).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.vrStore.updateVRLastLocation).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.updateLoopStore.setNextDiscordUpdate).toHaveBeenCalledWith(
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(deps.workerTimers.setTimeout).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.workerTimers.setTimeout.mock.calls[0][1]).toBe(60000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
import { describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createUserEventCoordinator } from '../userEventCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {object} Mock dependencies for user event tests.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
const friendRef = {
|
|
||||||
id: 'usr_1'
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
friendStore: {
|
|
||||||
friends: new Map([['usr_1', friendRef]])
|
|
||||||
},
|
|
||||||
state: {
|
|
||||||
instancePlayerCount: new Map()
|
|
||||||
},
|
|
||||||
parseLocation: vi.fn((location) => {
|
|
||||||
if (location === 'loc_old') {
|
|
||||||
return {
|
|
||||||
tag: 'loc_old',
|
|
||||||
worldId: 'world_old',
|
|
||||||
groupId: 'group_old'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (location === 'loc_new') {
|
|
||||||
return {
|
|
||||||
tag: 'loc_new',
|
|
||||||
worldId: 'world_new',
|
|
||||||
groupId: 'group_new'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
tag: location,
|
|
||||||
worldId: '',
|
|
||||||
groupId: ''
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
userDialog: {
|
|
||||||
value: {
|
|
||||||
$location: {
|
|
||||||
tag: 'loc_new'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
applyUserDialogLocation: vi.fn(),
|
|
||||||
worldStore: {
|
|
||||||
worldDialog: {
|
|
||||||
id: 'world_old'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
groupStore: {
|
|
||||||
groupDialog: {
|
|
||||||
id: 'group_new'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
instanceStore: {
|
|
||||||
applyWorldDialogInstances: vi.fn(),
|
|
||||||
applyGroupDialogInstances: vi.fn()
|
|
||||||
},
|
|
||||||
appDebug: {
|
|
||||||
debugFriendState: false
|
|
||||||
},
|
|
||||||
getWorldName: vi.fn().mockResolvedValue('World'),
|
|
||||||
getGroupName: vi.fn().mockResolvedValue('Group'),
|
|
||||||
feedStore: {
|
|
||||||
addFeed: vi.fn()
|
|
||||||
},
|
|
||||||
database: {
|
|
||||||
addGPSToDatabase: vi.fn(),
|
|
||||||
addAvatarToDatabase: vi.fn(),
|
|
||||||
addStatusToDatabase: vi.fn(),
|
|
||||||
addBioToDatabase: vi.fn()
|
|
||||||
},
|
|
||||||
avatarStore: {
|
|
||||||
getAvatarName: vi.fn().mockResolvedValue({
|
|
||||||
ownerId: 'usr_owner',
|
|
||||||
avatarName: 'Avatar'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
generalSettingsStore: {
|
|
||||||
logEmptyAvatars: false
|
|
||||||
},
|
|
||||||
checkNote: vi.fn(),
|
|
||||||
now: vi.fn(() => 1000),
|
|
||||||
nowIso: vi.fn(() => '2025-01-01T00:00:00.000Z')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createUserEventCoordinator', () => {
|
|
||||||
test('returns early when target user is not in friend map', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.friendStore.friends.clear();
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(
|
|
||||||
{
|
|
||||||
id: 'usr_404',
|
|
||||||
displayName: 'Unknown'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: ['online', 'offline']
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(deps.feedStore.addFeed).not.toHaveBeenCalled();
|
|
||||||
expect(deps.database.addStatusToDatabase).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updates location counters and dialog instance hooks on location change', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.state.instancePlayerCount.set('loc_old', 2);
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
displayName: 'User 1'
|
|
||||||
};
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(ref, {
|
|
||||||
location: ['loc_new', 'loc_old', 50],
|
|
||||||
state: ['online', 'online']
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.state.instancePlayerCount.get('loc_old')).toBe(1);
|
|
||||||
expect(deps.state.instancePlayerCount.get('loc_new')).toBe(1);
|
|
||||||
expect(deps.applyUserDialogLocation).toHaveBeenCalledWith(true);
|
|
||||||
expect(deps.instanceStore.applyWorldDialogInstances).toHaveBeenCalled();
|
|
||||||
expect(deps.instanceStore.applyGroupDialogInstances).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('writes GPS feed with adjusted traveling time contract', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
displayName: 'User 1',
|
|
||||||
$previousLocation: 'loc_old',
|
|
||||||
$travelingToTime: 900,
|
|
||||||
$location_at: 700
|
|
||||||
};
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(ref, {
|
|
||||||
location: ['loc_new', 'traveling', 300]
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'GPS',
|
|
||||||
userId: 'usr_1',
|
|
||||||
previousLocation: 'loc_old',
|
|
||||||
location: 'loc_new',
|
|
||||||
worldName: 'World',
|
|
||||||
groupName: 'Group',
|
|
||||||
time: 200
|
|
||||||
})
|
|
||||||
);
|
|
||||||
expect(deps.database.addGPSToDatabase).toHaveBeenCalledTimes(1);
|
|
||||||
expect(ref.$previousLocation).toBe('');
|
|
||||||
expect(ref.$travelingToTime).toBe(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('stores previous location while user becomes traveling', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
displayName: 'User 1',
|
|
||||||
$previousLocation: '',
|
|
||||||
$travelingToTime: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(ref, {
|
|
||||||
location: ['traveling', 'loc_old']
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(ref.$previousLocation).toBe('loc_old');
|
|
||||||
expect(ref.$travelingToTime).toBe(1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('writes status and bio feeds and triggers note check', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
displayName: 'User 1',
|
|
||||||
status: 'busy',
|
|
||||||
statusDescription: 'old',
|
|
||||||
currentAvatarImageUrl: '',
|
|
||||||
currentAvatarThumbnailImageUrl: '',
|
|
||||||
currentAvatarTags: [],
|
|
||||||
profilePicOverride: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(ref, {
|
|
||||||
status: ['join me', 'busy'],
|
|
||||||
statusDescription: ['new desc', 'old desc'],
|
|
||||||
bio: ['new bio', 'old bio'],
|
|
||||||
note: ['new note', 'old note']
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledTimes(2);
|
|
||||||
expect(deps.database.addStatusToDatabase).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.database.addBioToDatabase).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.checkNote).toHaveBeenCalledWith('usr_1', 'new note');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('writes avatar change feed contract', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.generalSettingsStore.logEmptyAvatars = true;
|
|
||||||
deps.avatarStore.getAvatarName
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
ownerId: 'usr_owner_new',
|
|
||||||
avatarName: 'Avatar New'
|
|
||||||
})
|
|
||||||
.mockResolvedValueOnce({
|
|
||||||
ownerId: 'usr_owner_old',
|
|
||||||
avatarName: 'Avatar Old'
|
|
||||||
});
|
|
||||||
const coordinator = createUserEventCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
displayName: 'User 1',
|
|
||||||
currentAvatarImageUrl: 'img_old',
|
|
||||||
currentAvatarThumbnailImageUrl: 'thumb_old',
|
|
||||||
currentAvatarTags: ['tag_old'],
|
|
||||||
profilePicOverride: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
await coordinator.runHandleUserUpdateFlow(ref, {
|
|
||||||
currentAvatarImageUrl: ['img_new', 'img_old'],
|
|
||||||
currentAvatarThumbnailImageUrl: ['thumb_new', 'thumb_old'],
|
|
||||||
currentAvatarTags: [['tag_new'], ['tag_old']]
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.database.addAvatarToDatabase).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.feedStore.addFeed).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
type: 'Avatar',
|
|
||||||
userId: 'usr_1',
|
|
||||||
ownerId: 'usr_owner_new',
|
|
||||||
previousOwnerId: 'usr_owner_old',
|
|
||||||
avatarName: 'Avatar New',
|
|
||||||
previousAvatarName: 'Avatar Old',
|
|
||||||
currentAvatarImageUrl: 'img_new',
|
|
||||||
previousCurrentAvatarImageUrl: 'img_old'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
|
|
||||||
import { createUserSessionCoordinator } from '../userSessionCoordinator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {Promise<void>} Promise flush helper.
|
|
||||||
*/
|
|
||||||
async function flushPromises() {
|
|
||||||
await Promise.resolve();
|
|
||||||
await Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns {object} Mock dependencies for user session coordinator.
|
|
||||||
*/
|
|
||||||
function makeDeps() {
|
|
||||||
return {
|
|
||||||
avatarStore: {
|
|
||||||
addAvatarToHistory: vi.fn(),
|
|
||||||
addAvatarWearTime: vi.fn()
|
|
||||||
},
|
|
||||||
gameStore: {
|
|
||||||
isGameRunning: false
|
|
||||||
},
|
|
||||||
groupStore: {
|
|
||||||
applyPresenceGroups: vi.fn()
|
|
||||||
},
|
|
||||||
instanceStore: {
|
|
||||||
applyQueuedInstance: vi.fn()
|
|
||||||
},
|
|
||||||
friendStore: {
|
|
||||||
updateUserCurrentStatus: vi.fn(),
|
|
||||||
updateFriendships: vi.fn()
|
|
||||||
},
|
|
||||||
authStore: {
|
|
||||||
loginComplete: vi.fn()
|
|
||||||
},
|
|
||||||
cachedUsers: {
|
|
||||||
clear: vi.fn()
|
|
||||||
},
|
|
||||||
currentUser: {
|
|
||||||
value: {
|
|
||||||
homeLocation: 'wrld_current'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
userDialog: {
|
|
||||||
value: {
|
|
||||||
visible: false,
|
|
||||||
id: '',
|
|
||||||
$homeLocationName: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getWorldName: vi.fn().mockResolvedValue('World Name'),
|
|
||||||
parseLocation: vi.fn((tag) => ({ tag })),
|
|
||||||
now: vi.fn(() => 111)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('createUserSessionCoordinator', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runAvatarSwapFlow does nothing when user is not logged in', () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createUserSessionCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
currentAvatar: 'avtr_old',
|
|
||||||
$previousAvatarSwapTime: null
|
|
||||||
};
|
|
||||||
|
|
||||||
coordinator.runAvatarSwapFlow({
|
|
||||||
json: { currentAvatar: 'avtr_new' },
|
|
||||||
ref,
|
|
||||||
isLoggedIn: false
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.avatarStore.addAvatarToHistory).not.toHaveBeenCalled();
|
|
||||||
expect(deps.avatarStore.addAvatarWearTime).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runAvatarSwapFlow applies avatar swap side effects when game is running', () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.gameStore.isGameRunning = true;
|
|
||||||
const coordinator = createUserSessionCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
currentAvatar: 'avtr_old',
|
|
||||||
$previousAvatarSwapTime: null
|
|
||||||
};
|
|
||||||
|
|
||||||
coordinator.runAvatarSwapFlow({
|
|
||||||
json: { currentAvatar: 'avtr_new' },
|
|
||||||
ref,
|
|
||||||
isLoggedIn: true
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(deps.avatarStore.addAvatarToHistory).toHaveBeenCalledWith(
|
|
||||||
'avtr_new'
|
|
||||||
);
|
|
||||||
expect(deps.avatarStore.addAvatarWearTime).toHaveBeenCalledWith(
|
|
||||||
'avtr_old'
|
|
||||||
);
|
|
||||||
expect(ref.$previousAvatarSwapTime).toBe(111);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runFirstLoginFlow clears cache, updates currentUser and completes login', () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.gameStore.isGameRunning = true;
|
|
||||||
const coordinator = createUserSessionCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
$previousAvatarSwapTime: null
|
|
||||||
};
|
|
||||||
|
|
||||||
coordinator.runFirstLoginFlow(ref);
|
|
||||||
|
|
||||||
expect(deps.cachedUsers.clear).toHaveBeenCalledTimes(1);
|
|
||||||
expect(deps.currentUser.value).toBe(ref);
|
|
||||||
expect(deps.authStore.loginComplete).toHaveBeenCalledTimes(1);
|
|
||||||
expect(ref.$previousAvatarSwapTime).toBe(111);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runPostApplySyncFlow applies cross-store synchronization', () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
const coordinator = createUserSessionCoordinator(deps);
|
|
||||||
const ref = { queuedInstance: 'wrld_1:123' };
|
|
||||||
|
|
||||||
coordinator.runPostApplySyncFlow(ref);
|
|
||||||
|
|
||||||
expect(deps.groupStore.applyPresenceGroups).toHaveBeenCalledWith(ref);
|
|
||||||
expect(deps.instanceStore.applyQueuedInstance).toHaveBeenCalledWith(
|
|
||||||
'wrld_1:123'
|
|
||||||
);
|
|
||||||
expect(deps.friendStore.updateUserCurrentStatus).toHaveBeenCalledWith(
|
|
||||||
ref
|
|
||||||
);
|
|
||||||
expect(deps.friendStore.updateFriendships).toHaveBeenCalledWith(ref);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('runHomeLocationSyncFlow updates home location and visible dialog name', async () => {
|
|
||||||
const deps = makeDeps();
|
|
||||||
deps.currentUser.value.homeLocation = 'wrld_home';
|
|
||||||
deps.userDialog.value.visible = true;
|
|
||||||
deps.userDialog.value.id = 'usr_1';
|
|
||||||
const coordinator = createUserSessionCoordinator(deps);
|
|
||||||
const ref = {
|
|
||||||
id: 'usr_1',
|
|
||||||
homeLocation: 'wrld_home',
|
|
||||||
$homeLocation: { tag: 'wrld_other' }
|
|
||||||
};
|
|
||||||
|
|
||||||
coordinator.runHomeLocationSyncFlow(ref);
|
|
||||||
await flushPromises();
|
|
||||||
|
|
||||||
expect(deps.parseLocation).toHaveBeenCalledWith('wrld_home');
|
|
||||||
expect(ref.$homeLocation).toEqual({ tag: 'wrld_home' });
|
|
||||||
expect(deps.getWorldName).toHaveBeenCalledWith('wrld_home');
|
|
||||||
expect(deps.userDialog.value.$homeLocationName).toBe('World Name');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -52,31 +52,28 @@ export async function runHandleAutoLoginFlow({
|
|||||||
}
|
}
|
||||||
autoLoginAttempts.add(currentTimestamp);
|
autoLoginAttempts.add(currentTimestamp);
|
||||||
console.log('Attempting automatic login...');
|
console.log('Attempting automatic login...');
|
||||||
authStore
|
try {
|
||||||
.relogin(user)
|
await authStore.relogin(user);
|
||||||
.then(() => {
|
if (AppDebug.errorNoty) {
|
||||||
if (AppDebug.errorNoty) {
|
toast.dismiss(AppDebug.errorNoty);
|
||||||
toast.dismiss(AppDebug.errorNoty);
|
}
|
||||||
}
|
AppDebug.errorNoty = toast.success(
|
||||||
AppDebug.errorNoty = toast.success(
|
t('message.auth.auto_login_success')
|
||||||
t('message.auth.auto_login_success')
|
);
|
||||||
);
|
console.log('Automatically logged in.');
|
||||||
console.log('Automatically logged in.');
|
} catch (err) {
|
||||||
})
|
if (AppDebug.errorNoty) {
|
||||||
.catch((err) => {
|
toast.dismiss(AppDebug.errorNoty);
|
||||||
if (AppDebug.errorNoty) {
|
}
|
||||||
toast.dismiss(AppDebug.errorNoty);
|
AppDebug.errorNoty = toast.error(
|
||||||
}
|
t('message.auth.auto_login_failed')
|
||||||
AppDebug.errorNoty = toast.error(
|
);
|
||||||
t('message.auth.auto_login_failed')
|
console.error('Failed to login automatically.', err);
|
||||||
);
|
} finally {
|
||||||
console.error('Failed to login automatically.', err);
|
authStore.setAttemptingAutoLogin(false);
|
||||||
})
|
if (!isOnline()) {
|
||||||
.finally(() => {
|
AppDebug.errorNoty = toast.error(t('message.auth.offline'));
|
||||||
authStore.setAttemptingAutoLogin(false);
|
console.error(`You're offline.`);
|
||||||
if (!isOnline()) {
|
}
|
||||||
AppDebug.errorNoty = toast.error(t('message.auth.offline'));
|
}
|
||||||
console.error(`You're offline.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,331 @@
|
|||||||
|
import { i18n } from '../plugin/i18n';
|
||||||
|
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
import { friendRequest } from '../api';
|
import { friendRequest, userRequest } from '../api';
|
||||||
|
import { getNameColour } from '../shared/utils';
|
||||||
import { handleFavoriteDelete } from './favoriteCoordinator';
|
import { handleFavoriteDelete } from './favoriteCoordinator';
|
||||||
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
||||||
import { useFavoriteStore } from '../stores/favorite';
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
import { useFriendStore } from '../stores/friend';
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useModalStore } from '../stores/modal';
|
||||||
import { useNotificationStore } from '../stores/notification';
|
import { useNotificationStore } from '../stores/notification';
|
||||||
import { useSharedFeedStore } from '../stores/sharedFeed';
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} args
|
||||||
|
*/
|
||||||
|
export function handleFriendStatus(args) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const D = userStore.userDialog;
|
||||||
|
if (D.visible === false || D.id !== args.params.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { json } = args;
|
||||||
|
D.isFriend = json.isFriend;
|
||||||
|
D.incomingRequest = json.incomingRequest;
|
||||||
|
D.outgoingRequest = json.outgoingRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} args
|
||||||
|
*/
|
||||||
|
export function handleFriendDelete(args) {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const D = userStore.userDialog;
|
||||||
|
if (D.visible === false || D.id !== args.params.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
D.isFriend = false;
|
||||||
|
runDeleteFriendshipFlow(args.params.userId);
|
||||||
|
friendStore.deleteFriend(args.params.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} args
|
||||||
|
*/
|
||||||
|
export function handleFriendAdd(args) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
addFriendship(args.params.userId);
|
||||||
|
friendStore.addFriend(args.params.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getFriendRequest(userId) {
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const array = notificationStore.notificationTable.data;
|
||||||
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
|
if (
|
||||||
|
array[i].type === 'friendRequest' &&
|
||||||
|
array[i].senderUserId === userId
|
||||||
|
) {
|
||||||
|
return array[i].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} userId
|
||||||
|
*/
|
||||||
|
function deleteFriendRequest(userId) {
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const array = notificationStore.notificationTable.data;
|
||||||
|
for (let i = array.length - 1; i >= 0; i--) {
|
||||||
|
if (
|
||||||
|
array[i].type === 'friendRequest' &&
|
||||||
|
array[i].senderUserId === userId
|
||||||
|
) {
|
||||||
|
array.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function addFriendship(id) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
|
||||||
|
const { friendLog, friendLogTable, state } = friendStore;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!watchState.isFriendsLoaded ||
|
||||||
|
friendLog.has(id) ||
|
||||||
|
id === userStore.currentUser.id
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ref = userStore.cachedUsers.get(id);
|
||||||
|
if (typeof ref === 'undefined') {
|
||||||
|
// deleted account on friends list
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
friendRequest
|
||||||
|
.getFriendStatus({
|
||||||
|
userId: id,
|
||||||
|
currentUserId: userStore.currentUser.id
|
||||||
|
})
|
||||||
|
.then((args) => {
|
||||||
|
if (args.params.currentUserId !== userStore.currentUser.id) {
|
||||||
|
// safety check for delayed response
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleFriendStatus(args);
|
||||||
|
if (args.json.isFriend && !friendLog.has(id)) {
|
||||||
|
if (state.friendNumber === 0) {
|
||||||
|
state.friendNumber = friendStore.friends.size;
|
||||||
|
}
|
||||||
|
ref.$friendNumber = ++state.friendNumber;
|
||||||
|
configRepository.setInt(
|
||||||
|
`VRCX_friendNumber_${userStore.currentUser.id}`,
|
||||||
|
state.friendNumber
|
||||||
|
);
|
||||||
|
friendStore.addFriend(id, ref.state);
|
||||||
|
const friendLogHistory = {
|
||||||
|
created_at: new Date().toJSON(),
|
||||||
|
type: 'Friend',
|
||||||
|
userId: id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLogTable.value.data.push(friendLogHistory);
|
||||||
|
database.addFriendLogHistory(friendLogHistory);
|
||||||
|
notificationStore.queueFriendLogNoty(friendLogHistory);
|
||||||
|
sharedFeedStore.addEntry(friendLogHistory);
|
||||||
|
const friendLogCurrent = {
|
||||||
|
userId: id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
trustLevel: ref.$trustLevel,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLog.set(id, friendLogCurrent);
|
||||||
|
database.setFriendLogCurrent(friendLogCurrent);
|
||||||
|
uiStore.notifyMenu('friend-log');
|
||||||
|
deleteFriendRequest(id);
|
||||||
|
userRequest
|
||||||
|
.getUser({
|
||||||
|
userId: id
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (
|
||||||
|
userStore.userDialog.visible &&
|
||||||
|
id === userStore.userDialog.id
|
||||||
|
) {
|
||||||
|
userStore.applyUserDialogLocation(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function updateFriendship(ref) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
|
const uiStore = useUiStore();
|
||||||
|
|
||||||
|
const { friendLog, friendLogTable } = friendStore;
|
||||||
|
|
||||||
|
const ctx = friendLog.get(ref.id);
|
||||||
|
if (!watchState.isFriendsLoaded || typeof ctx === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (ctx.friendNumber) {
|
||||||
|
ref.$friendNumber = ctx.friendNumber;
|
||||||
|
}
|
||||||
|
if (!ref.$friendNumber) {
|
||||||
|
ref.$friendNumber = 0; // no null
|
||||||
|
}
|
||||||
|
if (ctx.displayName !== ref.displayName) {
|
||||||
|
if (ctx.displayName) {
|
||||||
|
const friendLogHistoryDisplayName = {
|
||||||
|
created_at: new Date().toJSON(),
|
||||||
|
type: 'DisplayName',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
previousDisplayName: ctx.displayName,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLogTable.value.data.push(friendLogHistoryDisplayName);
|
||||||
|
database.addFriendLogHistory(friendLogHistoryDisplayName);
|
||||||
|
notificationStore.queueFriendLogNoty(
|
||||||
|
friendLogHistoryDisplayName
|
||||||
|
);
|
||||||
|
sharedFeedStore.addEntry(friendLogHistoryDisplayName);
|
||||||
|
const friendLogCurrent = {
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
trustLevel: ref.$trustLevel,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLog.set(ref.id, friendLogCurrent);
|
||||||
|
database.setFriendLogCurrent(friendLogCurrent);
|
||||||
|
ctx.displayName = ref.displayName;
|
||||||
|
uiStore.notifyMenu('friend-log');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ref.$trustLevel &&
|
||||||
|
ctx.trustLevel &&
|
||||||
|
ctx.trustLevel !== ref.$trustLevel
|
||||||
|
) {
|
||||||
|
if (
|
||||||
|
(ctx.trustLevel === 'Trusted User' &&
|
||||||
|
ref.$trustLevel === 'Veteran User') ||
|
||||||
|
(ctx.trustLevel === 'Veteran User' &&
|
||||||
|
ref.$trustLevel === 'Trusted User')
|
||||||
|
) {
|
||||||
|
const friendLogCurrent3 = {
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
trustLevel: ref.$trustLevel,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLog.set(ref.id, friendLogCurrent3);
|
||||||
|
database.setFriendLogCurrent(friendLogCurrent3);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const friendLogHistoryTrustLevel = {
|
||||||
|
created_at: new Date().toJSON(),
|
||||||
|
type: 'TrustLevel',
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
trustLevel: ref.$trustLevel,
|
||||||
|
previousTrustLevel: ctx.trustLevel,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLogTable.value.data.push(friendLogHistoryTrustLevel);
|
||||||
|
database.addFriendLogHistory(friendLogHistoryTrustLevel);
|
||||||
|
notificationStore.queueFriendLogNoty(friendLogHistoryTrustLevel);
|
||||||
|
sharedFeedStore.addEntry(friendLogHistoryTrustLevel);
|
||||||
|
const friendLogCurrent2 = {
|
||||||
|
userId: ref.id,
|
||||||
|
displayName: ref.displayName,
|
||||||
|
trustLevel: ref.$trustLevel,
|
||||||
|
friendNumber: ref.$friendNumber
|
||||||
|
};
|
||||||
|
friendLog.set(ref.id, friendLogCurrent2);
|
||||||
|
database.setFriendLogCurrent(friendLogCurrent2);
|
||||||
|
uiStore.notifyMenu('friend-log');
|
||||||
|
}
|
||||||
|
ctx.trustLevel = ref.$trustLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function confirmDeleteFriend(id) {
|
||||||
|
const t = i18n.global.t;
|
||||||
|
const modalStore = useModalStore();
|
||||||
|
modalStore
|
||||||
|
.confirm({
|
||||||
|
description: t('confirm.unfriend'),
|
||||||
|
title: t('confirm.title')
|
||||||
|
})
|
||||||
|
.then(async ({ ok }) => {
|
||||||
|
if (!ok) return;
|
||||||
|
const args = await friendRequest.deleteFriend({
|
||||||
|
userId: id
|
||||||
|
});
|
||||||
|
handleFriendDelete(args);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function userOnFriend(ref) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
updateFriendship(ref);
|
||||||
|
if (
|
||||||
|
watchState.isFriendsLoaded &&
|
||||||
|
ref.isFriend &&
|
||||||
|
!friendStore.friendLog.has(ref.id) &&
|
||||||
|
ref.id !== userStore.currentUser.id
|
||||||
|
) {
|
||||||
|
addFriendship(ref.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function updateUserCurrentStatus(ref) {
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
|
|
||||||
|
if (watchState.isFriendsLoaded) {
|
||||||
|
friendStore.refreshFriendsStatus(ref);
|
||||||
|
}
|
||||||
|
friendStore.updateOnlineFriendCounter();
|
||||||
|
|
||||||
|
if (appearanceSettingsStore.randomUserColours) {
|
||||||
|
getNameColour(userStore.currentUser.id).then((colour) => {
|
||||||
|
userStore.setCurrentUserColour(colour);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and applies unfriend transition side effects.
|
* Validates and applies unfriend transition side effects.
|
||||||
@@ -43,7 +361,7 @@ export function runDeleteFriendshipFlow(
|
|||||||
// safety check for delayed response
|
// safety check for delayed response
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
friendStore.handleFriendStatus(args);
|
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(),
|
||||||
@@ -84,7 +402,7 @@ export function runUpdateFriendshipsFlow(
|
|||||||
const set = new Set();
|
const set = new Set();
|
||||||
for (id of ref.friends) {
|
for (id of ref.friends) {
|
||||||
set.add(id);
|
set.add(id);
|
||||||
friendStore.addFriendship(id);
|
addFriendship(id);
|
||||||
}
|
}
|
||||||
for (id of friendLog.keys()) {
|
for (id of friendLog.keys()) {
|
||||||
if (id === userStore.currentUser.id) {
|
if (id === userStore.currentUser.id) {
|
||||||
|
|||||||
@@ -0,0 +1,582 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createJoinLeaveEntry,
|
||||||
|
createLocationEntry,
|
||||||
|
createPortalSpawnEntry,
|
||||||
|
createResourceLoadEntry,
|
||||||
|
findUserByDisplayName,
|
||||||
|
parseLocation,
|
||||||
|
parseInventoryFromUrl,
|
||||||
|
parsePrintFromUrl,
|
||||||
|
replaceBioSymbols
|
||||||
|
} from '../shared/utils';
|
||||||
|
import { i18n } from '../plugin/i18n';
|
||||||
|
import { AppDebug } from '../service/appConfig';
|
||||||
|
import { database } from '../service/database';
|
||||||
|
import { runLastLocationResetFlow, runUpdateCurrentUserLocationFlow } from './locationCoordinator';
|
||||||
|
import { getGroupName } from '../shared/utils';
|
||||||
|
import { userRequest } from '../api';
|
||||||
|
import { watchState } from '../service/watchState';
|
||||||
|
import { toast } from 'vue-sonner';
|
||||||
|
|
||||||
|
import { useAdvancedSettingsStore } from '../stores/settings/advanced';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useGalleryStore } from '../stores/gallery';
|
||||||
|
import { useGameStore } from '../stores/game';
|
||||||
|
import { useGameLogStore } from '../stores/gameLog';
|
||||||
|
import { useGeneralSettingsStore } from '../stores/settings/general';
|
||||||
|
import { useInstanceStore } from '../stores/instance';
|
||||||
|
import { useLocationStore } from '../stores/location';
|
||||||
|
import { useModalStore } from '../stores/modal';
|
||||||
|
import { useNotificationStore } from '../stores/notification';
|
||||||
|
import { usePhotonStore } from '../stores/photon';
|
||||||
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { useVrStore } from '../stores/vr';
|
||||||
|
import { useVrcxStore } from '../stores/vrcx';
|
||||||
|
|
||||||
|
import gameLogService from '../service/gameLog.js';
|
||||||
|
|
||||||
|
import * as workerTimers from 'worker-timers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the player list from game log history and syncs it to
|
||||||
|
* locationStore, instanceStore, vrStore, and userStore.
|
||||||
|
*/
|
||||||
|
export async function tryLoadPlayerList() {
|
||||||
|
const gameStore = useGameStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
const vrStore = useVrStore();
|
||||||
|
|
||||||
|
if (!gameStore.isGameRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('Loading player list from game log...');
|
||||||
|
let ctx;
|
||||||
|
let i;
|
||||||
|
const data = await database.getGamelogDatabase();
|
||||||
|
if (data.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let length = 0;
|
||||||
|
for (i = data.length - 1; i > -1; i--) {
|
||||||
|
ctx = data[i];
|
||||||
|
if (ctx.type === 'Location') {
|
||||||
|
locationStore.setLastLocation({
|
||||||
|
date: Date.parse(ctx.created_at),
|
||||||
|
location: ctx.location,
|
||||||
|
name: ctx.worldName,
|
||||||
|
playerList: new Map(),
|
||||||
|
friendList: new Map()
|
||||||
|
});
|
||||||
|
length = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (length > 0) {
|
||||||
|
for (i = length + 1; i < data.length; i++) {
|
||||||
|
ctx = data[i];
|
||||||
|
if (ctx.type === 'OnPlayerJoined') {
|
||||||
|
if (!ctx.userId) {
|
||||||
|
ctx.userId =
|
||||||
|
findUserByDisplayName(
|
||||||
|
userStore.cachedUsers,
|
||||||
|
ctx.displayName
|
||||||
|
)?.id ?? '';
|
||||||
|
}
|
||||||
|
const userMap = {
|
||||||
|
displayName: ctx.displayName,
|
||||||
|
userId: ctx.userId,
|
||||||
|
joinTime: Date.parse(ctx.created_at),
|
||||||
|
lastAvatar: ''
|
||||||
|
};
|
||||||
|
locationStore.lastLocation.playerList.set(
|
||||||
|
ctx.userId,
|
||||||
|
userMap
|
||||||
|
);
|
||||||
|
if (friendStore.friends.has(ctx.userId)) {
|
||||||
|
locationStore.lastLocation.friendList.set(
|
||||||
|
ctx.userId,
|
||||||
|
userMap
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ctx.type === 'OnPlayerLeft') {
|
||||||
|
locationStore.lastLocation.playerList.delete(ctx.userId);
|
||||||
|
locationStore.lastLocation.friendList.delete(ctx.userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locationStore.lastLocation.playerList.forEach((ref1) => {
|
||||||
|
if (
|
||||||
|
ref1.userId &&
|
||||||
|
typeof ref1.userId === 'string' &&
|
||||||
|
!userStore.cachedUsers.has(ref1.userId)
|
||||||
|
) {
|
||||||
|
userRequest.getUser({ userId: ref1.userId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
runUpdateCurrentUserLocationFlow();
|
||||||
|
instanceStore.updateCurrentInstanceWorld();
|
||||||
|
vrStore.updateVRLastLocation();
|
||||||
|
instanceStore.getCurrentInstanceUserList();
|
||||||
|
userStore.applyUserDialogLocation();
|
||||||
|
instanceStore.applyWorldDialogInstances();
|
||||||
|
instanceStore.applyGroupDialogInstances();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core game log entry processor. Dispatches game log events to the
|
||||||
|
* appropriate stores based on type.
|
||||||
|
*
|
||||||
|
* @param {object} gameLog
|
||||||
|
* @param {string} location
|
||||||
|
*/
|
||||||
|
export function addGameLogEntry(gameLog, location) {
|
||||||
|
const gameLogStore = useGameLogStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const vrStore = useVrStore();
|
||||||
|
const gameStore = useGameStore();
|
||||||
|
const vrcxStore = useVrcxStore();
|
||||||
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||||
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
|
const galleryStore = useGalleryStore();
|
||||||
|
const photonStore = usePhotonStore();
|
||||||
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
|
const notificationStore = useNotificationStore();
|
||||||
|
|
||||||
|
let entry = undefined;
|
||||||
|
if (advancedSettingsStore.gameLogDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let userId = String(gameLog.userId || '');
|
||||||
|
if (!userId && gameLog.displayName) {
|
||||||
|
userId =
|
||||||
|
findUserByDisplayName(
|
||||||
|
userStore.cachedUsers,
|
||||||
|
gameLog.displayName
|
||||||
|
)?.id ?? '';
|
||||||
|
}
|
||||||
|
switch (gameLog.type) {
|
||||||
|
case 'location-destination':
|
||||||
|
if (gameStore.isGameRunning) {
|
||||||
|
gameLogStore.addGameLog({
|
||||||
|
created_at: gameLog.dt,
|
||||||
|
type: 'LocationDestination',
|
||||||
|
location: gameLog.location
|
||||||
|
});
|
||||||
|
runLastLocationResetFlow(gameLog.dt);
|
||||||
|
locationStore.setLastLocationLocation('traveling');
|
||||||
|
locationStore.setLastLocationDestination(gameLog.location);
|
||||||
|
locationStore.setLastLocationDestinationTime(
|
||||||
|
Date.parse(gameLog.dt)
|
||||||
|
);
|
||||||
|
gameLogStore.state.lastLocationAvatarList.clear();
|
||||||
|
instanceStore.removeQueuedInstance(gameLog.location);
|
||||||
|
runUpdateCurrentUserLocationFlow();
|
||||||
|
gameLogStore.clearNowPlaying();
|
||||||
|
instanceStore.updateCurrentInstanceWorld();
|
||||||
|
userStore.applyUserDialogLocation();
|
||||||
|
instanceStore.applyWorldDialogInstances();
|
||||||
|
instanceStore.applyGroupDialogInstances();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'location':
|
||||||
|
instanceStore.addInstanceJoinHistory(
|
||||||
|
locationStore.lastLocation.location,
|
||||||
|
gameLog.dt
|
||||||
|
);
|
||||||
|
const worldName = replaceBioSymbols(gameLog.worldName);
|
||||||
|
if (gameStore.isGameRunning) {
|
||||||
|
runLastLocationResetFlow(gameLog.dt);
|
||||||
|
gameLogStore.clearNowPlaying();
|
||||||
|
locationStore.setLastLocation({
|
||||||
|
date: Date.parse(gameLog.dt),
|
||||||
|
location: gameLog.location,
|
||||||
|
name: worldName,
|
||||||
|
playerList: new Map(),
|
||||||
|
friendList: new Map()
|
||||||
|
});
|
||||||
|
instanceStore.removeQueuedInstance(gameLog.location);
|
||||||
|
runUpdateCurrentUserLocationFlow();
|
||||||
|
vrStore.updateVRLastLocation();
|
||||||
|
instanceStore.updateCurrentInstanceWorld();
|
||||||
|
userStore.applyUserDialogLocation();
|
||||||
|
instanceStore.applyWorldDialogInstances();
|
||||||
|
instanceStore.applyGroupDialogInstances();
|
||||||
|
}
|
||||||
|
instanceStore.addInstanceJoinHistory(
|
||||||
|
gameLog.location,
|
||||||
|
gameLog.dt
|
||||||
|
);
|
||||||
|
const L = parseLocation(gameLog.location);
|
||||||
|
entry = createLocationEntry(
|
||||||
|
gameLog.dt,
|
||||||
|
gameLog.location,
|
||||||
|
L.worldId,
|
||||||
|
worldName
|
||||||
|
);
|
||||||
|
getGroupName(gameLog.location).then((groupName) => {
|
||||||
|
entry.groupName = groupName;
|
||||||
|
});
|
||||||
|
gameLogStore.addGamelogLocationToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'player-joined':
|
||||||
|
const joinTime = Date.parse(gameLog.dt);
|
||||||
|
const userMap = {
|
||||||
|
displayName: gameLog.displayName,
|
||||||
|
userId,
|
||||||
|
joinTime,
|
||||||
|
lastAvatar: ''
|
||||||
|
};
|
||||||
|
locationStore.lastLocation.playerList.set(userId, userMap);
|
||||||
|
const ref = userStore.cachedUsers.get(userId);
|
||||||
|
if (!userId) {
|
||||||
|
console.error('Missing userId:', gameLog.displayName);
|
||||||
|
} else if (userId === userStore.currentUser.id) {
|
||||||
|
// skip
|
||||||
|
} else if (
|
||||||
|
friendStore.friends.has(userId) &&
|
||||||
|
typeof ref !== 'undefined'
|
||||||
|
) {
|
||||||
|
locationStore.lastLocation.friendList.set(userId, userMap);
|
||||||
|
if (
|
||||||
|
ref.location !== locationStore.lastLocation.location &&
|
||||||
|
ref.travelingToLocation !==
|
||||||
|
locationStore.lastLocation.location
|
||||||
|
) {
|
||||||
|
ref.$location_at = joinTime;
|
||||||
|
}
|
||||||
|
} else if (typeof ref !== 'undefined') {
|
||||||
|
ref.$location_at = joinTime;
|
||||||
|
} else {
|
||||||
|
if (AppDebug.debugGameLog || AppDebug.debugWebRequests) {
|
||||||
|
console.log('Fetching user from gameLog:', userId);
|
||||||
|
}
|
||||||
|
userRequest.getUser({ userId });
|
||||||
|
}
|
||||||
|
vrStore.updateVRLastLocation();
|
||||||
|
instanceStore.getCurrentInstanceUserList();
|
||||||
|
entry = createJoinLeaveEntry(
|
||||||
|
'OnPlayerJoined',
|
||||||
|
gameLog.dt,
|
||||||
|
gameLog.displayName,
|
||||||
|
location,
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
database.addGamelogJoinLeaveToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'player-left':
|
||||||
|
const ref1 = locationStore.lastLocation.playerList.get(userId);
|
||||||
|
if (typeof ref1 === 'undefined') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const time = dayjs(gameLog.dt) - ref1.joinTime;
|
||||||
|
locationStore.lastLocation.playerList.delete(userId);
|
||||||
|
locationStore.lastLocation.friendList.delete(userId);
|
||||||
|
gameLogStore.state.lastLocationAvatarList.delete(gameLog.displayName);
|
||||||
|
photonStore.photonLobbyAvatars.delete(userId);
|
||||||
|
vrStore.updateVRLastLocation();
|
||||||
|
instanceStore.getCurrentInstanceUserList();
|
||||||
|
entry = createJoinLeaveEntry(
|
||||||
|
'OnPlayerLeft',
|
||||||
|
gameLog.dt,
|
||||||
|
gameLog.displayName,
|
||||||
|
location,
|
||||||
|
userId,
|
||||||
|
time
|
||||||
|
);
|
||||||
|
database.addGamelogJoinLeaveToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'portal-spawn':
|
||||||
|
if (vrcxStore.ipcEnabled && gameStore.isGameRunning) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entry = createPortalSpawnEntry(gameLog.dt, location);
|
||||||
|
database.addGamelogPortalSpawnToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'video-play':
|
||||||
|
gameLog.videoUrl = decodeURI(gameLog.videoUrl);
|
||||||
|
if (gameLogStore.lastVideoUrl === gameLog.videoUrl) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gameLogStore.lastVideoUrl = gameLog.videoUrl;
|
||||||
|
gameLogStore.addGameLogVideo(gameLog, location, userId);
|
||||||
|
break;
|
||||||
|
case 'video-sync':
|
||||||
|
const timestamp = gameLog.timestamp.replace(/,/g, '');
|
||||||
|
if (gameLogStore.nowPlaying.playing) {
|
||||||
|
gameLogStore.nowPlaying.offset = parseInt(timestamp, 10);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'resource-load-string':
|
||||||
|
case 'resource-load-image':
|
||||||
|
if (
|
||||||
|
!generalSettingsStore.logResourceLoad ||
|
||||||
|
gameLogStore.lastResourceloadUrl === gameLog.resourceUrl
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gameLogStore.lastResourceloadUrl = gameLog.resourceUrl;
|
||||||
|
entry = createResourceLoadEntry(
|
||||||
|
gameLog.type,
|
||||||
|
gameLog.dt,
|
||||||
|
gameLog.resourceUrl,
|
||||||
|
location
|
||||||
|
);
|
||||||
|
database.addGamelogResourceLoadToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'screenshot':
|
||||||
|
vrcxStore.processScreenshot(gameLog.screenshotPath);
|
||||||
|
break;
|
||||||
|
case 'api-request':
|
||||||
|
if (AppDebug.debugWebRequests) {
|
||||||
|
console.log('API Request:', gameLog.url);
|
||||||
|
}
|
||||||
|
if (advancedSettingsStore.saveInstanceEmoji) {
|
||||||
|
const inv = parseInventoryFromUrl(gameLog.url);
|
||||||
|
if (inv) {
|
||||||
|
galleryStore.queueCheckInstanceInventory(
|
||||||
|
inv.inventoryId,
|
||||||
|
inv.userId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (advancedSettingsStore.saveInstancePrints) {
|
||||||
|
const printId = parsePrintFromUrl(gameLog.url);
|
||||||
|
if (printId) {
|
||||||
|
galleryStore.queueSavePrintToFile(printId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'avatar-change':
|
||||||
|
if (!gameStore.isGameRunning) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let avatarName = gameLogStore.state.lastLocationAvatarList.get(
|
||||||
|
gameLog.displayName
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
photonStore.photonLoggingEnabled ||
|
||||||
|
avatarName === gameLog.avatarName
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!avatarName) {
|
||||||
|
avatarName = gameLog.avatarName;
|
||||||
|
gameLogStore.state.lastLocationAvatarList.set(
|
||||||
|
gameLog.displayName,
|
||||||
|
avatarName
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
avatarName = gameLog.avatarName;
|
||||||
|
gameLogStore.state.lastLocationAvatarList.set(
|
||||||
|
gameLog.displayName,
|
||||||
|
avatarName
|
||||||
|
);
|
||||||
|
entry = {
|
||||||
|
created_at: gameLog.dt,
|
||||||
|
type: 'AvatarChange',
|
||||||
|
userId,
|
||||||
|
name: avatarName,
|
||||||
|
displayName: gameLog.displayName
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case 'vrcx':
|
||||||
|
const type = gameLog.data.substr(0, gameLog.data.indexOf(' '));
|
||||||
|
if (type === 'VideoPlay(PyPyDance)') {
|
||||||
|
gameLogStore.addGameLogPyPyDance(gameLog, location);
|
||||||
|
} else if (type === 'VideoPlay(VRDancing)') {
|
||||||
|
gameLogStore.addGameLogVRDancing(gameLog, location);
|
||||||
|
} else if (type === 'VideoPlay(ZuwaZuwaDance)') {
|
||||||
|
gameLogStore.addGameLogZuwaZuwaDance(gameLog, location);
|
||||||
|
} else if (type === 'LSMedia') {
|
||||||
|
gameLogStore.addGameLogLSMedia(gameLog, location);
|
||||||
|
} else if (type === 'VideoPlay(PopcornPalace)') {
|
||||||
|
gameLogStore.addGameLogPopcornPalace(gameLog, location);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'photon-id':
|
||||||
|
if (!gameStore.isGameRunning || !watchState.isFriendsLoaded) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const photonId = parseInt(gameLog.photonId, 10);
|
||||||
|
const ref2 = photonStore.photonLobby.get(photonId);
|
||||||
|
if (typeof ref2 === 'undefined') {
|
||||||
|
const foundUser = findUserByDisplayName(
|
||||||
|
userStore.cachedUsers,
|
||||||
|
gameLog.displayName
|
||||||
|
);
|
||||||
|
if (foundUser) {
|
||||||
|
photonStore.photonLobby.set(photonId, foundUser);
|
||||||
|
photonStore.photonLobbyCurrent.set(photonId, foundUser);
|
||||||
|
}
|
||||||
|
const ctx1 = {
|
||||||
|
displayName: gameLog.displayName
|
||||||
|
};
|
||||||
|
photonStore.photonLobby.set(photonId, ctx1);
|
||||||
|
photonStore.photonLobbyCurrent.set(photonId, ctx1);
|
||||||
|
instanceStore.getCurrentInstanceUserList();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'notification':
|
||||||
|
break;
|
||||||
|
case 'event':
|
||||||
|
entry = {
|
||||||
|
created_at: gameLog.dt,
|
||||||
|
type: 'Event',
|
||||||
|
data: gameLog.event
|
||||||
|
};
|
||||||
|
database.addGamelogEventToDatabase(entry);
|
||||||
|
break;
|
||||||
|
case 'vrc-quit':
|
||||||
|
if (!gameStore.isGameRunning) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (advancedSettingsStore.vrcQuitFix) {
|
||||||
|
const bias = Date.parse(gameLog.dt) + 3000;
|
||||||
|
if (bias < Date.now()) {
|
||||||
|
console.log('QuitFix: Bias too low, not killing VRC');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
AppApi.QuitGame().then((processCount) => {
|
||||||
|
if (processCount > 1) {
|
||||||
|
console.log(
|
||||||
|
'QuitFix: More than 1 process running, not killing VRC'
|
||||||
|
);
|
||||||
|
} else if (processCount === 1) {
|
||||||
|
console.log('QuitFix: Killed VRC');
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
'QuitFix: Nothing to kill, no VRC process running'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'openvr-init':
|
||||||
|
gameStore.setIsGameNoVR(false);
|
||||||
|
configRepository.setBool('isGameNoVR', gameStore.isGameNoVR);
|
||||||
|
vrStore.updateOpenVR();
|
||||||
|
break;
|
||||||
|
case 'desktop-mode':
|
||||||
|
gameStore.setIsGameNoVR(true);
|
||||||
|
configRepository.setBool('isGameNoVR', gameStore.isGameNoVR);
|
||||||
|
vrStore.updateOpenVR();
|
||||||
|
break;
|
||||||
|
case 'udon-exception':
|
||||||
|
if (generalSettingsStore.udonExceptionLogging) {
|
||||||
|
console.log('UdonException', gameLog.data);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'sticker-spawn':
|
||||||
|
if (!advancedSettingsStore.saveInstanceStickers) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
galleryStore.trySaveStickerToFile(
|
||||||
|
gameLog.displayName,
|
||||||
|
gameLog.userId,
|
||||||
|
gameLog.inventoryId
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (typeof entry !== 'undefined') {
|
||||||
|
sharedFeedStore.addEntry(entry);
|
||||||
|
notificationStore.queueGameLogNoty(entry);
|
||||||
|
gameLogStore.addGameLog(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses raw game log JSON and delegates to addGameLogEntry.
|
||||||
|
* Called from C# / updateLoop.
|
||||||
|
*
|
||||||
|
* @param {string} json
|
||||||
|
*/
|
||||||
|
export function addGameLogEvent(json) {
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
|
||||||
|
const rawLogs = JSON.parse(json);
|
||||||
|
const gameLog = gameLogService.parseRawGameLog(
|
||||||
|
rawLogs[1],
|
||||||
|
rawLogs[2],
|
||||||
|
rawLogs.slice(3)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
AppDebug.debugGameLog &&
|
||||||
|
gameLog.type !== 'photon-id' &&
|
||||||
|
gameLog.type !== 'api-request' &&
|
||||||
|
gameLog.type !== 'udon-exception'
|
||||||
|
) {
|
||||||
|
console.log('gameLog:', gameLog);
|
||||||
|
}
|
||||||
|
addGameLogEntry(gameLog, locationStore.lastLocation.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts game log processing from the database tail.
|
||||||
|
*/
|
||||||
|
export async function getGameLogTable() {
|
||||||
|
await database.initTables();
|
||||||
|
const dateTill = await database.getLastDateGameLogDatabase();
|
||||||
|
await updateGameLog(dateTill);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all game log entries since dateTill and processes them.
|
||||||
|
*
|
||||||
|
* @param {string} dateTill
|
||||||
|
*/
|
||||||
|
async function updateGameLog(dateTill) {
|
||||||
|
await gameLogService.setDateTill(dateTill);
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
workerTimers.setTimeout(resolve, 10000);
|
||||||
|
});
|
||||||
|
let location = '';
|
||||||
|
for (const gameLog of await gameLogService.getAll()) {
|
||||||
|
if (gameLog.type === 'location') {
|
||||||
|
location = gameLog.location;
|
||||||
|
}
|
||||||
|
addGameLogEntry(gameLog, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows confirmation dialog before toggling the game log disabled setting.
|
||||||
|
*/
|
||||||
|
export async function disableGameLogDialog() {
|
||||||
|
const gameStore = useGameStore();
|
||||||
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||||
|
const modalStore = useModalStore();
|
||||||
|
const t = i18n.global.t;
|
||||||
|
|
||||||
|
if (gameStore.isGameRunning) {
|
||||||
|
toast.error(t('message.gamelog.vrchat_must_be_closed'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!advancedSettingsStore.gameLogDisabled) {
|
||||||
|
modalStore
|
||||||
|
.confirm({
|
||||||
|
description: t('confirm.disable_gamelog'),
|
||||||
|
title: t('confirm.title')
|
||||||
|
})
|
||||||
|
.then(({ ok }) => {
|
||||||
|
if (!ok) return;
|
||||||
|
advancedSettingsStore.setGameLogDisabled();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
} else {
|
||||||
|
advancedSettingsStore.setGameLogDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import configRepository from '../service/config';
|
||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
import { runHandleUserUpdateFlow } from './userEventCoordinator';
|
import { runHandleUserUpdateFlow } from './userEventCoordinator';
|
||||||
import { runUpdateCurrentUserLocationFlow } from './locationCoordinator';
|
import { runUpdateCurrentUserLocationFlow } from './locationCoordinator';
|
||||||
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
|
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
|
||||||
|
import { userOnFriend } from './friendRelationshipCoordinator';
|
||||||
import { handleGroupRepresented } from './groupCoordinator';
|
import { handleGroupRepresented } from './groupCoordinator';
|
||||||
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
import { useAppearanceSettingsStore } from '../stores/settings/appearance';
|
||||||
import { useAuthStore } from '../stores/auth';
|
import { useAuthStore } from '../stores/auth';
|
||||||
@@ -56,7 +57,8 @@ import { useSharedFeedStore } from '../stores/sharedFeed';
|
|||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
|
|
||||||
const robotUrl = `${AppDebug.endpointDomain}/file/file_0e8c4e32-7444-44ea-ade4-313c010d4bae/1/file`;
|
const getRobotUrl = () =>
|
||||||
|
`${AppDebug.endpointDomain}/file/file_0e8c4e32-7444-44ea-ade4-313c010d4bae/1/file`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('../types/api/user').GetUserResponse} json
|
* @param {import('../types/api/user').GetUserResponse} json
|
||||||
@@ -76,7 +78,7 @@ export function applyUser(json) {
|
|||||||
let ref = cachedUsers.get(json.id);
|
let ref = cachedUsers.get(json.id);
|
||||||
let hasPropChanged = false;
|
let hasPropChanged = false;
|
||||||
let changedProps = {};
|
let changedProps = {};
|
||||||
sanitizeUserJson(json, robotUrl);
|
sanitizeUserJson(json, getRobotUrl());
|
||||||
if (typeof ref === 'undefined') {
|
if (typeof ref === 'undefined') {
|
||||||
ref = reactive(createDefaultUserRef(json));
|
ref = reactive(createDefaultUserRef(json));
|
||||||
if (locationStore.lastLocation.playerList.has(json.id)) {
|
if (locationStore.lastLocation.playerList.has(json.id)) {
|
||||||
@@ -219,7 +221,7 @@ export function applyUser(json) {
|
|||||||
runUpdateFriendFlow(ref.id, ref.state);
|
runUpdateFriendFlow(ref.id, ref.state);
|
||||||
}
|
}
|
||||||
applyFavorite('friend', ref.id);
|
applyFavorite('friend', ref.id);
|
||||||
friendStore.userOnFriend(ref);
|
userOnFriend(ref);
|
||||||
const D = userDialog;
|
const D = userDialog;
|
||||||
if (D.visible && D.id === ref.id) {
|
if (D.visible && D.id === ref.id) {
|
||||||
D.ref = ref;
|
D.ref = ref;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { getWorldName, parseLocation } from '../shared/utils';
|
import { getWorldName, parseLocation } from '../shared/utils';
|
||||||
import { runUpdateFriendshipsFlow } from './friendRelationshipCoordinator';
|
import {
|
||||||
|
runUpdateFriendshipsFlow,
|
||||||
|
updateUserCurrentStatus
|
||||||
|
} from './friendRelationshipCoordinator';
|
||||||
import { useAuthStore } from '../stores/auth';
|
import { useAuthStore } from '../stores/auth';
|
||||||
import { useAvatarStore } from '../stores/avatar';
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
import { addAvatarToHistory, addAvatarWearTime } from './avatarCoordinator';
|
import { addAvatarToHistory, addAvatarWearTime } from './avatarCoordinator';
|
||||||
@@ -68,7 +71,7 @@ export function runPostApplySyncFlow(ref) {
|
|||||||
|
|
||||||
applyPresenceGroups(ref);
|
applyPresenceGroups(ref);
|
||||||
instanceStore.applyQueuedInstance(ref.queuedInstance);
|
instanceStore.applyQueuedInstance(ref.queuedInstance);
|
||||||
friendStore.updateUserCurrentStatus(ref);
|
updateUserCurrentStatus(ref);
|
||||||
if (typeof ref.friends !== 'undefined') {
|
if (typeof ref.friends !== 'undefined') {
|
||||||
runUpdateFriendshipsFlow(ref);
|
runUpdateFriendshipsFlow(ref);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
|
import { useFriendStore } from '../stores/friend';
|
||||||
|
import { useGalleryStore } from '../stores/gallery';
|
||||||
|
import { useGroupStore } from '../stores/group';
|
||||||
|
import { useInstanceStore } from '../stores/instance';
|
||||||
|
import { useLocationStore } from '../stores/location';
|
||||||
|
import { useUserStore } from '../stores/user';
|
||||||
|
import { useWorldStore } from '../stores/world';
|
||||||
|
import { failedGetRequests } from '../service/request';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears caches across multiple stores while preserving data that is
|
||||||
|
* still needed (friends, current user, favorites, active instances).
|
||||||
|
*/
|
||||||
|
export function clearVRCXCache() {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const worldStore = useWorldStore();
|
||||||
|
const avatarStore = useAvatarStore();
|
||||||
|
const groupStore = useGroupStore();
|
||||||
|
const instanceStore = useInstanceStore();
|
||||||
|
const friendStore = useFriendStore();
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
const locationStore = useLocationStore();
|
||||||
|
const galleryStore = useGalleryStore();
|
||||||
|
|
||||||
|
console.log('Clearing VRCX cache...');
|
||||||
|
failedGetRequests.clear();
|
||||||
|
userStore.cachedUsers.forEach((ref, id) => {
|
||||||
|
if (
|
||||||
|
!friendStore.friends.has(id) &&
|
||||||
|
!locationStore.lastLocation.playerList.has(ref.id) &&
|
||||||
|
id !== userStore.currentUser.id
|
||||||
|
) {
|
||||||
|
userStore.cachedUsers.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
worldStore.cachedWorlds.forEach((ref, id) => {
|
||||||
|
if (
|
||||||
|
!favoriteStore.getCachedFavoritesByObjectId(id) &&
|
||||||
|
ref.authorId !== userStore.currentUser.id &&
|
||||||
|
!favoriteStore.localWorldFavoritesList.includes(id)
|
||||||
|
) {
|
||||||
|
worldStore.cachedWorlds.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatarStore.cachedAvatars.forEach((ref, id) => {
|
||||||
|
if (
|
||||||
|
!favoriteStore.getCachedFavoritesByObjectId(id) &&
|
||||||
|
ref.authorId !== userStore.currentUser.id &&
|
||||||
|
!favoriteStore.localAvatarFavoritesList.includes(id) &&
|
||||||
|
!avatarStore.avatarHistory.includes(id)
|
||||||
|
) {
|
||||||
|
avatarStore.cachedAvatars.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
groupStore.cachedGroups.forEach((ref, id) => {
|
||||||
|
if (!groupStore.currentUserGroups.has(id)) {
|
||||||
|
groupStore.cachedGroups.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instanceStore.cachedInstances.forEach((ref, id) => {
|
||||||
|
if (
|
||||||
|
[...friendStore.friends.values()].some(
|
||||||
|
(f) => f.$location?.tag === id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// delete instances over an hour old
|
||||||
|
if (Date.parse(ref.$fetchedAt) < Date.now() - 3600000) {
|
||||||
|
instanceStore.cachedInstances.delete(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
avatarStore.cachedAvatarNames.clear();
|
||||||
|
userStore.customUserTags.clear();
|
||||||
|
galleryStore.cachedEmoji.clear();
|
||||||
|
}
|
||||||
@@ -19,6 +19,10 @@ import {
|
|||||||
getGroupDialogGroup,
|
getGroupDialogGroup,
|
||||||
handleGroupMember
|
handleGroupMember
|
||||||
} from '../coordinators/groupCoordinator';
|
} from '../coordinators/groupCoordinator';
|
||||||
|
import {
|
||||||
|
handleFriendAdd,
|
||||||
|
handleFriendDelete
|
||||||
|
} from '../coordinators/friendRelationshipCoordinator';
|
||||||
import { escapeTag, parseLocation } from '../shared/utils';
|
import { escapeTag, parseLocation } from '../shared/utils';
|
||||||
import { AppDebug } from './appConfig';
|
import { AppDebug } from './appConfig';
|
||||||
import { groupRequest } from '../api';
|
import { groupRequest } from '../api';
|
||||||
@@ -268,7 +272,7 @@ function handlePipeline(args) {
|
|||||||
|
|
||||||
case 'friend-add':
|
case 'friend-add':
|
||||||
applyUser(content.user);
|
applyUser(content.user);
|
||||||
friendStore.handleFriendAdd({
|
handleFriendAdd({
|
||||||
params: {
|
params: {
|
||||||
userId: content.userId
|
userId: content.userId
|
||||||
}
|
}
|
||||||
@@ -276,7 +280,7 @@ function handlePipeline(args) {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'friend-delete':
|
case 'friend-delete':
|
||||||
friendStore.handleFriendDelete({
|
handleFriendDelete({
|
||||||
params: {
|
params: {
|
||||||
userId: content.userId
|
userId: content.userId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,29 +90,21 @@ const mockGroupStrictsearch = vi.fn();
|
|||||||
|
|
||||||
vi.mock('../user', () => ({
|
vi.mock('../user', () => ({
|
||||||
useUserStore: () => ({
|
useUserStore: () => ({
|
||||||
showUserDialog: mockShowUserDialog,
|
|
||||||
cachedUsers: new Map(),
|
cachedUsers: new Map(),
|
||||||
showUserDialogHistory: new Set(),
|
showUserDialogHistory: new Set(),
|
||||||
currentUser: ref({ id: 'usr_me', homeLocation: '' }),
|
currentUser: ref({ id: 'usr_me', homeLocation: '' })
|
||||||
lookupUser: vi.fn(),
|
|
||||||
applyUser: vi.fn()
|
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
vi.mock('../avatar', () => ({
|
vi.mock('../avatar', () => ({
|
||||||
useAvatarStore: () => ({
|
useAvatarStore: () => ({})
|
||||||
showAvatarDialog: mockShowAvatarDialog
|
|
||||||
})
|
|
||||||
}));
|
}));
|
||||||
vi.mock('../group', () => ({
|
vi.mock('../group', () => ({
|
||||||
useGroupStore: () => ({
|
useGroupStore: () => ({})
|
||||||
showGroupDialog: mockShowGroupDialog
|
|
||||||
})
|
|
||||||
}));
|
}));
|
||||||
vi.mock('../world', () => ({
|
vi.mock('../world', () => ({
|
||||||
useWorldStore: () => ({
|
useWorldStore: () => ({})
|
||||||
showWorldDialog: mockShowWorldDialog
|
|
||||||
})
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('../friend', () => ({
|
vi.mock('../friend', () => ({
|
||||||
useFriendStore: () => ({
|
useFriendStore: () => ({
|
||||||
friends: new Map()
|
friends: new Map()
|
||||||
@@ -141,12 +133,29 @@ function makeApiMock() {
|
|||||||
groupRequest: {
|
groupRequest: {
|
||||||
groupStrictsearch: (...args) => mockGroupStrictsearch(...args)
|
groupStrictsearch: (...args) => mockGroupStrictsearch(...args)
|
||||||
},
|
},
|
||||||
|
queryRequest: {},
|
||||||
miscRequest: {}
|
miscRequest: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
vi.mock('../../api', () => makeApiMock());
|
vi.mock('../../api', () => makeApiMock());
|
||||||
vi.mock('../../api/', () => makeApiMock());
|
vi.mock('../../api/', () => makeApiMock());
|
||||||
|
|
||||||
|
vi.mock('../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mockShowUserDialog(...args),
|
||||||
|
lookupUser: vi.fn(),
|
||||||
|
applyUser: vi.fn()
|
||||||
|
}));
|
||||||
|
vi.mock('../../coordinators/avatarCoordinator', () => ({
|
||||||
|
showAvatarDialog: (...args) => mockShowAvatarDialog(...args),
|
||||||
|
getAvatarName: vi.fn()
|
||||||
|
}));
|
||||||
|
vi.mock('../../coordinators/groupCoordinator', () => ({
|
||||||
|
showGroupDialog: (...args) => mockShowGroupDialog(...args)
|
||||||
|
}));
|
||||||
|
vi.mock('../../coordinators/worldCoordinator', () => ({
|
||||||
|
showWorldDialog: (...args) => mockShowWorldDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('vue-sonner', () => ({
|
vi.mock('vue-sonner', () => ({
|
||||||
toast: {
|
toast: {
|
||||||
success: vi.fn(),
|
success: vi.fn(),
|
||||||
|
|||||||
+67
-97
@@ -39,9 +39,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
autoLoginAttempts: new Set(),
|
autoLoginAttempts: new Set()
|
||||||
enableCustomEndpoint: false,
|
|
||||||
cachedConfig: {}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginForm = ref({
|
const loginForm = ref({
|
||||||
@@ -106,12 +104,12 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function init() {
|
async function init() {
|
||||||
const [lastUserLoggedIn, enableCustomEndpoint] = await Promise.all([
|
const [lastUserLoggedIn, savedEnableCustomEndpoint] = await Promise.all([
|
||||||
configRepository.getString('lastUserLoggedIn', ''),
|
configRepository.getString('lastUserLoggedIn', ''),
|
||||||
configRepository.getBool('VRCX_enableCustomEndpoint', false)
|
configRepository.getBool('VRCX_enableCustomEndpoint', false)
|
||||||
]);
|
]);
|
||||||
loginForm.value.lastUserLoggedIn = lastUserLoggedIn;
|
loginForm.value.lastUserLoggedIn = lastUserLoggedIn;
|
||||||
state.enableCustomEndpoint = enableCustomEndpoint;
|
enableCustomEndpoint.value = savedEnableCustomEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
@@ -191,22 +189,17 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
await applyAutoLoginDelay();
|
await applyAutoLoginDelay();
|
||||||
// login at startup
|
// login at startup
|
||||||
loginForm.value.loading = true;
|
loginForm.value.loading = true;
|
||||||
authRequest
|
try {
|
||||||
.getConfig()
|
await authRequest.getConfig();
|
||||||
.catch((err) => {
|
try {
|
||||||
loginForm.value.loading = false;
|
await getCurrentUser();
|
||||||
throw err;
|
} catch (err) {
|
||||||
})
|
updateLoopStore.setNextCurrentUserRefresh(60); // 1min
|
||||||
.then(() => {
|
console.error(err);
|
||||||
getCurrentUser()
|
}
|
||||||
.finally(() => {
|
} finally {
|
||||||
loginForm.value.loading = false;
|
loginForm.value.loading = false;
|
||||||
})
|
}
|
||||||
.catch((err) => {
|
|
||||||
updateLoopStore.setNextCurrentUserRefresh(60); // 1min
|
|
||||||
console.error(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,7 +428,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
async function toggleCustomEndpoint() {
|
async function toggleCustomEndpoint() {
|
||||||
await configRepository.setBool(
|
await configRepository.setBool(
|
||||||
'VRCX_enableCustomEndpoint',
|
'VRCX_enableCustomEndpoint',
|
||||||
state.enableCustomEndpoint
|
enableCustomEndpoint.value
|
||||||
);
|
);
|
||||||
loginForm.value.endpoint = '';
|
loginForm.value.endpoint = '';
|
||||||
loginForm.value.websocket = '';
|
loginForm.value.websocket = '';
|
||||||
@@ -552,87 +545,64 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
||||||
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
||||||
}
|
}
|
||||||
authRequest
|
try {
|
||||||
.getConfig()
|
await authRequest.getConfig();
|
||||||
.catch((err) => {
|
if (
|
||||||
loginForm.value.loading = false;
|
loginForm.value.saveCredentials &&
|
||||||
throw err;
|
advancedSettingsStore.enablePrimaryPassword
|
||||||
})
|
) {
|
||||||
.then((args) => {
|
try {
|
||||||
if (
|
const { ok, value } = await modalStore.prompt({
|
||||||
loginForm.value.saveCredentials &&
|
title: t('prompt.primary_password.header'),
|
||||||
advancedSettingsStore.enablePrimaryPassword
|
description: t(
|
||||||
) {
|
'prompt.primary_password.description'
|
||||||
modalStore
|
),
|
||||||
.prompt({
|
inputType: 'password',
|
||||||
title: t('prompt.primary_password.header'),
|
pattern: /[\s\S]{1,32}/
|
||||||
description: t(
|
});
|
||||||
'prompt.primary_password.description'
|
if (ok) {
|
||||||
),
|
const savedCredentials = JSON.parse(
|
||||||
inputType: 'password',
|
await configRepository.getString(
|
||||||
pattern: /[\s\S]{1,32}/
|
'savedCredentials'
|
||||||
})
|
)
|
||||||
.then(async ({ ok, value }) => {
|
);
|
||||||
if (!ok) return;
|
const saveCredential =
|
||||||
const savedCredentials = JSON.parse(
|
savedCredentials[
|
||||||
await configRepository.getString(
|
Object.keys(savedCredentials)[0]
|
||||||
'savedCredentials'
|
];
|
||||||
)
|
await security.decrypt(
|
||||||
);
|
saveCredential.loginParams.password,
|
||||||
const saveCredential =
|
value
|
||||||
savedCredentials[
|
);
|
||||||
Object.keys(savedCredentials)[0]
|
const pwd = await security.encrypt(
|
||||||
];
|
loginForm.value.password,
|
||||||
security
|
value
|
||||||
.decrypt(
|
);
|
||||||
saveCredential.loginParams.password,
|
await authLogin({
|
||||||
value
|
username: loginForm.value.username,
|
||||||
)
|
password: loginForm.value.password,
|
||||||
.then(() => {
|
endpoint: loginForm.value.endpoint,
|
||||||
security
|
websocket: loginForm.value.websocket,
|
||||||
.encrypt(
|
saveCredentials:
|
||||||
loginForm.value.password,
|
loginForm.value.saveCredentials,
|
||||||
value
|
cipher: pwd
|
||||||
)
|
});
|
||||||
.then((pwd) => {
|
}
|
||||||
authLogin({
|
} catch {
|
||||||
username:
|
// prompt cancelled or crypto failed
|
||||||
loginForm.value
|
|
||||||
.username,
|
|
||||||
password:
|
|
||||||
loginForm.value
|
|
||||||
.password,
|
|
||||||
endpoint:
|
|
||||||
loginForm.value
|
|
||||||
.endpoint,
|
|
||||||
websocket:
|
|
||||||
loginForm.value
|
|
||||||
.websocket,
|
|
||||||
saveCredentials:
|
|
||||||
loginForm.value
|
|
||||||
.saveCredentials,
|
|
||||||
cipher: pwd
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loginForm.value.loading = false;
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
return args;
|
|
||||||
}
|
}
|
||||||
authLogin({
|
} else {
|
||||||
|
await authLogin({
|
||||||
username: loginForm.value.username,
|
username: loginForm.value.username,
|
||||||
password: loginForm.value.password,
|
password: loginForm.value.password,
|
||||||
endpoint: loginForm.value.endpoint,
|
endpoint: loginForm.value.endpoint,
|
||||||
websocket: loginForm.value.websocket,
|
websocket: loginForm.value.websocket,
|
||||||
saveCredentials: loginForm.value.saveCredentials
|
saveCredentials: loginForm.value.saveCredentials
|
||||||
}).finally(() => {
|
|
||||||
loginForm.value.loading = false;
|
|
||||||
});
|
});
|
||||||
return args;
|
}
|
||||||
});
|
} finally {
|
||||||
|
loginForm.value.loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -876,7 +846,6 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
*/
|
*/
|
||||||
function setCachedConfig(value) {
|
function setCachedConfig(value) {
|
||||||
cachedConfig.value = value;
|
cachedConfig.value = value;
|
||||||
state.cachedConfig = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -915,6 +884,7 @@ export const useAuthStore = defineStore('Auth', () => {
|
|||||||
handleCurrentUserUpdate,
|
handleCurrentUserUpdate,
|
||||||
loginComplete,
|
loginComplete,
|
||||||
getAllSavedCredentials,
|
getAllSavedCredentials,
|
||||||
|
getSavedCredentials,
|
||||||
setCachedConfig,
|
setCachedConfig,
|
||||||
setAttemptingAutoLogin
|
setAttemptingAutoLogin
|
||||||
};
|
};
|
||||||
|
|||||||
+9
-297
@@ -1,22 +1,17 @@
|
|||||||
import { computed, reactive, ref, watch } from 'vue';
|
import { computed, reactive, ref, watch } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
|
import { i18n } from '../plugin/i18n';
|
||||||
import {
|
import {
|
||||||
compareByCreatedAtAscending,
|
compareByCreatedAtAscending,
|
||||||
createRateLimiter,
|
createRateLimiter,
|
||||||
executeWithBackoff,
|
executeWithBackoff,
|
||||||
getFriendsSortFunction,
|
getFriendsSortFunction,
|
||||||
getNameColour,
|
|
||||||
getUserMemo,
|
getUserMemo,
|
||||||
isRealInstance
|
isRealInstance
|
||||||
} from '../shared/utils';
|
} from '../shared/utils';
|
||||||
import { friendRequest, userRequest } from '../api';
|
import { friendRequest, userRequest } from '../api';
|
||||||
import {
|
|
||||||
runDeleteFriendshipFlow,
|
|
||||||
runUpdateFriendshipsFlow
|
|
||||||
} from '../coordinators/friendRelationshipCoordinator';
|
|
||||||
import {
|
import {
|
||||||
runInitFriendsListFlow,
|
runInitFriendsListFlow,
|
||||||
runRefreshFriendsListFlow
|
runRefreshFriendsListFlow
|
||||||
@@ -25,6 +20,10 @@ import {
|
|||||||
runPendingOfflineTickFlow,
|
runPendingOfflineTickFlow,
|
||||||
runUpdateFriendFlow
|
runUpdateFriendFlow
|
||||||
} from '../coordinators/friendPresenceCoordinator';
|
} from '../coordinators/friendPresenceCoordinator';
|
||||||
|
import {
|
||||||
|
updateFriendship,
|
||||||
|
runUpdateFriendshipsFlow
|
||||||
|
} from '../coordinators/friendRelationshipCoordinator';
|
||||||
import { applyUser } from '../coordinators/userCoordinator';
|
import { applyUser } from '../coordinators/userCoordinator';
|
||||||
import { AppDebug } from '../service/appConfig';
|
import { AppDebug } from '../service/appConfig';
|
||||||
import { database } from '../service/database';
|
import { database } from '../service/database';
|
||||||
@@ -33,10 +32,6 @@ import { useFavoriteStore } from './favorite';
|
|||||||
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';
|
||||||
import { useModalStore } from './modal';
|
|
||||||
import { useNotificationStore } from './notification';
|
|
||||||
import { useSharedFeedStore } from './sharedFeed';
|
|
||||||
import { useUiStore } from './ui';
|
|
||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
@@ -48,16 +43,12 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
const generalSettingsStore = useGeneralSettingsStore();
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const notificationStore = useNotificationStore();
|
|
||||||
const uiStore = useUiStore();
|
|
||||||
const groupStore = useGroupStore();
|
const groupStore = useGroupStore();
|
||||||
const sharedFeedStore = useSharedFeedStore();
|
|
||||||
const locationStore = useLocationStore();
|
const locationStore = useLocationStore();
|
||||||
const favoriteStore = useFavoriteStore();
|
const favoriteStore = useFavoriteStore();
|
||||||
const modalStore = useModalStore();
|
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const t = i18n.global.t;
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
friendNumber: 0
|
friendNumber: 0
|
||||||
@@ -236,7 +227,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
onlineFriendCount.value = 0;
|
onlineFriendCount.value = 0;
|
||||||
pendingOfflineMap.clear();
|
pendingOfflineMap.clear();
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
runInitFriendsListFlow(t);
|
runInitFriendsListFlow(i18n.global.t);
|
||||||
pendingOfflineWorkerFunction();
|
pendingOfflineWorkerFunction();
|
||||||
} else {
|
} else {
|
||||||
if (pendingOfflineWorker !== null) {
|
if (pendingOfflineWorker !== null) {
|
||||||
@@ -270,94 +261,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param ref
|
|
||||||
*/
|
|
||||||
function updateUserCurrentStatus(ref) {
|
|
||||||
if (watchState.isFriendsLoaded) {
|
|
||||||
refreshFriendsStatus(ref);
|
|
||||||
}
|
|
||||||
updateOnlineFriendCounter();
|
|
||||||
|
|
||||||
if (appearanceSettingsStore.randomUserColours) {
|
|
||||||
getNameColour(userStore.currentUser.id).then((colour) => {
|
|
||||||
userStore.setCurrentUserColour(colour);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
function handleFriendStatus(args) {
|
|
||||||
const D = userStore.userDialog;
|
|
||||||
if (D.visible === false || D.id !== args.params.userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { json } = args;
|
|
||||||
D.isFriend = json.isFriend;
|
|
||||||
D.incomingRequest = json.incomingRequest;
|
|
||||||
D.outgoingRequest = json.outgoingRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
function handleFriendDelete(args) {
|
|
||||||
const D = userStore.userDialog;
|
|
||||||
if (D.visible === false || D.id !== args.params.userId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
D.isFriend = false;
|
|
||||||
runDeleteFriendshipFlow(args.params.userId);
|
|
||||||
deleteFriend(args.params.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param args
|
|
||||||
*/
|
|
||||||
function handleFriendAdd(args) {
|
|
||||||
addFriendship(args.params.userId);
|
|
||||||
addFriend(args.params.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param ref
|
|
||||||
*/
|
|
||||||
function userOnFriend(ref) {
|
|
||||||
updateFriendship(ref);
|
|
||||||
if (
|
|
||||||
watchState.isFriendsLoaded &&
|
|
||||||
ref.isFriend &&
|
|
||||||
!friendLog.has(ref.id) &&
|
|
||||||
ref.id !== userStore.currentUser.id
|
|
||||||
) {
|
|
||||||
addFriendship(ref.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} userId
|
|
||||||
* @returns {*|string}
|
|
||||||
*/
|
|
||||||
function getFriendRequest(userId) {
|
|
||||||
const array = notificationStore.notificationTable.data;
|
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
|
||||||
if (
|
|
||||||
array[i].type === 'friendRequest' &&
|
|
||||||
array[i].senderUserId === userId
|
|
||||||
) {
|
|
||||||
return array[i].id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -794,183 +698,13 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
*
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
*/
|
*/
|
||||||
function addFriendship(id) {
|
|
||||||
if (
|
|
||||||
!watchState.isFriendsLoaded ||
|
|
||||||
friendLog.has(id) ||
|
|
||||||
id === userStore.currentUser.id
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const ref = userStore.cachedUsers.get(id);
|
|
||||||
if (typeof ref === 'undefined') {
|
|
||||||
// deleted account on friends list
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
friendRequest
|
|
||||||
.getFriendStatus({
|
|
||||||
userId: id,
|
|
||||||
currentUserId: userStore.currentUser.id
|
|
||||||
})
|
|
||||||
.then((args) => {
|
|
||||||
if (args.params.currentUserId !== userStore.currentUser.id) {
|
|
||||||
// safety check for delayed response
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
handleFriendStatus(args);
|
|
||||||
if (args.json.isFriend && !friendLog.has(id)) {
|
|
||||||
if (state.friendNumber === 0) {
|
|
||||||
state.friendNumber = friends.size;
|
|
||||||
}
|
|
||||||
ref.$friendNumber = ++state.friendNumber;
|
|
||||||
configRepository.setInt(
|
|
||||||
`VRCX_friendNumber_${userStore.currentUser.id}`,
|
|
||||||
state.friendNumber
|
|
||||||
);
|
|
||||||
addFriend(id, ref.state);
|
|
||||||
const friendLogHistory = {
|
|
||||||
created_at: new Date().toJSON(),
|
|
||||||
type: 'Friend',
|
|
||||||
userId: id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLogTable.value.data.push(friendLogHistory);
|
|
||||||
database.addFriendLogHistory(friendLogHistory);
|
|
||||||
notificationStore.queueFriendLogNoty(friendLogHistory);
|
|
||||||
sharedFeedStore.addEntry(friendLogHistory);
|
|
||||||
const friendLogCurrent = {
|
|
||||||
userId: id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLog.set(id, friendLogCurrent);
|
|
||||||
database.setFriendLogCurrent(friendLogCurrent);
|
|
||||||
uiStore.notifyMenu('friend-log');
|
|
||||||
deleteFriendRequest(id);
|
|
||||||
userRequest
|
|
||||||
.getUser({
|
|
||||||
userId: id
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
if (
|
|
||||||
userStore.userDialog.visible &&
|
|
||||||
id === userStore.userDialog.id
|
|
||||||
) {
|
|
||||||
userStore.applyUserDialogLocation(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} userId
|
|
||||||
*/
|
|
||||||
function deleteFriendRequest(userId) {
|
|
||||||
const array = notificationStore.notificationTable.data;
|
|
||||||
for (let i = array.length - 1; i >= 0; i--) {
|
|
||||||
if (
|
|
||||||
array[i].type === 'friendRequest' &&
|
|
||||||
array[i].senderUserId === userId
|
|
||||||
) {
|
|
||||||
array.splice(i, 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {object} ref
|
* @param {object} ref
|
||||||
*/
|
*/
|
||||||
function updateFriendship(ref) {
|
|
||||||
const ctx = friendLog.get(ref.id);
|
|
||||||
if (!watchState.isFriendsLoaded || typeof ctx === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ctx.friendNumber) {
|
|
||||||
ref.$friendNumber = ctx.friendNumber;
|
|
||||||
}
|
|
||||||
if (!ref.$friendNumber) {
|
|
||||||
ref.$friendNumber = 0; // no null
|
|
||||||
}
|
|
||||||
if (ctx.displayName !== ref.displayName) {
|
|
||||||
if (ctx.displayName) {
|
|
||||||
const friendLogHistoryDisplayName = {
|
|
||||||
created_at: new Date().toJSON(),
|
|
||||||
type: 'DisplayName',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
previousDisplayName: ctx.displayName,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLogTable.value.data.push(friendLogHistoryDisplayName);
|
|
||||||
database.addFriendLogHistory(friendLogHistoryDisplayName);
|
|
||||||
notificationStore.queueFriendLogNoty(
|
|
||||||
friendLogHistoryDisplayName
|
|
||||||
);
|
|
||||||
sharedFeedStore.addEntry(friendLogHistoryDisplayName);
|
|
||||||
const friendLogCurrent = {
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLog.set(ref.id, friendLogCurrent);
|
|
||||||
database.setFriendLogCurrent(friendLogCurrent);
|
|
||||||
ctx.displayName = ref.displayName;
|
|
||||||
uiStore.notifyMenu('friend-log');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
ref.$trustLevel &&
|
|
||||||
ctx.trustLevel &&
|
|
||||||
ctx.trustLevel !== ref.$trustLevel
|
|
||||||
) {
|
|
||||||
if (
|
|
||||||
(ctx.trustLevel === 'Trusted User' &&
|
|
||||||
ref.$trustLevel === 'Veteran User') ||
|
|
||||||
(ctx.trustLevel === 'Veteran User' &&
|
|
||||||
ref.$trustLevel === 'Trusted User')
|
|
||||||
) {
|
|
||||||
const friendLogCurrent3 = {
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLog.set(ref.id, friendLogCurrent3);
|
|
||||||
database.setFriendLogCurrent(friendLogCurrent3);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const friendLogHistoryTrustLevel = {
|
|
||||||
created_at: new Date().toJSON(),
|
|
||||||
type: 'TrustLevel',
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
previousTrustLevel: ctx.trustLevel,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLogTable.value.data.push(friendLogHistoryTrustLevel);
|
|
||||||
database.addFriendLogHistory(friendLogHistoryTrustLevel);
|
|
||||||
notificationStore.queueFriendLogNoty(friendLogHistoryTrustLevel);
|
|
||||||
sharedFeedStore.addEntry(friendLogHistoryTrustLevel);
|
|
||||||
const friendLogCurrent2 = {
|
|
||||||
userId: ref.id,
|
|
||||||
displayName: ref.displayName,
|
|
||||||
trustLevel: ref.$trustLevel,
|
|
||||||
friendNumber: ref.$friendNumber
|
|
||||||
};
|
|
||||||
friendLog.set(ref.id, friendLogCurrent2);
|
|
||||||
database.setFriendLogCurrent(friendLogCurrent2);
|
|
||||||
uiStore.notifyMenu('friend-log');
|
|
||||||
}
|
|
||||||
ctx.trustLevel = ref.$trustLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -1394,21 +1128,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
*
|
*
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
function confirmDeleteFriend(id) {
|
|
||||||
modalStore
|
|
||||||
.confirm({
|
|
||||||
description: t('confirm.unfriend'),
|
|
||||||
title: t('confirm.title')
|
|
||||||
})
|
|
||||||
.then(async ({ ok }) => {
|
|
||||||
if (!ok) return;
|
|
||||||
const args = await friendRequest.deleteFriend({
|
|
||||||
userId: id
|
|
||||||
});
|
|
||||||
handleFriendDelete(args);
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all entries in friendLog.
|
* Clears all entries in friendLog.
|
||||||
@@ -1452,14 +1172,6 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
initFriendLog,
|
initFriendLog,
|
||||||
migrateFriendLog,
|
migrateFriendLog,
|
||||||
getFriendLog,
|
getFriendLog,
|
||||||
getFriendRequest,
|
|
||||||
userOnFriend,
|
|
||||||
confirmDeleteFriend,
|
|
||||||
updateUserCurrentStatus,
|
|
||||||
handleFriendAdd,
|
|
||||||
handleFriendDelete,
|
|
||||||
handleFriendStatus,
|
|
||||||
addFriendship,
|
|
||||||
tryApplyFriendOrder,
|
tryApplyFriendOrder,
|
||||||
resetFriendLog,
|
resetFriendLog,
|
||||||
initFriendLogHistoryTable
|
initFriendLogHistoryTable
|
||||||
|
|||||||
+14
-577
@@ -1,50 +1,34 @@
|
|||||||
import { reactive, ref, shallowRef, watch } from 'vue';
|
import { reactive, ref, shallowRef, watch } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { toast } from 'vue-sonner';
|
|
||||||
import { useI18n } from 'vue-i18n';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
compareGameLogRows,
|
compareGameLogRows,
|
||||||
createJoinLeaveEntry,
|
|
||||||
createLocationEntry,
|
|
||||||
createPortalSpawnEntry,
|
|
||||||
createResourceLoadEntry,
|
|
||||||
findUserByDisplayName,
|
findUserByDisplayName,
|
||||||
formatSeconds,
|
formatSeconds,
|
||||||
gameLogSearchFilter,
|
gameLogSearchFilter,
|
||||||
getGroupName,
|
getGroupName
|
||||||
parseInventoryFromUrl,
|
|
||||||
parseLocation,
|
|
||||||
parsePrintFromUrl,
|
|
||||||
replaceBioSymbols
|
|
||||||
} from '../../shared/utils';
|
} from '../../shared/utils';
|
||||||
import { AppDebug } from '../../service/appConfig';
|
|
||||||
import { createMediaParsers } from './mediaParsers';
|
import { createMediaParsers } from './mediaParsers';
|
||||||
import { database } from '../../service/database';
|
import { database } from '../../service/database';
|
||||||
import { useAdvancedSettingsStore } from '../settings/advanced';
|
import { useAdvancedSettingsStore } from '../settings/advanced';
|
||||||
import { useFriendStore } from '../friend';
|
import { useFriendStore } from '../friend';
|
||||||
import { useGalleryStore } from '../gallery';
|
|
||||||
import { useGameStore } from '../game';
|
import { useGameStore } from '../game';
|
||||||
import { useGeneralSettingsStore } from '../settings/general';
|
|
||||||
import { useInstanceStore } from '../instance';
|
import { useInstanceStore } from '../instance';
|
||||||
import { useLocationStore } from '../location';
|
import { useLocationStore } from '../location';
|
||||||
import { runLastLocationResetFlow, runUpdateCurrentUserLocationFlow } from '../../coordinators/locationCoordinator';
|
|
||||||
import { useModalStore } from '../modal';
|
|
||||||
import { useNotificationStore } from '../notification';
|
import { useNotificationStore } from '../notification';
|
||||||
import { usePhotonStore } from '../photon';
|
|
||||||
import { useSharedFeedStore } from '../sharedFeed';
|
import { useSharedFeedStore } from '../sharedFeed';
|
||||||
import { useUiStore } from '../ui';
|
import { useUiStore } from '../ui';
|
||||||
import { useUserStore } from '../user';
|
import { useUserStore } from '../user';
|
||||||
import { useVrStore } from '../vr';
|
import { useVrStore } from '../vr';
|
||||||
import { useVrcxStore } from '../vrcx';
|
import { useVrcxStore } from '../vrcx';
|
||||||
import { userRequest } from '../../api';
|
|
||||||
import { watchState } from '../../service/watchState';
|
import { watchState } from '../../service/watchState';
|
||||||
|
|
||||||
|
import { tryLoadPlayerList, addGameLogEvent } from '../../coordinators/gameLogCoordinator';
|
||||||
|
|
||||||
import configRepository from '../../service/config';
|
import configRepository from '../../service/config';
|
||||||
import gameLogService from '../../service/gameLog.js';
|
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
|
||||||
@@ -59,14 +43,9 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
const vrcxStore = useVrcxStore();
|
const vrcxStore = useVrcxStore();
|
||||||
const advancedSettingsStore = useAdvancedSettingsStore();
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||||
const gameStore = useGameStore();
|
const gameStore = useGameStore();
|
||||||
const generalSettingsStore = useGeneralSettingsStore();
|
|
||||||
const galleryStore = useGalleryStore();
|
|
||||||
const photonStore = usePhotonStore();
|
|
||||||
const sharedFeedStore = useSharedFeedStore();
|
const sharedFeedStore = useSharedFeedStore();
|
||||||
const modalStore = useModalStore();
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
lastLocationAvatarList: new Map()
|
lastLocationAvatarList: new Map()
|
||||||
@@ -304,89 +283,6 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
workerTimers.setTimeout(() => updateNowPlaying(), 1000);
|
workerTimers.setTimeout(() => updateNowPlaying(), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
async function tryLoadPlayerList() {
|
|
||||||
// TODO: make this work again
|
|
||||||
if (!gameStore.isGameRunning) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log('Loading player list from game log...');
|
|
||||||
let ctx;
|
|
||||||
let i;
|
|
||||||
const data = await database.getGamelogDatabase();
|
|
||||||
if (data.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let length = 0;
|
|
||||||
for (i = data.length - 1; i > -1; i--) {
|
|
||||||
ctx = data[i];
|
|
||||||
if (ctx.type === 'Location') {
|
|
||||||
locationStore.setLastLocation({
|
|
||||||
date: Date.parse(ctx.created_at),
|
|
||||||
location: ctx.location,
|
|
||||||
name: ctx.worldName,
|
|
||||||
playerList: new Map(),
|
|
||||||
friendList: new Map()
|
|
||||||
});
|
|
||||||
length = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (length > 0) {
|
|
||||||
for (i = length + 1; i < data.length; i++) {
|
|
||||||
ctx = data[i];
|
|
||||||
if (ctx.type === 'OnPlayerJoined') {
|
|
||||||
if (!ctx.userId) {
|
|
||||||
ctx.userId =
|
|
||||||
findUserByDisplayName(
|
|
||||||
userStore.cachedUsers,
|
|
||||||
ctx.displayName
|
|
||||||
)?.id ?? '';
|
|
||||||
}
|
|
||||||
const userMap = {
|
|
||||||
displayName: ctx.displayName,
|
|
||||||
userId: ctx.userId,
|
|
||||||
joinTime: Date.parse(ctx.created_at),
|
|
||||||
lastAvatar: ''
|
|
||||||
};
|
|
||||||
locationStore.lastLocation.playerList.set(
|
|
||||||
ctx.userId,
|
|
||||||
userMap
|
|
||||||
);
|
|
||||||
if (friendStore.friends.has(ctx.userId)) {
|
|
||||||
locationStore.lastLocation.friendList.set(
|
|
||||||
ctx.userId,
|
|
||||||
userMap
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ctx.type === 'OnPlayerLeft') {
|
|
||||||
locationStore.lastLocation.playerList.delete(ctx.userId);
|
|
||||||
locationStore.lastLocation.friendList.delete(ctx.userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
locationStore.lastLocation.playerList.forEach((ref1) => {
|
|
||||||
if (
|
|
||||||
ref1.userId &&
|
|
||||||
typeof ref1.userId === 'string' &&
|
|
||||||
!userStore.cachedUsers.has(ref1.userId)
|
|
||||||
) {
|
|
||||||
userRequest.getUser({ userId: ref1.userId });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
runUpdateCurrentUserLocationFlow();
|
|
||||||
instanceStore.updateCurrentInstanceWorld();
|
|
||||||
vrStore.updateVRLastLocation();
|
|
||||||
instanceStore.getCurrentInstanceUserList();
|
|
||||||
userStore.applyUserDialogLocation();
|
|
||||||
instanceStore.applyWorldDialogInstances();
|
|
||||||
instanceStore.applyGroupDialogInstances();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param row
|
* @param row
|
||||||
@@ -534,472 +430,6 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param gameLog
|
|
||||||
* @param location
|
|
||||||
*/
|
|
||||||
function addGameLogEntry(gameLog, location) {
|
|
||||||
let entry = undefined;
|
|
||||||
if (advancedSettingsStore.gameLogDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let userId = String(gameLog.userId || '');
|
|
||||||
if (!userId && gameLog.displayName) {
|
|
||||||
userId =
|
|
||||||
findUserByDisplayName(
|
|
||||||
userStore.cachedUsers,
|
|
||||||
gameLog.displayName
|
|
||||||
)?.id ?? '';
|
|
||||||
}
|
|
||||||
switch (gameLog.type) {
|
|
||||||
case 'location-destination':
|
|
||||||
if (gameStore.isGameRunning) {
|
|
||||||
// needs to be added before OnPlayerLeft entries from LocationReset
|
|
||||||
addGameLog({
|
|
||||||
created_at: gameLog.dt,
|
|
||||||
type: 'LocationDestination',
|
|
||||||
location: gameLog.location
|
|
||||||
});
|
|
||||||
runLastLocationResetFlow(gameLog.dt);
|
|
||||||
locationStore.setLastLocationLocation('traveling');
|
|
||||||
locationStore.setLastLocationDestination(gameLog.location);
|
|
||||||
locationStore.setLastLocationDestinationTime(
|
|
||||||
Date.parse(gameLog.dt)
|
|
||||||
);
|
|
||||||
state.lastLocationAvatarList.clear();
|
|
||||||
instanceStore.removeQueuedInstance(gameLog.location);
|
|
||||||
runUpdateCurrentUserLocationFlow();
|
|
||||||
clearNowPlaying();
|
|
||||||
instanceStore.updateCurrentInstanceWorld();
|
|
||||||
userStore.applyUserDialogLocation();
|
|
||||||
instanceStore.applyWorldDialogInstances();
|
|
||||||
instanceStore.applyGroupDialogInstances();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'location':
|
|
||||||
instanceStore.addInstanceJoinHistory(
|
|
||||||
locationStore.lastLocation.location,
|
|
||||||
gameLog.dt
|
|
||||||
);
|
|
||||||
const worldName = replaceBioSymbols(gameLog.worldName);
|
|
||||||
if (gameStore.isGameRunning) {
|
|
||||||
runLastLocationResetFlow(gameLog.dt);
|
|
||||||
clearNowPlaying();
|
|
||||||
locationStore.setLastLocation({
|
|
||||||
date: Date.parse(gameLog.dt),
|
|
||||||
location: gameLog.location,
|
|
||||||
name: worldName,
|
|
||||||
playerList: new Map(),
|
|
||||||
friendList: new Map()
|
|
||||||
});
|
|
||||||
instanceStore.removeQueuedInstance(gameLog.location);
|
|
||||||
runUpdateCurrentUserLocationFlow();
|
|
||||||
vrStore.updateVRLastLocation();
|
|
||||||
instanceStore.updateCurrentInstanceWorld();
|
|
||||||
userStore.applyUserDialogLocation();
|
|
||||||
instanceStore.applyWorldDialogInstances();
|
|
||||||
instanceStore.applyGroupDialogInstances();
|
|
||||||
}
|
|
||||||
instanceStore.addInstanceJoinHistory(
|
|
||||||
gameLog.location,
|
|
||||||
gameLog.dt
|
|
||||||
);
|
|
||||||
const L = parseLocation(gameLog.location);
|
|
||||||
entry = createLocationEntry(
|
|
||||||
gameLog.dt,
|
|
||||||
gameLog.location,
|
|
||||||
L.worldId,
|
|
||||||
worldName
|
|
||||||
);
|
|
||||||
getGroupName(gameLog.location).then((groupName) => {
|
|
||||||
entry.groupName = groupName;
|
|
||||||
});
|
|
||||||
addGamelogLocationToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'player-joined':
|
|
||||||
const joinTime = Date.parse(gameLog.dt);
|
|
||||||
const userMap = {
|
|
||||||
displayName: gameLog.displayName,
|
|
||||||
userId,
|
|
||||||
joinTime,
|
|
||||||
lastAvatar: ''
|
|
||||||
};
|
|
||||||
locationStore.lastLocation.playerList.set(userId, userMap);
|
|
||||||
const ref = userStore.cachedUsers.get(userId);
|
|
||||||
if (!userId) {
|
|
||||||
console.error('Missing userId:', gameLog.displayName);
|
|
||||||
} else if (userId === userStore.currentUser.id) {
|
|
||||||
// skip
|
|
||||||
} else if (
|
|
||||||
friendStore.friends.has(userId) &&
|
|
||||||
typeof ref !== 'undefined'
|
|
||||||
) {
|
|
||||||
locationStore.lastLocation.friendList.set(userId, userMap);
|
|
||||||
if (
|
|
||||||
ref.location !== locationStore.lastLocation.location &&
|
|
||||||
ref.travelingToLocation !==
|
|
||||||
locationStore.lastLocation.location
|
|
||||||
) {
|
|
||||||
// fix $location_at with private
|
|
||||||
ref.$location_at = joinTime;
|
|
||||||
}
|
|
||||||
} else if (typeof ref !== 'undefined') {
|
|
||||||
// set $location_at to join time if user isn't a friend
|
|
||||||
ref.$location_at = joinTime;
|
|
||||||
} else {
|
|
||||||
if (AppDebug.debugGameLog || AppDebug.debugWebRequests) {
|
|
||||||
console.log('Fetching user from gameLog:', userId);
|
|
||||||
}
|
|
||||||
userRequest.getUser({ userId });
|
|
||||||
}
|
|
||||||
vrStore.updateVRLastLocation();
|
|
||||||
instanceStore.getCurrentInstanceUserList();
|
|
||||||
entry = createJoinLeaveEntry(
|
|
||||||
'OnPlayerJoined',
|
|
||||||
gameLog.dt,
|
|
||||||
gameLog.displayName,
|
|
||||||
location,
|
|
||||||
userId
|
|
||||||
);
|
|
||||||
database.addGamelogJoinLeaveToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'player-left':
|
|
||||||
const ref1 = locationStore.lastLocation.playerList.get(userId);
|
|
||||||
if (typeof ref1 === 'undefined') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const time = dayjs(gameLog.dt) - ref1.joinTime;
|
|
||||||
locationStore.lastLocation.playerList.delete(userId);
|
|
||||||
locationStore.lastLocation.friendList.delete(userId);
|
|
||||||
state.lastLocationAvatarList.delete(gameLog.displayName);
|
|
||||||
photonStore.photonLobbyAvatars.delete(userId);
|
|
||||||
vrStore.updateVRLastLocation();
|
|
||||||
instanceStore.getCurrentInstanceUserList();
|
|
||||||
entry = createJoinLeaveEntry(
|
|
||||||
'OnPlayerLeft',
|
|
||||||
gameLog.dt,
|
|
||||||
gameLog.displayName,
|
|
||||||
location,
|
|
||||||
userId,
|
|
||||||
time
|
|
||||||
);
|
|
||||||
database.addGamelogJoinLeaveToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'portal-spawn':
|
|
||||||
if (vrcxStore.ipcEnabled && gameStore.isGameRunning) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
entry = createPortalSpawnEntry(gameLog.dt, location);
|
|
||||||
database.addGamelogPortalSpawnToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'video-play':
|
|
||||||
gameLog.videoUrl = decodeURI(gameLog.videoUrl);
|
|
||||||
if (lastVideoUrl.value === gameLog.videoUrl) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lastVideoUrl.value = gameLog.videoUrl;
|
|
||||||
addGameLogVideo(gameLog, location, userId);
|
|
||||||
break;
|
|
||||||
case 'video-sync':
|
|
||||||
const timestamp = gameLog.timestamp.replace(/,/g, '');
|
|
||||||
if (nowPlaying.value.playing) {
|
|
||||||
nowPlaying.value.offset = parseInt(timestamp, 10);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'resource-load-string':
|
|
||||||
case 'resource-load-image':
|
|
||||||
if (
|
|
||||||
!generalSettingsStore.logResourceLoad ||
|
|
||||||
lastResourceloadUrl.value === gameLog.resourceUrl
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
lastResourceloadUrl.value = gameLog.resourceUrl;
|
|
||||||
entry = createResourceLoadEntry(
|
|
||||||
gameLog.type,
|
|
||||||
gameLog.dt,
|
|
||||||
gameLog.resourceUrl,
|
|
||||||
location
|
|
||||||
);
|
|
||||||
database.addGamelogResourceLoadToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'screenshot':
|
|
||||||
// entry = {
|
|
||||||
// created_at: gameLog.dt,
|
|
||||||
// type: 'Event',
|
|
||||||
// data: `Screenshot Processed: ${gameLog.screenshotPath.replace(
|
|
||||||
// /^.*[\\/]/,
|
|
||||||
// ''
|
|
||||||
// )}`
|
|
||||||
// };
|
|
||||||
// database.addGamelogEventToDatabase(entry);
|
|
||||||
|
|
||||||
vrcxStore.processScreenshot(gameLog.screenshotPath);
|
|
||||||
break;
|
|
||||||
case 'api-request':
|
|
||||||
if (AppDebug.debugWebRequests) {
|
|
||||||
console.log('API Request:', gameLog.url);
|
|
||||||
}
|
|
||||||
// const userId = '';
|
|
||||||
// try {
|
|
||||||
// const url = new URL(gameLog.url);
|
|
||||||
// const urlParams = new URLSearchParams(gameLog.url);
|
|
||||||
// if (url.pathname.substring(0, 13) === '/api/1/users/') {
|
|
||||||
// const pathArray = url.pathname.split('/');
|
|
||||||
// userId = pathArray[4];
|
|
||||||
// } else if (urlParams.has('userId')) {
|
|
||||||
// userId = urlParams.get('userId');
|
|
||||||
// }
|
|
||||||
// } catch (err) {
|
|
||||||
// console.error(err);
|
|
||||||
// }
|
|
||||||
// if (!userId) {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (advancedSettingsStore.saveInstanceEmoji) {
|
|
||||||
const inv = parseInventoryFromUrl(gameLog.url);
|
|
||||||
if (inv) {
|
|
||||||
galleryStore.queueCheckInstanceInventory(
|
|
||||||
inv.inventoryId,
|
|
||||||
inv.userId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (advancedSettingsStore.saveInstancePrints) {
|
|
||||||
const printId = parsePrintFromUrl(gameLog.url);
|
|
||||||
if (printId) {
|
|
||||||
galleryStore.queueSavePrintToFile(printId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'avatar-change':
|
|
||||||
if (!gameStore.isGameRunning) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let avatarName = state.lastLocationAvatarList.get(
|
|
||||||
gameLog.displayName
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
photonStore.photonLoggingEnabled ||
|
|
||||||
avatarName === gameLog.avatarName
|
|
||||||
) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!avatarName) {
|
|
||||||
avatarName = gameLog.avatarName;
|
|
||||||
state.lastLocationAvatarList.set(
|
|
||||||
gameLog.displayName,
|
|
||||||
avatarName
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
avatarName = gameLog.avatarName;
|
|
||||||
state.lastLocationAvatarList.set(
|
|
||||||
gameLog.displayName,
|
|
||||||
avatarName
|
|
||||||
);
|
|
||||||
entry = {
|
|
||||||
created_at: gameLog.dt,
|
|
||||||
type: 'AvatarChange',
|
|
||||||
userId,
|
|
||||||
name: avatarName,
|
|
||||||
displayName: gameLog.displayName
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
case 'vrcx':
|
|
||||||
// VideoPlay(PyPyDance) "https://jd.pypy.moe/api/v1/videos/jr1NX4Jo8GE.mp4",0.1001,239.606,"0905 : [J-POP] 【まなこ】金曜日のおはよう 踊ってみた (vernities)"
|
|
||||||
const type = gameLog.data.substr(0, gameLog.data.indexOf(' '));
|
|
||||||
if (type === 'VideoPlay(PyPyDance)') {
|
|
||||||
addGameLogPyPyDance(gameLog, location);
|
|
||||||
} else if (type === 'VideoPlay(VRDancing)') {
|
|
||||||
addGameLogVRDancing(gameLog, location);
|
|
||||||
} else if (type === 'VideoPlay(ZuwaZuwaDance)') {
|
|
||||||
addGameLogZuwaZuwaDance(gameLog, location);
|
|
||||||
} else if (type === 'LSMedia') {
|
|
||||||
addGameLogLSMedia(gameLog, location);
|
|
||||||
} else if (type === 'VideoPlay(PopcornPalace)') {
|
|
||||||
addGameLogPopcornPalace(gameLog, location);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'photon-id':
|
|
||||||
if (!gameStore.isGameRunning || !watchState.isFriendsLoaded) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const photonId = parseInt(gameLog.photonId, 10);
|
|
||||||
const ref2 = photonStore.photonLobby.get(photonId);
|
|
||||||
if (typeof ref2 === 'undefined') {
|
|
||||||
const foundUser = findUserByDisplayName(
|
|
||||||
userStore.cachedUsers,
|
|
||||||
gameLog.displayName
|
|
||||||
);
|
|
||||||
if (foundUser) {
|
|
||||||
photonStore.photonLobby.set(photonId, foundUser);
|
|
||||||
photonStore.photonLobbyCurrent.set(photonId, foundUser);
|
|
||||||
}
|
|
||||||
const ctx1 = {
|
|
||||||
displayName: gameLog.displayName
|
|
||||||
};
|
|
||||||
photonStore.photonLobby.set(photonId, ctx1);
|
|
||||||
photonStore.photonLobbyCurrent.set(photonId, ctx1);
|
|
||||||
instanceStore.getCurrentInstanceUserList();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'notification':
|
|
||||||
// entry = {
|
|
||||||
// created_at: gameLog.dt,
|
|
||||||
// type: 'Notification',
|
|
||||||
// data: gameLog.json
|
|
||||||
// };
|
|
||||||
break;
|
|
||||||
case 'event':
|
|
||||||
entry = {
|
|
||||||
created_at: gameLog.dt,
|
|
||||||
type: 'Event',
|
|
||||||
data: gameLog.event
|
|
||||||
};
|
|
||||||
database.addGamelogEventToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'vrc-quit':
|
|
||||||
if (!gameStore.isGameRunning) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (advancedSettingsStore.vrcQuitFix) {
|
|
||||||
const bias = Date.parse(gameLog.dt) + 3000;
|
|
||||||
if (bias < Date.now()) {
|
|
||||||
console.log('QuitFix: Bias too low, not killing VRC');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
AppApi.QuitGame().then((processCount) => {
|
|
||||||
if (processCount > 1) {
|
|
||||||
console.log(
|
|
||||||
'QuitFix: More than 1 process running, not killing VRC'
|
|
||||||
);
|
|
||||||
} else if (processCount === 1) {
|
|
||||||
console.log('QuitFix: Killed VRC');
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'QuitFix: Nothing to kill, no VRC process running'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'openvr-init':
|
|
||||||
gameStore.setIsGameNoVR(false);
|
|
||||||
configRepository.setBool('isGameNoVR', gameStore.isGameNoVR);
|
|
||||||
vrStore.updateOpenVR();
|
|
||||||
break;
|
|
||||||
case 'desktop-mode':
|
|
||||||
gameStore.setIsGameNoVR(true);
|
|
||||||
configRepository.setBool('isGameNoVR', gameStore.isGameNoVR);
|
|
||||||
vrStore.updateOpenVR();
|
|
||||||
break;
|
|
||||||
case 'udon-exception':
|
|
||||||
if (generalSettingsStore.udonExceptionLogging) {
|
|
||||||
console.log('UdonException', gameLog.data);
|
|
||||||
}
|
|
||||||
// entry = {
|
|
||||||
// created_at: gameLog.dt,
|
|
||||||
// type: 'Event',
|
|
||||||
// data: gameLog.data
|
|
||||||
// };
|
|
||||||
// database.addGamelogEventToDatabase(entry);
|
|
||||||
break;
|
|
||||||
case 'sticker-spawn':
|
|
||||||
if (!advancedSettingsStore.saveInstanceStickers) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
galleryStore.trySaveStickerToFile(
|
|
||||||
gameLog.displayName,
|
|
||||||
gameLog.userId,
|
|
||||||
gameLog.inventoryId
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (typeof entry !== 'undefined') {
|
|
||||||
sharedFeedStore.addEntry(entry);
|
|
||||||
notificationStore.queueGameLogNoty(entry);
|
|
||||||
addGameLog(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
async function getGameLogTable() {
|
|
||||||
await database.initTables();
|
|
||||||
const dateTill = await database.getLastDateGameLogDatabase();
|
|
||||||
updateGameLog(dateTill);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param dateTill
|
|
||||||
*/
|
|
||||||
async function updateGameLog(dateTill) {
|
|
||||||
await gameLogService.setDateTill(dateTill);
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
workerTimers.setTimeout(resolve, 10000);
|
|
||||||
});
|
|
||||||
let location = '';
|
|
||||||
for (const gameLog of await gameLogService.getAll()) {
|
|
||||||
if (gameLog.type === 'location') {
|
|
||||||
location = gameLog.location;
|
|
||||||
}
|
|
||||||
addGameLogEntry(gameLog, location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use in C#
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param json
|
|
||||||
*/
|
|
||||||
function addGameLogEvent(json) {
|
|
||||||
const rawLogs = JSON.parse(json);
|
|
||||||
const gameLog = gameLogService.parseRawGameLog(
|
|
||||||
rawLogs[1],
|
|
||||||
rawLogs[2],
|
|
||||||
rawLogs.slice(3)
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
AppDebug.debugGameLog &&
|
|
||||||
gameLog.type !== 'photon-id' &&
|
|
||||||
gameLog.type !== 'api-request' &&
|
|
||||||
gameLog.type !== 'udon-exception'
|
|
||||||
) {
|
|
||||||
console.log('gameLog:', gameLog);
|
|
||||||
}
|
|
||||||
addGameLogEntry(gameLog, locationStore.lastLocation.location);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
async function disableGameLogDialog() {
|
|
||||||
if (gameStore.isGameRunning) {
|
|
||||||
toast.error(t('message.gamelog.vrchat_must_be_closed'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!advancedSettingsStore.gameLogDisabled) {
|
|
||||||
modalStore
|
|
||||||
.confirm({
|
|
||||||
description: t('confirm.disable_gamelog'),
|
|
||||||
title: t('confirm.title')
|
|
||||||
})
|
|
||||||
.then(({ ok }) => {
|
|
||||||
if (!ok) return;
|
|
||||||
advancedSettingsStore.setGameLogDisabled();
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
} else {
|
|
||||||
advancedSettingsStore.setGameLogDisabled();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@@ -1028,14 +458,21 @@ export const useGameLogStore = defineStore('GameLog', () => {
|
|||||||
|
|
||||||
clearNowPlaying,
|
clearNowPlaying,
|
||||||
resetLastMediaUrls,
|
resetLastMediaUrls,
|
||||||
tryLoadPlayerList,
|
|
||||||
gameLogIsFriend,
|
gameLogIsFriend,
|
||||||
gameLogIsFavorite,
|
gameLogIsFavorite,
|
||||||
gameLogTableLookup,
|
gameLogTableLookup,
|
||||||
addGameLog,
|
addGameLog,
|
||||||
addGamelogLocationToDatabase,
|
addGamelogLocationToDatabase,
|
||||||
getGameLogTable,
|
|
||||||
addGameLogEvent,
|
// Media parsers (used by coordinator)
|
||||||
disableGameLogDialog
|
addGameLogVideo,
|
||||||
|
addGameLogPyPyDance,
|
||||||
|
addGameLogVRDancing,
|
||||||
|
addGameLogZuwaZuwaDance,
|
||||||
|
addGameLogLSMedia,
|
||||||
|
addGameLogPopcornPalace,
|
||||||
|
|
||||||
|
// Re-exported from coordinator (called by C# via window.$pinia)
|
||||||
|
addGameLogEvent
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import { useAdvancedSettingsStore } from '../settings/advanced';
|
|||||||
import { useAppearanceSettingsStore } from '../settings/appearance';
|
import { useAppearanceSettingsStore } from '../settings/appearance';
|
||||||
import { useFavoriteStore } from '../favorite';
|
import { useFavoriteStore } from '../favorite';
|
||||||
import { useFriendStore } from '../friend';
|
import { useFriendStore } from '../friend';
|
||||||
|
import { handleFriendAdd } from '../../coordinators/friendRelationshipCoordinator';
|
||||||
import { useGameStore } from '../game';
|
import { useGameStore } from '../game';
|
||||||
import { useGeneralSettingsStore } from '../settings/general';
|
import { useGeneralSettingsStore } from '../settings/general';
|
||||||
import { useGroupStore } from '../group';
|
import { useGroupStore } from '../group';
|
||||||
@@ -533,7 +534,7 @@ export const useNotificationStore = defineStore('Notification', () => {
|
|||||||
notificationId: ref.id
|
notificationId: ref.id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
friendStore.handleFriendAdd({
|
handleFriendAdd({
|
||||||
params: {
|
params: {
|
||||||
userId: ref.senderUserId
|
userId: ref.senderUserId
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import { database } from '../service/database';
|
|||||||
import { groupRequest } from '../api';
|
import { groupRequest } from '../api';
|
||||||
import { runRefreshFriendsListFlow } from '../coordinators/friendSyncCoordinator';
|
import { runRefreshFriendsListFlow } from '../coordinators/friendSyncCoordinator';
|
||||||
import { runUpdateIsGameRunningFlow } from '../coordinators/gameCoordinator';
|
import { runUpdateIsGameRunningFlow } from '../coordinators/gameCoordinator';
|
||||||
|
import { addGameLogEvent } from '../coordinators/gameLogCoordinator';
|
||||||
import { runRefreshPlayerModerationsFlow } from '../coordinators/moderationCoordinator';
|
import { runRefreshPlayerModerationsFlow } from '../coordinators/moderationCoordinator';
|
||||||
|
import { clearVRCXCache } from '../coordinators/vrcxCoordinator';
|
||||||
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';
|
||||||
import { useGameLogStore } from './gameLog';
|
|
||||||
import { useGameStore } from './game';
|
import { useGameStore } from './game';
|
||||||
import { useGroupStore } from './group';
|
import { useGroupStore } from './group';
|
||||||
import { handleGroupUserInstances } from '../coordinators/groupCoordinator';
|
import { handleGroupUserInstances } from '../coordinators/groupCoordinator';
|
||||||
@@ -31,7 +32,6 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
const moderationStore = useModerationStore();
|
const moderationStore = useModerationStore();
|
||||||
const vrcxStore = useVrcxStore();
|
const vrcxStore = useVrcxStore();
|
||||||
const discordPresenceSettingsStore = useDiscordPresenceSettingsStore();
|
const discordPresenceSettingsStore = useDiscordPresenceSettingsStore();
|
||||||
const gameLogStore = useGameLogStore();
|
|
||||||
const vrcxUpdaterStore = useVRCXUpdaterStore();
|
const vrcxUpdaterStore = useVRCXUpdaterStore();
|
||||||
const groupStore = useGroupStore();
|
const groupStore = useGroupStore();
|
||||||
const vrStore = useVrStore();
|
const vrStore = useVrStore();
|
||||||
@@ -114,7 +114,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
) {
|
) {
|
||||||
state.nextClearVRCXCacheCheck =
|
state.nextClearVRCXCacheCheck =
|
||||||
vrcxStore.clearVRCXCacheFrequency / 2;
|
vrcxStore.clearVRCXCacheFrequency / 2;
|
||||||
vrcxStore.clearVRCXCache();
|
clearVRCXCache();
|
||||||
}
|
}
|
||||||
if (--state.nextDiscordUpdate <= 0) {
|
if (--state.nextDiscordUpdate <= 0) {
|
||||||
state.nextDiscordUpdate = 3;
|
state.nextDiscordUpdate = 3;
|
||||||
@@ -131,7 +131,7 @@ export const useUpdateLoopStore = defineStore('UpdateLoop', () => {
|
|||||||
const logLines = await LogWatcher.GetLogLines();
|
const logLines = await LogWatcher.GetLogLines();
|
||||||
if (logLines) {
|
if (logLines) {
|
||||||
logLines.forEach((logLine) => {
|
logLines.forEach((logLine) => {
|
||||||
gameLogStore.addGameLogEvent(logLine);
|
addGameLogEvent(logLine);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-53
@@ -46,6 +46,7 @@ import { useUpdateLoopStore } from './updateLoop';
|
|||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { useVrcStatusStore } from './vrcStatus';
|
import { useVrcStatusStore } from './vrcStatus';
|
||||||
import { useWorldStore } from './world';
|
import { useWorldStore } from './world';
|
||||||
|
import { clearVRCXCache } from '../coordinators/vrcxCoordinator';
|
||||||
import { watchState } from '../service/watchState';
|
import { watchState } from '../service/watchState';
|
||||||
|
|
||||||
import configRepository from '../service/config';
|
import configRepository from '../service/config';
|
||||||
@@ -277,59 +278,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function clearVRCXCache() {
|
|
||||||
console.log('Clearing VRCX cache...');
|
|
||||||
failedGetRequests.clear();
|
|
||||||
userStore.cachedUsers.forEach((ref, id) => {
|
|
||||||
if (
|
|
||||||
!friendStore.friends.has(id) &&
|
|
||||||
!locationStore.lastLocation.playerList.has(ref.id) &&
|
|
||||||
id !== userStore.currentUser.id
|
|
||||||
) {
|
|
||||||
userStore.cachedUsers.delete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
worldStore.cachedWorlds.forEach((ref, id) => {
|
|
||||||
if (
|
|
||||||
!favoriteStore.getCachedFavoritesByObjectId(id) &&
|
|
||||||
ref.authorId !== userStore.currentUser.id &&
|
|
||||||
!favoriteStore.localWorldFavoritesList.includes(id)
|
|
||||||
) {
|
|
||||||
worldStore.cachedWorlds.delete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
avatarStore.cachedAvatars.forEach((ref, id) => {
|
|
||||||
if (
|
|
||||||
!favoriteStore.getCachedFavoritesByObjectId(id) &&
|
|
||||||
ref.authorId !== userStore.currentUser.id &&
|
|
||||||
!favoriteStore.localAvatarFavoritesList.includes(id) &&
|
|
||||||
!avatarStore.avatarHistory.includes(id)
|
|
||||||
) {
|
|
||||||
avatarStore.cachedAvatars.delete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
groupStore.cachedGroups.forEach((ref, id) => {
|
|
||||||
if (!groupStore.currentUserGroups.has(id)) {
|
|
||||||
groupStore.cachedGroups.delete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
instanceStore.cachedInstances.forEach((ref, id) => {
|
|
||||||
if (
|
|
||||||
[...friendStore.friends.values()].some(
|
|
||||||
(f) => f.$location?.tag === id
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// delete instances over an hour old
|
|
||||||
if (Date.parse(ref.$fetchedAt) < Date.now() - 3600000) {
|
|
||||||
instanceStore.cachedInstances.delete(id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
avatarStore.cachedAvatarNames.clear();
|
|
||||||
userStore.customUserTags.clear();
|
|
||||||
galleryStore.cachedEmoji.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -149,6 +149,7 @@
|
|||||||
import { useDataTableScrollHeight } from '../../composables/useDataTableScrollHeight';
|
import { useDataTableScrollHeight } from '../../composables/useDataTableScrollHeight';
|
||||||
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
import { useVrcxVueTable } from '../../lib/table/useVrcxVueTable';
|
||||||
import { showUserDialog } from '../../coordinators/userCoordinator';
|
import { showUserDialog } from '../../coordinators/userCoordinator';
|
||||||
|
import { confirmDeleteFriend, handleFriendDelete } from '../../coordinators/friendRelationshipCoordinator';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
@@ -156,7 +157,7 @@ import { showUserDialog } from '../../coordinators/userCoordinator';
|
|||||||
|
|
||||||
const { friends, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
const { friends, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
||||||
const modalStore = useModalStore();
|
const modalStore = useModalStore();
|
||||||
const { getAllUserStats, getAllUserMutualCount, confirmDeleteFriend, handleFriendDelete } = useFriendStore();
|
const { getAllUserStats, getAllUserMutualCount } = useFriendStore();
|
||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
|
const { randomUserColours } = storeToRefs(appearanceSettingsStore);
|
||||||
|
|
||||||
|
|||||||
@@ -40,9 +40,13 @@ mocks.pagination = mocks.makeRef({
|
|||||||
});
|
});
|
||||||
mocks.sorting = mocks.makeRef([]);
|
mocks.sorting = mocks.makeRef([]);
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('vue-i18n', () => ({
|
vi.mock('vue-i18n', () => ({
|
||||||
useI18n: () => ({
|
useI18n: () => ({
|
||||||
@@ -66,9 +70,7 @@ vi.mock('../../../stores', () => ({
|
|||||||
friends: mocks.friends,
|
friends: mocks.friends,
|
||||||
allFavoriteFriendIds: mocks.allFavoriteFriendIds,
|
allFavoriteFriendIds: mocks.allFavoriteFriendIds,
|
||||||
getAllUserStats: mocks.getAllUserStats,
|
getAllUserStats: mocks.getAllUserStats,
|
||||||
getAllUserMutualCount: mocks.getAllUserMutualCount,
|
getAllUserMutualCount: mocks.getAllUserMutualCount
|
||||||
confirmDeleteFriend: mocks.confirmDeleteFriend,
|
|
||||||
handleFriendDelete: mocks.handleFriendDelete
|
|
||||||
}),
|
}),
|
||||||
useModalStore: () => ({
|
useModalStore: () => ({
|
||||||
confirm: (...args) => mocks.modalConfirm(...args),
|
confirm: (...args) => mocks.modalConfirm(...args),
|
||||||
@@ -78,9 +80,7 @@ vi.mock('../../../stores', () => ({
|
|||||||
stringComparer: mocks.stringComparer,
|
stringComparer: mocks.stringComparer,
|
||||||
friendsListSearch: mocks.friendsListSearch
|
friendsListSearch: mocks.friendsListSearch
|
||||||
}),
|
}),
|
||||||
useUserStore: () => ({
|
useUserStore: () => ({}),
|
||||||
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
|
||||||
}),
|
|
||||||
useAppearanceSettingsStore: () => ({
|
useAppearanceSettingsStore: () => ({
|
||||||
tablePageSizes: [10, 25, 50],
|
tablePageSizes: [10, 25, 50],
|
||||||
tablePageSize: 25,
|
tablePageSize: 25,
|
||||||
@@ -91,6 +91,15 @@ vi.mock('../../../stores', () => ({
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../coordinators/friendRelationshipCoordinator', () => ({
|
||||||
|
confirmDeleteFriend: (...args) => mocks.confirmDeleteFriend(...args),
|
||||||
|
handleFriendDelete: (...args) => mocks.handleFriendDelete(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../plugin/router', () => ({
|
vi.mock('../../../plugin/router', () => ({
|
||||||
router: {
|
router: {
|
||||||
push: (...args) => mocks.routerPush(...args)
|
push: (...args) => mocks.routerPush(...args)
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ const mocks = vi.hoisted(() => ({
|
|||||||
te: vi.fn((key) => key === 'view.moderation.filters.block')
|
te: vi.fn((key) => key === 'view.moderation.filters.block')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('../../../plugin', () => ({
|
vi.mock('../../../plugin', () => ({
|
||||||
i18n: {
|
i18n: {
|
||||||
@@ -26,11 +30,14 @@ vi.mock('../../../stores', () => ({
|
|||||||
shiftHeld: mocks.shiftHeld
|
shiftHeld: mocks.shiftHeld
|
||||||
}),
|
}),
|
||||||
useUserStore: () => ({
|
useUserStore: () => ({
|
||||||
currentUser: mocks.currentUser,
|
currentUser: mocks.currentUser
|
||||||
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
|
||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../shared/utils', () => ({
|
vi.mock('../../../shared/utils', () => ({
|
||||||
formatDateFilter: (value, format) => `${format}:${value}`
|
formatDateFilter: (value, format) => `${format}:${value}`
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -21,9 +21,13 @@ const mocks = vi.hoisted(() => ({
|
|||||||
photonColumnToggleVisibility: vi.fn()
|
photonColumnToggleVisibility: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('vue-i18n', () => ({
|
vi.mock('vue-i18n', () => ({
|
||||||
useI18n: () => ({
|
useI18n: () => ({
|
||||||
@@ -43,9 +47,7 @@ vi.mock('../../../stores', () => ({
|
|||||||
saveChatboxUserBlacklist: (...args) => mocks.saveChatboxUserBlacklist(...args)
|
saveChatboxUserBlacklist: (...args) => mocks.saveChatboxUserBlacklist(...args)
|
||||||
}),
|
}),
|
||||||
useUserStore: () => ({
|
useUserStore: () => ({
|
||||||
currentUser: mocks.currentUser,
|
currentUser: mocks.currentUser
|
||||||
showUserDialog: (...args) => mocks.showUserDialog(...args),
|
|
||||||
lookupUser: (...args) => mocks.lookupUser(...args)
|
|
||||||
}),
|
}),
|
||||||
useWorldStore: () => ({
|
useWorldStore: () => ({
|
||||||
showWorldDialog: (...args) => mocks.showWorldDialog(...args)
|
showWorldDialog: (...args) => mocks.showWorldDialog(...args)
|
||||||
@@ -64,6 +66,11 @@ vi.mock('../../../stores', () => ({
|
|||||||
})
|
})
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mocks.showUserDialog(...args),
|
||||||
|
lookupUser: (...args) => mocks.lookupUser(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../lib/table/useVrcxVueTable', () => ({
|
vi.mock('../../../lib/table/useVrcxVueTable', () => ({
|
||||||
useVrcxVueTable: () => ({
|
useVrcxVueTable: () => ({
|
||||||
table: {
|
table: {
|
||||||
|
|||||||
@@ -390,7 +390,6 @@
|
|||||||
useAuthStore,
|
useAuthStore,
|
||||||
useAvatarProviderStore,
|
useAvatarProviderStore,
|
||||||
useAvatarStore,
|
useAvatarStore,
|
||||||
useGameLogStore,
|
|
||||||
useGeneralSettingsStore,
|
useGeneralSettingsStore,
|
||||||
useGroupStore,
|
useGroupStore,
|
||||||
useInstanceStore,
|
useInstanceStore,
|
||||||
@@ -400,10 +399,11 @@
|
|||||||
useUserStore,
|
useUserStore,
|
||||||
useVRCXUpdaterStore,
|
useVRCXUpdaterStore,
|
||||||
useVrStore,
|
useVrStore,
|
||||||
useVrcxStore,
|
|
||||||
useWorldStore
|
useWorldStore
|
||||||
} from '../../../../stores';
|
} from '../../../../stores';
|
||||||
import { authRequest, queryRequest } from '../../../../api';
|
import { authRequest, queryRequest } from '../../../../api';
|
||||||
|
import { disableGameLogDialog } from '../../../../coordinators/gameLogCoordinator';
|
||||||
|
import { clearVRCXCache } from '../../../../coordinators/vrcxCoordinator';
|
||||||
import { openExternalLink } from '../../../../shared/utils';
|
import { openExternalLink } from '../../../../shared/utils';
|
||||||
|
|
||||||
import AvatarProviderDialog from '../../dialogs/AvatarProviderDialog.vue';
|
import AvatarProviderDialog from '../../dialogs/AvatarProviderDialog.vue';
|
||||||
@@ -420,9 +420,7 @@
|
|||||||
const { updateVRLastLocation, updateOpenVR } = useVrStore();
|
const { updateVRLastLocation, updateOpenVR } = useVrStore();
|
||||||
const { enablePrimaryPasswordChange } = useAuthStore();
|
const { enablePrimaryPasswordChange } = useAuthStore();
|
||||||
const { cachedConfig } = storeToRefs(useAuthStore());
|
const { cachedConfig } = storeToRefs(useAuthStore());
|
||||||
const { clearVRCXCache } = useVrcxStore();
|
|
||||||
const { showConsole } = useUiStore();
|
const { showConsole } = useUiStore();
|
||||||
const { disableGameLogDialog } = useGameLogStore();
|
|
||||||
|
|
||||||
const generalSettingsStore = useGeneralSettingsStore();
|
const generalSettingsStore = useGeneralSettingsStore();
|
||||||
const { udonExceptionLogging, logResourceLoad, logEmptyAvatars, autoLoginDelayEnabled } =
|
const { udonExceptionLogging, logResourceLoad, logEmptyAvatars, autoLoginDelayEnabled } =
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
|
|
||||||
import '@/styles/status-icon.css';
|
import '@/styles/status-icon.css';
|
||||||
import { showUserDialog } from '../../../coordinators/userCoordinator';
|
import { showUserDialog } from '../../../coordinators/userCoordinator';
|
||||||
|
import { confirmDeleteFriend } from '../../../coordinators/friendRelationshipCoordinator';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
friend: { type: Object, required: true },
|
friend: { type: Object, required: true },
|
||||||
@@ -89,7 +90,7 @@ import { showUserDialog } from '../../../coordinators/userCoordinator';
|
|||||||
|
|
||||||
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
|
const { hideNicknames } = storeToRefs(useAppearanceSettingsStore());
|
||||||
const { isRefreshFriendsLoading, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
const { isRefreshFriendsLoading, allFavoriteFriendIds } = storeToRefs(useFriendStore());
|
||||||
const { confirmDeleteFriend } = useFriendStore();
|
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,20 @@ const mocks = vi.hoisted(() => ({
|
|||||||
},
|
},
|
||||||
friendStore: {
|
friendStore: {
|
||||||
isRefreshFriendsLoading: false,
|
isRefreshFriendsLoading: false,
|
||||||
allFavoriteFriendIds: new Set(),
|
allFavoriteFriendIds: new Set()
|
||||||
confirmDeleteFriend: vi.fn()
|
|
||||||
},
|
},
|
||||||
userStore: {
|
userStore: {},
|
||||||
showUserDialog: vi.fn()
|
showUserDialog: vi.fn(),
|
||||||
}
|
confirmDeleteFriend: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('../../../../stores', () => ({
|
vi.mock('../../../../stores', () => ({
|
||||||
useAppearanceSettingsStore: () => mocks.appearanceStore,
|
useAppearanceSettingsStore: () => mocks.appearanceStore,
|
||||||
@@ -25,6 +28,14 @@ vi.mock('../../../../stores', () => ({
|
|||||||
useUserStore: () => mocks.userStore
|
useUserStore: () => mocks.userStore
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../../coordinators/friendRelationshipCoordinator', () => ({
|
||||||
|
confirmDeleteFriend: (...args) => mocks.confirmDeleteFriend(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../shared/utils', () => ({
|
vi.mock('../../../../shared/utils', () => ({
|
||||||
userImage: vi.fn(() => 'https://example.com/avatar.png'),
|
userImage: vi.fn(() => 'https://example.com/avatar.png'),
|
||||||
userStatusClass: vi.fn(() => 'status-online')
|
userStatusClass: vi.fn(() => 'status-online')
|
||||||
@@ -125,8 +136,8 @@ describe('FriendItem.vue', () => {
|
|||||||
mocks.appearanceStore.hideNicknames = false;
|
mocks.appearanceStore.hideNicknames = false;
|
||||||
mocks.friendStore.isRefreshFriendsLoading = false;
|
mocks.friendStore.isRefreshFriendsLoading = false;
|
||||||
mocks.friendStore.allFavoriteFriendIds = new Set();
|
mocks.friendStore.allFavoriteFriendIds = new Set();
|
||||||
mocks.friendStore.confirmDeleteFriend.mockReset();
|
mocks.confirmDeleteFriend.mockReset();
|
||||||
mocks.userStore.showUserDialog.mockReset();
|
mocks.showUserDialog.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders nickname when hideNicknames is false', () => {
|
test('renders nickname when hideNicknames is false', () => {
|
||||||
@@ -149,7 +160,7 @@ describe('FriendItem.vue', () => {
|
|||||||
test('clicking row opens user dialog', async () => {
|
test('clicking row opens user dialog', async () => {
|
||||||
const wrapper = mountItem();
|
const wrapper = mountItem();
|
||||||
await wrapper.get('div').trigger('click');
|
await wrapper.get('div').trigger('click');
|
||||||
expect(mocks.userStore.showUserDialog).toHaveBeenCalledWith('usr_1');
|
expect(mocks.showUserDialog).toHaveBeenCalledWith('usr_1');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('renders delete action for orphan friend and triggers confirmDeleteFriend', async () => {
|
test('renders delete action for orphan friend and triggers confirmDeleteFriend', async () => {
|
||||||
@@ -164,9 +175,9 @@ describe('FriendItem.vue', () => {
|
|||||||
expect(wrapper.text()).toContain('Ghost');
|
expect(wrapper.text()).toContain('Ghost');
|
||||||
const button = wrapper.get('[data-testid="delete-button"]');
|
const button = wrapper.get('[data-testid="delete-button"]');
|
||||||
await button.trigger('click');
|
await button.trigger('click');
|
||||||
expect(mocks.friendStore.confirmDeleteFriend).toHaveBeenCalledWith(
|
expect(mocks.confirmDeleteFriend).toHaveBeenCalledWith(
|
||||||
'usr_orphan'
|
'usr_orphan'
|
||||||
);
|
);
|
||||||
expect(mocks.userStore.showUserDialog).not.toHaveBeenCalled();
|
expect(mocks.showUserDialog).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ const mocks = vi.hoisted(() => ({
|
|||||||
gameLogDisabled: { value: false }
|
gameLogDisabled: { value: false }
|
||||||
},
|
},
|
||||||
userStore: {
|
userStore: {
|
||||||
showUserDialog: vi.fn(),
|
|
||||||
showSendBoopDialog: vi.fn(),
|
showSendBoopDialog: vi.fn(),
|
||||||
currentUser: {
|
currentUser: {
|
||||||
value: {
|
value: {
|
||||||
@@ -78,9 +77,13 @@ const mocks = vi.hoisted(() => ({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('@tanstack/vue-virtual', () => ({
|
vi.mock('@tanstack/vue-virtual', () => ({
|
||||||
useVirtualizer: (optionsRef) => ({
|
useVirtualizer: (optionsRef) => ({
|
||||||
@@ -111,6 +114,10 @@ vi.mock('../../../../stores', () => ({
|
|||||||
useUserStore: () => mocks.userStore
|
useUserStore: () => mocks.userStore
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: vi.fn()
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../shared/utils', () => ({
|
vi.mock('../../../../shared/utils', () => ({
|
||||||
getFriendsSortFunction: () => (a, b) => a.id.localeCompare(b.id),
|
getFriendsSortFunction: () => (a, b) => a.id.localeCompare(b.id),
|
||||||
isRealInstance: (location) =>
|
isRealInstance: (location) =>
|
||||||
|
|||||||
@@ -18,23 +18,26 @@ const mocks = vi.hoisted(() => ({
|
|||||||
},
|
},
|
||||||
userStore: {
|
userStore: {
|
||||||
cachedUsers: new Map(),
|
cachedUsers: new Map(),
|
||||||
showUserDialog: vi.fn(),
|
|
||||||
showSendBoopDialog: vi.fn()
|
showSendBoopDialog: vi.fn()
|
||||||
},
|
},
|
||||||
groupStore: {
|
groupStore: {},
|
||||||
showGroupDialog: vi.fn()
|
|
||||||
},
|
|
||||||
locationStore: {
|
locationStore: {
|
||||||
lastLocation: { value: { location: 'wrld_home:123' } }
|
lastLocation: { value: { location: 'wrld_home:123' } }
|
||||||
},
|
},
|
||||||
gameStore: {
|
gameStore: {
|
||||||
isGameRunning: { value: true }
|
isGameRunning: { value: true }
|
||||||
}
|
},
|
||||||
|
showUserDialog: vi.fn(),
|
||||||
|
showGroupDialog: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('pinia', () => ({
|
vi.mock('pinia', async (importOriginal) => {
|
||||||
storeToRefs: (store) => store
|
const actual = await importOriginal();
|
||||||
}));
|
return {
|
||||||
|
...actual,
|
||||||
|
storeToRefs: (store) => store
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
vi.mock('../../../../stores', () => ({
|
vi.mock('../../../../stores', () => ({
|
||||||
useNotificationStore: () => mocks.notificationStore,
|
useNotificationStore: () => mocks.notificationStore,
|
||||||
@@ -44,6 +47,14 @@ vi.mock('../../../../stores', () => ({
|
|||||||
useGameStore: () => mocks.gameStore
|
useGameStore: () => mocks.gameStore
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../../coordinators/userCoordinator', () => ({
|
||||||
|
showUserDialog: (...args) => mocks.showUserDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../../coordinators/groupCoordinator', () => ({
|
||||||
|
showGroupDialog: (...args) => mocks.showGroupDialog(...args)
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../../../../shared/utils', () => ({
|
vi.mock('../../../../shared/utils', () => ({
|
||||||
checkCanInvite: vi.fn(() => true),
|
checkCanInvite: vi.fn(() => true),
|
||||||
userImage: vi.fn(() => 'https://example.com/avatar.png')
|
userImage: vi.fn(() => 'https://example.com/avatar.png')
|
||||||
@@ -155,9 +166,9 @@ describe('NotificationItem.vue', () => {
|
|||||||
mocks.notificationStore.queueMarkAsSeen.mockReset();
|
mocks.notificationStore.queueMarkAsSeen.mockReset();
|
||||||
mocks.notificationStore.openNotificationLink.mockReset();
|
mocks.notificationStore.openNotificationLink.mockReset();
|
||||||
mocks.notificationStore.isNotificationExpired.mockReturnValue(false);
|
mocks.notificationStore.isNotificationExpired.mockReturnValue(false);
|
||||||
mocks.userStore.showUserDialog.mockReset();
|
mocks.showUserDialog.mockReset();
|
||||||
mocks.userStore.showSendBoopDialog.mockReset();
|
mocks.userStore.showSendBoopDialog.mockReset();
|
||||||
mocks.groupStore.showGroupDialog.mockReset();
|
mocks.showGroupDialog.mockReset();
|
||||||
mocks.userStore.cachedUsers = new Map();
|
mocks.userStore.cachedUsers = new Map();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,7 +181,7 @@ describe('NotificationItem.vue', () => {
|
|||||||
|
|
||||||
expect(wrapper.text()).toContain('Alice');
|
expect(wrapper.text()).toContain('Alice');
|
||||||
await wrapper.get('span.truncate.cursor-pointer').trigger('click');
|
await wrapper.get('span.truncate.cursor-pointer').trigger('click');
|
||||||
expect(mocks.userStore.showUserDialog).toHaveBeenCalledWith('usr_123');
|
expect(mocks.showUserDialog).toHaveBeenCalledWith('usr_123');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('clicking accept icon calls acceptFriendRequestNotification', async () => {
|
test('clicking accept icon calls acceptFriendRequestNotification', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user