mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
fix(app): refresh mutual communities on membership change (#2559)
This commit is contained in:
1 parent
00c1b19809
commit
6c0ce9369b
6 files changed
+126
-32
No files matched your search
@@ -23,6 +23,7 @@ import ReadStates from '@app/features/read_state/state/ReadStates';
|
||||
import QuickSwitcher from '@app/features/search/state/QuickSwitcher';
|
||||
import Nagbar from '@app/features/ui/state/Nagbar';
|
||||
import UserGuildSettings from '@app/features/user/state/UserGuildSettings';
|
||||
import UserProfile from '@app/features/user/state/UserProfile';
|
||||
import MediaEngine from '@app/features/voice/engine/MediaEngineFacade';
|
||||
import {FAVORITES_GUILD_ID} from '@fluxer/constants/src/AppConstants';
|
||||
|
||||
@@ -51,6 +52,7 @@ export function handleGuildCreate(data: GuildReadyData, _context: GatewayHandler
|
||||
GuildCount.handleGuildCreate(data);
|
||||
if (!isSync) {
|
||||
MemberSidebar.handleGuildCreate(data.id);
|
||||
UserProfile.handleGuildCreate();
|
||||
}
|
||||
if (!data.unavailable) {
|
||||
Channels.handleGuildCreate(data);
|
||||
|
||||
@@ -20,6 +20,7 @@ import MentionFeed from '@app/features/notification/state/MentionFeed';
|
||||
import Permission from '@app/features/permissions/state/Permission';
|
||||
import Presence from '@app/features/presence/state/Presence';
|
||||
import QuickSwitcher from '@app/features/search/state/QuickSwitcher';
|
||||
import UserProfile from '@app/features/user/state/UserProfile';
|
||||
import MediaEngine from '@app/features/voice/engine/MediaEngineFacade';
|
||||
import Webhooks from '@app/features/webhook/state/Webhooks';
|
||||
import type {Guild} from '@fluxer/schema/src/domains/guild/GuildResponseSchemas';
|
||||
@@ -52,5 +53,6 @@ export function handleGuildDelete(data: GuildDeletePayload, _context: GatewayHan
|
||||
Messages.handleGuildUnavailable(data.id, data.unavailable ?? false);
|
||||
Messages.handleCleanup();
|
||||
MentionFeed.handleGuildDelete(data.id);
|
||||
UserProfile.handleGuildDelete(data.unavailable);
|
||||
QuickSwitcher.recomputeIfOpen();
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import GuildMembers from '@app/features/member/state/GuildMembers';
|
||||
import MemberSearch from '@app/features/member/state/MemberSearch';
|
||||
import Permission from '@app/features/permissions/state/Permission';
|
||||
import Presence from '@app/features/presence/state/Presence';
|
||||
import UserProfile from '@app/features/user/state/UserProfile';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import type {GuildMemberData} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
|
||||
import type {User} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
|
||||
@@ -21,4 +22,5 @@ export function handleGuildMemberAdd(data: GuildMemberAddPayload, _context: Gate
|
||||
GuildVerification.handleGuildMemberUpdate(data.guild_id);
|
||||
Presence.handleGuildMemberAdd(data.guild_id, data.user.id);
|
||||
MemberSearch.handleMemberAdd(data.guild_id, data.user.id);
|
||||
UserProfile.handleGuildMemberAdd(data.user.id);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import GuildMembers from '@app/features/member/state/GuildMembers';
|
||||
import MemberSearch from '@app/features/member/state/MemberSearch';
|
||||
import Permission from '@app/features/permissions/state/Permission';
|
||||
import Presence from '@app/features/presence/state/Presence';
|
||||
import UserProfile from '@app/features/user/state/UserProfile';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import type {UserPartial} from '@fluxer/schema/src/domains/user/UserResponseSchemas';
|
||||
|
||||
@@ -19,4 +20,5 @@ export function handleGuildMemberRemove(data: GuildMemberRemovePayload, _context
|
||||
GuildMembers.handleMemberRemove(data.guild_id, data.user.id);
|
||||
Permission.handleGuildMemberUpdate(data.user.id);
|
||||
Presence.handleGuildMemberRemove(data.guild_id, data.user.id);
|
||||
UserProfile.handleGuildMemberRemove(data.user.id);
|
||||
}
|
||||
@@ -24,26 +24,30 @@ class UserProfile {
|
||||
}
|
||||
|
||||
handleGatewayReady(): void {
|
||||
Object.values(this.profileTimeouts).forEach(clearTimeout);
|
||||
this.profiles = {};
|
||||
this.profileTimeouts = {};
|
||||
this.clearAllProfiles();
|
||||
}
|
||||
|
||||
handleProfileInvalidate(userId: string, guildId?: string): void {
|
||||
const targetGuildId = guildId ?? ME;
|
||||
this.clearProfileTimeout(userId, targetGuildId);
|
||||
const userProfiles = this.profiles[userId];
|
||||
if (!userProfiles) return;
|
||||
const {[targetGuildId]: _, ...remainingGuildProfiles} = userProfiles;
|
||||
if (Object.keys(remainingGuildProfiles).length === 0) {
|
||||
const {[userId]: __, ...remainingProfiles} = this.profiles;
|
||||
this.profiles = remainingProfiles;
|
||||
} else {
|
||||
this.profiles = {
|
||||
...this.profiles,
|
||||
[userId]: remainingGuildProfiles,
|
||||
};
|
||||
}
|
||||
this.removeProfile(userId, targetGuildId);
|
||||
}
|
||||
|
||||
handleGuildMemberAdd(userId: string): void {
|
||||
this.clearUserProfiles(userId);
|
||||
}
|
||||
|
||||
handleGuildMemberRemove(userId: string): void {
|
||||
this.clearUserProfiles(userId);
|
||||
}
|
||||
|
||||
handleGuildCreate(): void {
|
||||
this.clearAllProfiles();
|
||||
}
|
||||
|
||||
handleGuildDelete(unavailable?: boolean): void {
|
||||
if (unavailable) return;
|
||||
this.clearAllProfiles();
|
||||
}
|
||||
|
||||
handleProfileCreate(profile: Profile): void {
|
||||
@@ -82,6 +86,37 @@ class UserProfile {
|
||||
this.profileTimeouts = updatedTimeouts;
|
||||
}
|
||||
|
||||
private clearAllProfiles(): void {
|
||||
Object.values(this.profileTimeouts).forEach(clearTimeout);
|
||||
this.profiles = {};
|
||||
this.profileTimeouts = {};
|
||||
}
|
||||
|
||||
private clearUserProfiles(userId: string): void {
|
||||
const userProfiles = this.profiles[userId];
|
||||
if (!userProfiles) return;
|
||||
for (const guildId of Object.keys(userProfiles)) {
|
||||
this.clearProfileTimeout(userId, guildId);
|
||||
}
|
||||
const {[userId]: _, ...remainingProfiles} = this.profiles;
|
||||
this.profiles = remainingProfiles;
|
||||
}
|
||||
|
||||
private removeProfile(userId: string, guildId: string): void {
|
||||
const userProfiles = this.profiles[userId];
|
||||
if (!userProfiles) return;
|
||||
const {[guildId]: _, ...remainingGuildProfiles} = userProfiles;
|
||||
if (Object.keys(remainingGuildProfiles).length === 0) {
|
||||
const {[userId]: __, ...remainingProfiles} = this.profiles;
|
||||
this.profiles = remainingProfiles;
|
||||
} else {
|
||||
this.profiles = {
|
||||
...this.profiles,
|
||||
[userId]: remainingGuildProfiles,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private createTimeoutKey(userId: string, guildId: string): string {
|
||||
return `${userId}:${guildId}`;
|
||||
}
|
||||
@@ -101,23 +136,8 @@ class UserProfile {
|
||||
this.clearProfileTimeout(userId, guildId);
|
||||
const timeout = setTimeout(() => {
|
||||
runInAction(() => {
|
||||
const userProfiles = this.profiles[userId];
|
||||
if (!userProfiles) {
|
||||
const {[timeoutKey]: _, ...remainingTimeouts} = this.profileTimeouts;
|
||||
this.profileTimeouts = remainingTimeouts;
|
||||
return;
|
||||
}
|
||||
const {[guildId]: _, ...remainingGuildProfiles} = userProfiles;
|
||||
if (Object.keys(remainingGuildProfiles).length === 0) {
|
||||
const {[userId]: __, ...remainingProfiles} = this.profiles;
|
||||
this.profiles = remainingProfiles;
|
||||
} else {
|
||||
this.profiles = {
|
||||
...this.profiles,
|
||||
[userId]: remainingGuildProfiles,
|
||||
};
|
||||
}
|
||||
const {[timeoutKey]: ___, ...remainingTimeouts} = this.profileTimeouts;
|
||||
this.removeProfile(userId, guildId);
|
||||
const {[timeoutKey]: _, ...remainingTimeouts} = this.profileTimeouts;
|
||||
this.profileTimeouts = remainingTimeouts;
|
||||
});
|
||||
}, PROFILE_TIMEOUT_MS);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {Profile} from '@app/features/user/models/Profile';
|
||||
import {afterEach, describe, expect, it, vi} from 'vitest';
|
||||
|
||||
vi.mock('@app/features/auth/state/Authentication', () => ({default: {currentUserId: null}}));
|
||||
|
||||
const {default: UserProfile} = await import('@app/features/user/state/UserProfile');
|
||||
|
||||
const GUILD_ID = '1000';
|
||||
const TARGET_ID = '2000';
|
||||
|
||||
function cache(userId: string = TARGET_ID, guildId?: string): void {
|
||||
UserProfile.handleProfileCreate({
|
||||
userId,
|
||||
guildId: guildId ?? null,
|
||||
mutualGuilds: [{id: GUILD_ID, nick: null}],
|
||||
} as unknown as Profile);
|
||||
}
|
||||
|
||||
describe('UserProfile cache invalidation', () => {
|
||||
afterEach(() => {
|
||||
UserProfile.handleGatewayReady();
|
||||
});
|
||||
|
||||
it('drops every cached scope for a member removed from a community', () => {
|
||||
cache();
|
||||
cache(TARGET_ID, GUILD_ID);
|
||||
expect(UserProfile.getProfile(TARGET_ID)?.mutualGuilds).toHaveLength(1);
|
||||
UserProfile.handleGuildMemberRemove(TARGET_ID);
|
||||
expect(UserProfile.getProfile(TARGET_ID)).toBeNull();
|
||||
expect(UserProfile.getProfile(TARGET_ID, GUILD_ID)).toBeNull();
|
||||
});
|
||||
|
||||
it('drops every cached scope for a member who joined a community', () => {
|
||||
cache();
|
||||
UserProfile.handleGuildMemberAdd(TARGET_ID);
|
||||
expect(UserProfile.getProfile(TARGET_ID)).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps other users cached when one member is removed', () => {
|
||||
cache();
|
||||
cache('3000');
|
||||
UserProfile.handleGuildMemberRemove(TARGET_ID);
|
||||
expect(UserProfile.getProfile(TARGET_ID)).toBeNull();
|
||||
expect(UserProfile.getProfile('3000')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('drops cached profiles when the viewer leaves a community', () => {
|
||||
cache();
|
||||
UserProfile.handleGuildDelete(false);
|
||||
expect(UserProfile.getProfile(TARGET_ID)).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps cached profiles when a community only goes unavailable', () => {
|
||||
cache();
|
||||
UserProfile.handleGuildDelete(true);
|
||||
expect(UserProfile.getProfile(TARGET_ID)).not.toBeNull();
|
||||
});
|
||||
|
||||
it('drops cached profiles when the viewer joins a community', () => {
|
||||
cache();
|
||||
UserProfile.handleGuildCreate();
|
||||
expect(UserProfile.getProfile(TARGET_ID)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user