Files
ncd-fe/packages/pl-fe/src/api/hooks/streaming/use-user-stream.ts
nicole mikołajczyk 1b1beceb4a nicolium: fix notification marker update
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
2026-02-26 15:18:17 +01:00

179 lines
5.5 KiB
TypeScript

import { useCallback } from 'react';
import { getLocale } from '@/actions/settings';
import { updateStatus } from '@/actions/statuses';
import { deleteFromTimelines, processTimelineUpdate } from '@/actions/timelines';
import { useStatContext } from '@/contexts/stat-context';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useLoggedIn } from '@/hooks/use-logged-in';
import messages from '@/messages';
import { queryClient } from '@/queries/client';
import { updateConversations } from '@/queries/conversations/use-conversations';
import { useProcessStreamNotification } from '@/queries/notifications/use-notifications';
import { useSettings } from '@/stores/settings';
import { getUnreadChatsCount, updateChatListItem } from '@/utils/chats';
import { play, soundCache } from '@/utils/sounds';
import { updateReactions } from '../../../queries/announcements/use-announcements';
import { useTimelineStream } from './use-timeline-stream';
import type { AppDispatch, RootState } from '@/store';
import type {
Announcement,
AnnouncementReaction,
FollowRelationshipUpdate,
Relationship,
StreamingEvent,
} from 'pl-api';
const updateAnnouncementReactions = (reaction: AnnouncementReaction) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map((value) => {
if (value.id !== reaction.announcement_id) return value;
return {
...value,
reactions: updateReactions(value.reactions, reaction.name, reaction.count, undefined, true),
};
}),
);
};
const updateAnnouncement = (announcement: Announcement) =>
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => {
let updated = false;
const result = prevResult.map((value) =>
value.id === announcement.id ? ((updated = true), announcement) : value,
);
if (!updated) return [announcement, ...result];
});
const deleteAnnouncement = (announcementId: string) =>
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.filter((value) => value.id !== announcementId),
);
const followStateToRelationship = (followState: FollowRelationshipUpdate['state']) => {
switch (followState) {
case 'follow_pending':
return { following: false, requested: true };
case 'follow_accept':
return { following: true, requested: false };
case 'follow_reject':
return { following: false, requested: false };
default:
return {};
}
};
const updateFollowRelationships =
(update: FollowRelationshipUpdate) => (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
const me = state.me;
if (update.follower.id === me) {
queryClient.setQueryData<Relationship>(
['accountRelationships', update.following.id],
(relationship) =>
relationship
? {
...relationship,
...followStateToRelationship(update.state),
}
: undefined,
);
}
};
const getTimelineFromStream = (stream: Array<string>) => {
switch (stream[0]) {
case 'user':
return 'home';
case 'hashtag':
case 'hashtag:local':
case 'list':
return `${stream[0]}:${stream[1]}`;
default:
return stream[0];
}
};
const useUserStream = () => {
const { isLoggedIn } = useLoggedIn();
const dispatch = useAppDispatch();
const statContext = useStatContext();
const settings = useSettings();
const processStreamNotification = useProcessStreamNotification();
const listener = useCallback((event: StreamingEvent) => {
switch (event.event) {
case 'update':
dispatch(processTimelineUpdate(getTimelineFromStream(event.stream), event.payload));
break;
case 'status.update':
dispatch(updateStatus(event.payload));
break;
case 'delete':
dispatch(deleteFromTimelines(event.payload));
break;
case 'notification':
messages[getLocale()]()
.then((messages) => {
processStreamNotification(event.payload, messages, getLocale());
})
.catch((error) => {
console.error(error);
});
break;
case 'conversation':
updateConversations(event.payload);
break;
case 'filters_changed':
queryClient.invalidateQueries({ queryKey: ['filters'] });
break;
case 'chat_update':
dispatch((_dispatch, getState) => {
const chat = event.payload;
const me = getState().me;
const messageOwned = chat.last_message?.account_id === me;
// Don't update own messages from streaming
if (!messageOwned) {
updateChatListItem(chat);
if (settings.chats.sound) {
play(soundCache.chat);
}
// Increment unread counter
statContext?.setUnreadChatsCount(getUnreadChatsCount());
}
});
break;
case 'follow_relationships_update':
dispatch(updateFollowRelationships(event.payload));
break;
case 'announcement':
updateAnnouncement(event.payload);
break;
case 'announcement.reaction':
updateAnnouncementReactions(event.payload);
break;
case 'announcement.delete':
deleteAnnouncement(event.payload);
break;
case 'marker':
queryClient.setQueryData(['markers', 'notifications'], event.payload.notifications ?? null);
break;
}
}, []);
return useTimelineStream('user', {}, isLoggedIn, listener);
};
export { useUserStream };