Files
ncd-fe/packages/pl-fe/src/api/hooks/streaming/useUserStream.ts
marcin mikołajczak 859149230e pl-fe: cleanup, avoid parsing schemas more than once
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-09-01 15:36:24 +02:00

180 lines
5.8 KiB
TypeScript

import { useCallback } from 'react';
import { updateConversations } from 'pl-fe/actions/conversations';
import { fetchFilters } from 'pl-fe/actions/filters';
import { updateNotificationsQueue } from 'pl-fe/actions/notifications';
import { getLocale, getSettings } from 'pl-fe/actions/settings';
import { updateStatus } from 'pl-fe/actions/statuses';
import { deleteFromTimelines, processTimelineUpdate } from 'pl-fe/actions/timelines';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { selectEntity } from 'pl-fe/entity-store/selectors';
import { useAppDispatch, useLoggedIn } from 'pl-fe/hooks';
import messages from 'pl-fe/messages';
import { queryClient } from 'pl-fe/queries/client';
import { getUnreadChatsCount, updateChatListItem } from 'pl-fe/utils/chats';
import { play, soundCache } from 'pl-fe/utils/sounds';
import { updateReactions } from '../announcements/useAnnouncements';
import { useTimelineStream } from './useTimelineStream';
import type { Announcement, AnnouncementReaction, FollowRelationshipUpdate, Relationship, StreamingEvent } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const updateAnnouncementReactions = ({ announcement_id: id, name }: AnnouncementReaction) => {
queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) =>
prevResult.map(value => {
if (value.id !== id) return value;
return {
...value,
reactions: updateReactions(value.reactions, name, -1, 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;
const relationship = selectEntity<Relationship>(state, Entities.RELATIONSHIPS, update.following.id);
if (update.follower.id === me && relationship) {
const updated = {
...relationship,
...followStateToRelationship(update.state),
};
// Add a small delay to deal with API race conditions.
setTimeout(() => dispatch(importEntities([updated], Entities.RELATIONSHIPS)), 300);
}
};
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 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':
dispatch((dispatch, getState) => {
const locale = getLocale(getState());
messages[locale]().then(messages => {
dispatch(
updateNotificationsQueue(
event.payload,
messages,
locale,
window.location.pathname,
),
);
}).catch(error => {
console.error(error);
});
});
break;
case 'conversation':
dispatch(updateConversations(event.payload));
break;
case 'filters_changed':
dispatch(fetchFilters());
break;
case 'chat_update':
dispatch((_dispatch, getState) => {
const chat = event.payload;
const me = getState().me;
const messageOwned = chat.last_message?.account_id === me;
const settings = getSettings(getState());
// Don't update own messages from streaming
if (!messageOwned) {
updateChatListItem(chat);
if (settings.getIn(['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':
// dispatch({ type: MARKER_FETCH_SUCCESS, marker: JSON.parse(data.payload) });
// break;
}
}, []);
return useTimelineStream('user', {}, isLoggedIn, listener);
};
export { useUserStream };