Add Streaming hooks
This commit is contained in:
parent
bea4707743
commit
77f0f4d377
6 changed files with 88 additions and 44 deletions
|
@ -73,8 +73,9 @@ const updateChatQuery = (chat: IChat) => {
|
||||||
queryClient.setQueryData<Chat>(ChatKeys.chat(chat.id), newChat as any);
|
queryClient.setQueryData<Chat>(ChatKeys.chat(chat.id), newChat as any);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface StreamOpts {
|
interface TimelineStreamOpts {
|
||||||
statContext?: IStatContext
|
statContext?: IStatContext
|
||||||
|
enabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectTimelineStream = (
|
const connectTimelineStream = (
|
||||||
|
@ -82,7 +83,7 @@ const connectTimelineStream = (
|
||||||
path: string,
|
path: string,
|
||||||
pollingRefresh: ((dispatch: AppDispatch, done?: () => void) => void) | null = null,
|
pollingRefresh: ((dispatch: AppDispatch, done?: () => void) => void) | null = null,
|
||||||
accept: ((status: APIEntity) => boolean) | null = null,
|
accept: ((status: APIEntity) => boolean) | null = null,
|
||||||
opts?: StreamOpts,
|
opts?: TimelineStreamOpts,
|
||||||
) => connectStream(path, pollingRefresh, (dispatch: AppDispatch, getState: () => RootState) => {
|
) => connectStream(path, pollingRefresh, (dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const locale = getLocale(getState());
|
const locale = getLocale(getState());
|
||||||
|
|
||||||
|
@ -196,7 +197,7 @@ const refreshHomeTimelineAndNotification = (dispatch: AppDispatch, done?: () =>
|
||||||
dispatch(expandNotifications({}, () =>
|
dispatch(expandNotifications({}, () =>
|
||||||
dispatch(fetchAnnouncements(done))))));
|
dispatch(fetchAnnouncements(done))))));
|
||||||
|
|
||||||
const connectUserStream = (opts?: StreamOpts) =>
|
const connectUserStream = (opts?: TimelineStreamOpts) =>
|
||||||
connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification, null, opts);
|
connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification, null, opts);
|
||||||
|
|
||||||
const connectCommunityStream = ({ onlyMedia }: Record<string, any> = {}) =>
|
const connectCommunityStream = ({ onlyMedia }: Record<string, any> = {}) =>
|
||||||
|
@ -236,4 +237,5 @@ export {
|
||||||
connectListStream,
|
connectListStream,
|
||||||
connectGroupStream,
|
connectGroupStream,
|
||||||
connectNostrStream,
|
connectNostrStream,
|
||||||
|
type TimelineStreamOpts,
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,3 +43,7 @@ export { useSuggestedGroups } from './groups/useSuggestedGroups';
|
||||||
export { useUnmuteGroup } from './groups/useUnmuteGroup';
|
export { useUnmuteGroup } from './groups/useUnmuteGroup';
|
||||||
export { useUpdateGroup } from './groups/useUpdateGroup';
|
export { useUpdateGroup } from './groups/useUpdateGroup';
|
||||||
export { useUpdateGroupTag } from './groups/useUpdateGroupTag';
|
export { useUpdateGroupTag } from './groups/useUpdateGroupTag';
|
||||||
|
|
||||||
|
// Streaming
|
||||||
|
export { useUserStream } from './streaming/useUserStream';
|
||||||
|
export { useNostrStream } from './streaming/useNostrStream';
|
11
app/soapbox/api/hooks/streaming/useNostrStream.ts
Normal file
11
app/soapbox/api/hooks/streaming/useNostrStream.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import { useFeatures } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import { useTimelineStream } from './useTimelineStream';
|
||||||
|
|
||||||
|
function useNostrStream() {
|
||||||
|
const features = useFeatures();
|
||||||
|
const enabled = features.nostrSign && Boolean(window.nostr);
|
||||||
|
return useTimelineStream('nostr', 'nostr', null, null, { enabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useNostrStream };
|
41
app/soapbox/api/hooks/streaming/useTimelineStream.ts
Normal file
41
app/soapbox/api/hooks/streaming/useTimelineStream.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
import { connectTimelineStream } from 'soapbox/actions/streaming';
|
||||||
|
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||||
|
import { getAccessToken } from 'soapbox/utils/auth';
|
||||||
|
|
||||||
|
function useTimelineStream(...args: Parameters<typeof connectTimelineStream>) {
|
||||||
|
// TODO: get rid of streaming.ts and move the actual opts here.
|
||||||
|
const { enabled = true } = args[4] ?? {};
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const instance = useInstance();
|
||||||
|
const stream = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
|
const accessToken = useAppSelector(getAccessToken);
|
||||||
|
const streamingUrl = instance.urls.get('streaming_api');
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
if (enabled && accessToken && streamingUrl && !stream.current) {
|
||||||
|
stream.current = dispatch(connectTimelineStream(...args));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const disconnect = () => {
|
||||||
|
if (stream.current) {
|
||||||
|
stream.current();
|
||||||
|
stream.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
connect();
|
||||||
|
return disconnect;
|
||||||
|
}, [accessToken, streamingUrl, enabled]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
disconnect,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useTimelineStream };
|
22
app/soapbox/api/hooks/streaming/useUserStream.ts
Normal file
22
app/soapbox/api/hooks/streaming/useUserStream.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { fetchAnnouncements } from 'soapbox/actions/announcements';
|
||||||
|
import { expandNotifications } from 'soapbox/actions/notifications';
|
||||||
|
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||||
|
import { useStatContext } from 'soapbox/contexts/stat-context';
|
||||||
|
|
||||||
|
import { useTimelineStream } from './useTimelineStream';
|
||||||
|
|
||||||
|
import type { AppDispatch } from 'soapbox/store';
|
||||||
|
|
||||||
|
function useUserStream() {
|
||||||
|
const statContext = useStatContext();
|
||||||
|
return useTimelineStream('home', 'user', refresh, null, { statContext });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Refresh home timeline and notifications. */
|
||||||
|
function refresh(dispatch: AppDispatch, done?: () => void) {
|
||||||
|
return dispatch(expandHomeTimeline({}, () =>
|
||||||
|
dispatch(expandNotifications({}, () =>
|
||||||
|
dispatch(fetchAnnouncements(done))))));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useUserStream };
|
|
@ -14,16 +14,15 @@ import { openModal } from 'soapbox/actions/modals';
|
||||||
import { expandNotifications } from 'soapbox/actions/notifications';
|
import { expandNotifications } from 'soapbox/actions/notifications';
|
||||||
import { register as registerPushNotifications } from 'soapbox/actions/push-notifications';
|
import { register as registerPushNotifications } from 'soapbox/actions/push-notifications';
|
||||||
import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses';
|
import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses';
|
||||||
import { connectNostrStream, connectUserStream } from 'soapbox/actions/streaming';
|
|
||||||
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions';
|
||||||
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
import { expandHomeTimeline } from 'soapbox/actions/timelines';
|
||||||
|
import { useNostrStream, useUserStream } from 'soapbox/api/hooks';
|
||||||
import GroupLookupHoc from 'soapbox/components/hoc/group-lookup-hoc';
|
import GroupLookupHoc from 'soapbox/components/hoc/group-lookup-hoc';
|
||||||
import withHoc from 'soapbox/components/hoc/with-hoc';
|
import withHoc from 'soapbox/components/hoc/with-hoc';
|
||||||
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
import SidebarNavigation from 'soapbox/components/sidebar-navigation';
|
||||||
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
import ThumbNavigation from 'soapbox/components/thumb-navigation';
|
||||||
import { Layout } from 'soapbox/components/ui';
|
import { Layout } from 'soapbox/components/ui';
|
||||||
import { useStatContext } from 'soapbox/contexts/stat-context';
|
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useDraggedFiles } from 'soapbox/hooks';
|
||||||
import { useAppDispatch, useAppSelector, useOwnAccount, useSoapboxConfig, useFeatures, useInstance, useDraggedFiles } from 'soapbox/hooks';
|
|
||||||
import AdminPage from 'soapbox/pages/admin-page';
|
import AdminPage from 'soapbox/pages/admin-page';
|
||||||
import ChatsPage from 'soapbox/pages/chats-page';
|
import ChatsPage from 'soapbox/pages/chats-page';
|
||||||
import DefaultPage from 'soapbox/pages/default-page';
|
import DefaultPage from 'soapbox/pages/default-page';
|
||||||
|
@ -39,7 +38,7 @@ import RemoteInstancePage from 'soapbox/pages/remote-instance-page';
|
||||||
import SearchPage from 'soapbox/pages/search-page';
|
import SearchPage from 'soapbox/pages/search-page';
|
||||||
import StatusPage from 'soapbox/pages/status-page';
|
import StatusPage from 'soapbox/pages/status-page';
|
||||||
import { usePendingPolicy } from 'soapbox/queries/policies';
|
import { usePendingPolicy } from 'soapbox/queries/policies';
|
||||||
import { getAccessToken, getVapidKey } from 'soapbox/utils/auth';
|
import { getVapidKey } from 'soapbox/utils/auth';
|
||||||
import { isStandalone } from 'soapbox/utils/state';
|
import { isStandalone } from 'soapbox/utils/state';
|
||||||
|
|
||||||
import BackgroundShapes from './components/background-shapes';
|
import BackgroundShapes from './components/background-shapes';
|
||||||
|
@ -363,21 +362,13 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { data: pendingPolicy } = usePendingPolicy();
|
const { data: pendingPolicy } = usePendingPolicy();
|
||||||
const instance = useInstance();
|
|
||||||
const statContext = useStatContext();
|
|
||||||
|
|
||||||
const userStream = useRef<any>(null);
|
|
||||||
const nostrStream = useRef<any>(null);
|
|
||||||
const node = useRef<HTMLDivElement | null>(null);
|
const node = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const me = useAppSelector(state => state.me);
|
const me = useAppSelector(state => state.me);
|
||||||
const { account } = useOwnAccount();
|
const { account } = useOwnAccount();
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const vapidKey = useAppSelector(state => getVapidKey(state));
|
const vapidKey = useAppSelector(state => getVapidKey(state));
|
||||||
|
|
||||||
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.isOpen);
|
const dropdownMenuIsOpen = useAppSelector(state => state.dropdown_menu.isOpen);
|
||||||
const accessToken = useAppSelector(state => getAccessToken(state));
|
|
||||||
const streamingUrl = instance.urls.get('streaming_api');
|
|
||||||
const standalone = useAppSelector(isStandalone);
|
const standalone = useAppSelector(isStandalone);
|
||||||
|
|
||||||
const { isDragging } = useDraggedFiles(node);
|
const { isDragging } = useDraggedFiles(node);
|
||||||
|
@ -390,28 +381,6 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectStreaming = () => {
|
|
||||||
if (accessToken && streamingUrl) {
|
|
||||||
if (!userStream.current) {
|
|
||||||
userStream.current = dispatch(connectUserStream({ statContext }));
|
|
||||||
}
|
|
||||||
if (!nostrStream.current && features.nostrSign && window.nostr) {
|
|
||||||
nostrStream.current = dispatch(connectNostrStream());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const disconnectStreaming = () => {
|
|
||||||
if (userStream.current) {
|
|
||||||
userStream.current();
|
|
||||||
userStream.current = null;
|
|
||||||
}
|
|
||||||
if (nostrStream.current) {
|
|
||||||
nostrStream.current();
|
|
||||||
nostrStream.current = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDragEnter = (e: DragEvent) => e.preventDefault();
|
const handleDragEnter = (e: DragEvent) => e.preventDefault();
|
||||||
const handleDragLeave = (e: DragEvent) => e.preventDefault();
|
const handleDragLeave = (e: DragEvent) => e.preventDefault();
|
||||||
const handleDragOver = (e: DragEvent) => e.preventDefault();
|
const handleDragOver = (e: DragEvent) => e.preventDefault();
|
||||||
|
@ -458,10 +427,6 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
if (window.Notification?.permission === 'default') {
|
if (window.Notification?.permission === 'default') {
|
||||||
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
|
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
|
||||||
disconnectStreaming();
|
|
||||||
};
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -477,9 +442,8 @@ const UI: React.FC<IUI> = ({ children }) => {
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useUserStream();
|
||||||
connectStreaming();
|
useNostrStream();
|
||||||
}, [accessToken, streamingUrl]);
|
|
||||||
|
|
||||||
// The user has logged in
|
// The user has logged in
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
Loading…
Reference in a new issue