Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/meteor/client/views/room/RoomOpenerEmbedded.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { useTranslation } from 'react-i18next';

import NotSubscribedRoom from './NotSubscribedRoom';
import RoomSkeleton from './RoomSkeleton';
import { useOpenRoom } from './hooks/useOpenRoom';
import { SubscriptionsCachedStore } from '../../cachedStores';
import { getErrorMessage } from '../../lib/errorHandling';
import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError';
import { NotSubscribedToRoomError } from '../../lib/errors/NotSubscribedToRoomError';
import { OldUrlRoomError } from '../../lib/errors/OldUrlRoomError';
import { RoomNotFoundError } from '../../lib/errors/RoomNotFoundError';
import { useEmbeddedRoomState } from '../root/MainLayout/EmbeddedPreload';

const RoomProvider = lazy(() => import('./providers/RoomProvider'));
const RoomNotFound = lazy(() => import('./RoomNotFound'));
Expand All @@ -28,7 +28,7 @@ type RoomOpenerProps = {
};

const RoomOpenerEmbedded = ({ type, reference }: RoomOpenerProps): ReactElement => {
const { data, error, isSuccess, isError, isLoading } = useOpenRoom({ type, reference });
const { data, error, isSuccess, isError } = useEmbeddedRoomState();
const uid = useUserId();
const subscribeToNotifyUser = useStream('notify-user');

Expand All @@ -53,7 +53,6 @@ const RoomOpenerEmbedded = ({ type, reference }: RoomOpenerProps): ReactElement
return (
<Box display='flex' w='full' h='full'>
<Suspense fallback={<RoomSkeleton />}>
{isLoading && <RoomSkeleton />}
{isSuccess && (
<RoomProvider rid={data.rid}>
<Room />
Expand Down
18 changes: 17 additions & 1 deletion apps/meteor/client/views/room/hooks/useOpenRoom.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { isPublicRoom, type IRoom, type RoomType } from '@rocket.chat/core-typings';
import { getObjectKeys } from '@rocket.chat/tools';
import { useMethod, usePermission, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useEndpoint, useMethod, usePermission, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';

import { useOpenRoomMutation } from './useOpenRoomMutation';
import { roomFields } from '../../../../lib/publishFields';
import { SubscriptionsCachedStore } from '../../../cachedStores';
import { NotAuthorizedError } from '../../../lib/errors/NotAuthorizedError';
import { NotSubscribedToRoomError } from '../../../lib/errors/NotSubscribedToRoomError';
import { OldUrlRoomError } from '../../../lib/errors/OldUrlRoomError';
import { RoomNotFoundError } from '../../../lib/errors/RoomNotFoundError';
import { roomsQueryKeys } from '../../../lib/queryKeys';
import { mapSubscriptionFromApi } from '../../../lib/utils/mapSubscriptionFromApi';
import { Rooms } from '../../../stores';

export function useOpenRoom({ type, reference }: { type: RoomType; reference: string }) {
Expand All @@ -19,6 +21,7 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st
const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead', true);
const getRoomByTypeAndName = useMethod('getRoomByTypeAndName');
const createDirectMessage = useMethod('createDirectMessage');
const getSubscription = useEndpoint('GET', '/v1/subscriptions.getOne');
const directRoute = useRoute('direct');
const openRoom = useOpenRoomMutation();

Expand Down Expand Up @@ -74,6 +77,19 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st

const { LegacyRoomManager } = await import('../../../../app/ui-utils/client');

// The subscription may not be in the store yet — mainly in embedded mode, which skips
// the full subscription list preload. Fetch it on demand if missing.
if (user && !Subscriptions.state.find((record) => record.rid === reference || record.name === reference)) {
try {
const { subscription } = await getSubscription({ roomId: room._id });
if (subscription) {
await SubscriptionsCachedStore.upsertSubscription(mapSubscriptionFromApi(subscription));
}
} catch {
// Fall through to existing not-subscribed handling.
}
}

const sub = Subscriptions.state.find((record) => record.rid === reference || record.name === reference);

if (reference !== undefined && room._id !== reference && type === 'd') {
Expand Down
35 changes: 35 additions & 0 deletions apps/meteor/client/views/room/hooks/useOpenRoomParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { RoomType } from '@rocket.chat/core-typings';
import { useRouter } from '@rocket.chat/ui-contexts';
import { useLayoutEffect, useState } from 'react';

import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';

type OpenRoomParams = { type: RoomType; reference: string };

const extractFromRouter = (router: ReturnType<typeof useRouter>): OpenRoomParams | null => {
const routeName = router.getRouteName();
if (!routeName) {
return null;
}

const identifier = roomCoordinator.getRouteNameIdentifier(routeName);
if (!identifier) {
return null;
}

const directives = roomCoordinator.getRoomDirectives(identifier);
if (!directives?.extractOpenRoomParams) {
return null;
}

return directives.extractOpenRoomParams(router.getRouteParameters());
};

export const useOpenRoomParams = (): OpenRoomParams | null => {
const router = useRouter();
const [params, setParams] = useState(() => extractFromRouter(router));

useLayoutEffect(() => router.subscribeToRouteChange(() => setParams(extractFromRouter(router))), [router]);

return params;
};
81 changes: 23 additions & 58 deletions apps/meteor/client/views/root/MainLayout/EmbeddedPreload.tsx
Original file line number Diff line number Diff line change
@@ -1,75 +1,40 @@
import { useEndpoint, useMethod, useRouter, useUserId } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import type { ReactElement, ReactNode } from 'react';
import { useEffect, useMemo } from 'react';
import { createContext, useContext, useEffect, type ReactElement, type ReactNode } from 'react';

import EmbeddedRoomPreload from './EmbeddedRoomPreload';
import { RoomsCachedStore, SubscriptionsCachedStore } from '../../../cachedStores';
import { roomsQueryKeys } from '../../../lib/queryKeys';
import { roomCoordinator } from '../../../lib/rooms/roomCoordinator';
import { mapSubscriptionFromApi } from '../../../lib/utils/mapSubscriptionFromApi';
import type { useOpenRoom } from '../../room/hooks/useOpenRoom';
import { useOpenRoomParams } from '../../room/hooks/useOpenRoomParams';
import PageLoading from '../PageLoading';
import { useMainReady } from '../hooks/useMainReady';

const EmbeddedPreload = ({ children }: { children: ReactNode }): ReactElement => {
const ready = useMainReady();
const router = useRouter();
const uid = useUserId();

const roomParams = useMemo(() => {
const routeName = router.getRouteName();
if (!routeName) {
return null;
}

const identifier = roomCoordinator.getRouteNameIdentifier(routeName);
if (!identifier) {
return null;
}

const directives = roomCoordinator.getRoomDirectives(identifier);
if (!directives?.extractOpenRoomParams) {
return null;
}

return directives.extractOpenRoomParams(router.getRouteParameters());
}, [router]);
type EmbeddedRoomState = ReturnType<typeof useOpenRoom>;

const getRoomByTypeAndName = useMethod('getRoomByTypeAndName');
const getSubscription = useEndpoint('GET', '/v1/subscriptions.getOne');
export const EmbeddedRoomContext = createContext<EmbeddedRoomState | null>(null);

const shouldFetch = !!roomParams && !!uid;

const { isLoading, isSuccess, isError } = useQuery({
queryKey: roomParams ? roomsQueryKeys.roomReference(roomParams.reference, roomParams.type, uid ?? undefined) : [],
queryFn: async () => {
if (!roomParams) {
return null;
}

const roomData = await getRoomByTypeAndName(roomParams.type, roomParams.reference);
if (!roomData?._id) {
return null;
}

const subResult = await getSubscription({ roomId: roomData._id });
if (subResult.subscription) {
SubscriptionsCachedStore.upsertSubscription(mapSubscriptionFromApi(subResult.subscription));
}
export const useEmbeddedRoomState = (): EmbeddedRoomState => {
const ctx = useContext(EmbeddedRoomContext);
if (!ctx) {
throw new Error('useEmbeddedRoomState must be used inside EmbeddedPreload');
}
return ctx;
};

return subResult;
},
enabled: shouldFetch,
retry: false,
});
const EmbeddedPreload = ({ children }: { children: ReactNode }): ReactElement => {
const ready = useMainReady();
const params = useOpenRoomParams();

useEffect(() => {
if (!shouldFetch || isSuccess || isError) {
if (!params) {
SubscriptionsCachedStore.setReady(true);
RoomsCachedStore.setReady(true);
}
}, [shouldFetch, isSuccess, isError]);
}, [params]);

if (params) {
return <EmbeddedRoomPreload params={params}>{children}</EmbeddedRoomPreload>;
}

if (!ready || (shouldFetch && isLoading)) {
if (!ready) {
return <PageLoading />;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { RoomType } from '@rocket.chat/core-typings';
import { useEffect, type ReactElement, type ReactNode } from 'react';

import { RoomsCachedStore, SubscriptionsCachedStore } from '../../../cachedStores';
import { useOpenRoom } from '../../room/hooks/useOpenRoom';
import PageLoading from '../PageLoading';
import { EmbeddedRoomContext } from './EmbeddedPreload';
import { useMainReady } from '../hooks/useMainReady';

const EmbeddedRoomPreload = ({
params,
children,
}: {
params: { type: RoomType; reference: string };
children: ReactNode;
}): ReactElement => {
const ready = useMainReady();
const state = useOpenRoom(params);

useEffect(() => {
if (!state.isLoading) {
SubscriptionsCachedStore.setReady(true);
RoomsCachedStore.setReady(true);
}
}, [state.isLoading]);

if (!ready || state.isLoading) {
return <PageLoading />;
}

return <EmbeddedRoomContext.Provider value={state}>{children}</EmbeddedRoomContext.Provider>;
};

export default EmbeddedRoomPreload;
Loading