diff --git a/apps/meteor/app/models/client/models/CachedChatSubscription.ts b/apps/meteor/app/models/client/models/CachedChatSubscription.ts index 6ea6ea6917e9..28b55a70197d 100644 --- a/apps/meteor/app/models/client/models/CachedChatSubscription.ts +++ b/apps/meteor/app/models/client/models/CachedChatSubscription.ts @@ -125,6 +125,10 @@ class CachedChatSubscription extends CachedCollection { + return this.handleRecordEvent('changed', record); + } + protected deserializeFromCache(record: unknown) { const deserialized = super.deserializeFromCache(record); diff --git a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts index 5f82e47921f8..bf33821de4a4 100644 --- a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts +++ b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts @@ -10,7 +10,7 @@ import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent'; import { getConfig } from '../../../../client/lib/utils/getConfig'; import { callbacks } from '../../../../lib/callbacks'; -import { CachedChatRoom, Messages, Subscriptions, CachedChatSubscription } from '../../../models/client'; +import { Messages, Subscriptions, CachedChatSubscription } from '../../../models/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; const maxRoomsOpen = parseInt(getConfig('maxRoomsOpen') ?? '5') || 5; @@ -79,8 +79,7 @@ function getOpenedRoomByRid(rid: IRoom['_id']) { } const computation = Tracker.autorun(() => { - const ready = CachedChatRoom.ready.get() && mainReady.get(); - if (ready !== true) { + if (!mainReady.get()) { return; } Tracker.nonreactive(() => diff --git a/apps/meteor/client/lib/cachedCollections/CachedCollection.ts b/apps/meteor/client/lib/cachedCollections/CachedCollection.ts index 8439afe648c8..9e9e5107d3f2 100644 --- a/apps/meteor/client/lib/cachedCollections/CachedCollection.ts +++ b/apps/meteor/client/lib/cachedCollections/CachedCollection.ts @@ -69,23 +69,6 @@ export class CachedCollection { : () => undefined; CachedCollectionManager.register(this); - - if (!userRelated) { - void this.init(); - return; - } - - if (process.env.NODE_ENV === 'test') { - return; - } - - onLoggedIn(() => { - void this.init(); - }); - - Accounts.onLogout(() => { - this.ready.set(false); - }); } protected get eventName(): `${Name}-changed` | `${string}/${Name}-changed` { @@ -239,23 +222,27 @@ export class CachedCollection { async setupListener() { sdk.stream(this.eventType, [this.eventName], (async (action: 'removed' | 'changed', record: any) => { this.log('record received', action, record); - const newRecord = this.handleReceived(record, action); + await this.handleRecordEvent(action, record); + }) as (...args: unknown[]) => void); + } - if (!hasId(newRecord)) { - return; - } + protected async handleRecordEvent(action: 'removed' | 'changed', record: any) { + const newRecord = this.handleReceived(record, action); - if (action === 'removed') { - this.collection.remove(newRecord._id); - } else { - const { _id } = newRecord; - if (!_id) { - return; - } - this.collection.upsert({ _id } as any, newRecord); + if (!hasId(newRecord)) { + return; + } + + if (action === 'removed') { + this.collection.remove(newRecord._id); + } else { + const { _id } = newRecord; + if (!_id) { + return; } - await this.save(); - }) as (...args: unknown[]) => void); + this.collection.upsert({ _id } as any, newRecord); + } + await this.save(); } trySync(delay = 10) { @@ -368,4 +355,23 @@ export class CachedCollection { } private reconnectionComputation: Tracker.Computation | undefined; + + listen() { + if (!this.userRelated) { + void this.init(); + return; + } + + if (process.env.NODE_ENV === 'test') { + return; + } + + onLoggedIn(() => { + void this.init(); + }); + + Accounts.onLogout(() => { + this.ready.set(false); + }); + } } diff --git a/apps/meteor/client/providers/AuthorizationProvider.tsx b/apps/meteor/client/providers/AuthorizationProvider.tsx index ceda8eb681ac..538626ebc409 100644 --- a/apps/meteor/client/providers/AuthorizationProvider.tsx +++ b/apps/meteor/client/providers/AuthorizationProvider.tsx @@ -6,7 +6,7 @@ import type { ReactNode } from 'react'; import React, { useCallback, useEffect } from 'react'; import { hasPermission, hasAtLeastOnePermission, hasAllPermission, hasRole } from '../../app/authorization/client'; -import { Roles } from '../../app/models/client/models/Roles'; +import { Roles, AuthzCachedCollection } from '../../app/models/client'; import { useReactiveValue } from '../hooks/useReactiveValue'; import { createReactiveSubscriptionFactory } from '../lib/createReactiveSubscriptionFactory'; @@ -48,6 +48,10 @@ const AuthorizationProvider = ({ children }: AuthorizationProviderProps) => { ), ); + useEffect(() => { + AuthzCachedCollection.listen(); + }, []); + useEffect(() => { contextValue.roleStore.roles = roles; contextValue.roleStore.emit('change', roles); diff --git a/apps/meteor/client/views/room/RoomOpenerEmbedded.tsx b/apps/meteor/client/views/room/RoomOpenerEmbedded.tsx new file mode 100644 index 000000000000..e2101e8d2f08 --- /dev/null +++ b/apps/meteor/client/views/room/RoomOpenerEmbedded.tsx @@ -0,0 +1,112 @@ +import type { ISubscription, RoomType } from '@rocket.chat/core-typings'; +import { Box, States, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; +import { FeaturePreviewOff, FeaturePreviewOn } from '@rocket.chat/ui-client'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import type { ReactElement } from 'react'; +import React, { lazy, Suspense } from 'react'; +import { useTranslation } from 'react-i18next'; + +import RoomSkeleton from './RoomSkeleton'; +import RoomSidepanel from './Sidepanel/RoomSidepanel'; +import { useOpenRoom } from './hooks/useOpenRoom'; +import { CachedChatSubscription } from '../../../app/models/client'; +import { FeaturePreviewSidePanelNavigation } from '../../components/FeaturePreviewSidePanelNavigation'; +import { Header } from '../../components/Header'; +import { getErrorMessage } from '../../lib/errorHandling'; +import { NotAuthorizedError } from '../../lib/errors/NotAuthorizedError'; +import { OldUrlRoomError } from '../../lib/errors/OldUrlRoomError'; +import { RoomNotFoundError } from '../../lib/errors/RoomNotFoundError'; + +const RoomProvider = lazy(() => import('./providers/RoomProvider')); +const RoomNotFound = lazy(() => import('./RoomNotFound')); +const Room = lazy(() => import('./Room')); +const RoomLayout = lazy(() => import('./layout/RoomLayout')); +const NotAuthorizedPage = lazy(() => import('../notAuthorized/NotAuthorizedPage')); + +type RoomOpenerProps = { + type: RoomType; + reference: string; +}; + +const isDirectOrOmnichannelRoom = (type: RoomType) => type === 'd' || type === 'l'; + +const RoomOpenerEmbedded = ({ type, reference }: RoomOpenerProps): ReactElement => { + const { data, error, isSuccess, isError, isLoading } = useOpenRoom({ type, reference }); + + const getSubscription = useEndpoint('GET', '/v1/subscriptions.getOne'); + + const rid = data?.rid; + useQuery( + ['subscriptions', rid], + () => { + if (!rid) { + throw new Error('Room not found'); + } + return getSubscription({ roomId: rid }); + }, + { + enabled: !!rid, + suspense: true, + onSuccess({ subscription }) { + if (!subscription) { + throw new Error('Room not found'); + } + CachedChatSubscription.upsertSubscription(subscription as unknown as ISubscription); + }, + }, + ); + + const { t } = useTranslation(); + + return ( + + {!isDirectOrOmnichannelRoom(type) && ( + + {null} + + + + + )} + + }> + {isLoading && } + {isSuccess && ( + + + + )} + {isError && + (() => { + if (error instanceof OldUrlRoomError) { + return ; + } + + if (error instanceof RoomNotFoundError) { + return ; + } + + if (error instanceof NotAuthorizedError) { + return ; + } + + return ( + } + body={ + + + {t('core.Error')} + {getErrorMessage(error)} + + } + /> + ); + })()} + + + ); +}; + +export default RoomOpenerEmbedded; diff --git a/apps/meteor/client/views/room/RoomRoute.tsx b/apps/meteor/client/views/room/RoomRoute.tsx index 863dbc02574e..423e1fe92208 100644 --- a/apps/meteor/client/views/room/RoomRoute.tsx +++ b/apps/meteor/client/views/room/RoomRoute.tsx @@ -3,6 +3,8 @@ import { useRouter } from '@rocket.chat/ui-contexts'; import React, { useLayoutEffect, useState } from 'react'; import RoomOpener from './RoomOpener'; +import RoomOpenerEmbedded from './RoomOpenerEmbedded'; +import { useEmbeddedLayout } from '../../hooks/useEmbeddedLayout'; type RoomRouteProps = { extractOpenRoomParams: (routeParams: Record) => { @@ -15,6 +17,8 @@ const RoomRoute = ({ extractOpenRoomParams }: RoomRouteProps) => { const router = useRouter(); const [params, setParams] = useState(() => extractOpenRoomParams(router.getRouteParameters())); + const isEmbeddedLayout = useEmbeddedLayout(); + useLayoutEffect( () => router.subscribeToRouteChange(() => { @@ -23,6 +27,10 @@ const RoomRoute = ({ extractOpenRoomParams }: RoomRouteProps) => { [extractOpenRoomParams, router], ); + if (isEmbeddedLayout) { + return ; + } + return ; }; diff --git a/apps/meteor/client/views/root/MainLayout/EmbeddedPreload.tsx b/apps/meteor/client/views/root/MainLayout/EmbeddedPreload.tsx new file mode 100644 index 000000000000..1de34cbf5f9f --- /dev/null +++ b/apps/meteor/client/views/root/MainLayout/EmbeddedPreload.tsx @@ -0,0 +1,36 @@ +import { useUserId } from '@rocket.chat/ui-contexts'; +import type { ReactElement, ReactNode } from 'react'; +import React, { useEffect } from 'react'; + +import { CachedChatRoom, CachedChatSubscription } from '../../../../app/models/client'; +import { settings } from '../../../../app/settings/client'; +import { mainReady } from '../../../../app/ui-utils/client'; +import { useReactiveVar } from '../../../hooks/useReactiveVar'; +import { isSyncReady } from '../../../lib/userData'; +import PageLoading from '../PageLoading'; + +const EmbeddedPreload = ({ children }: { children: ReactNode }): ReactElement => { + const uid = useUserId(); + const subscriptionsReady = useReactiveVar(CachedChatSubscription.ready); + const settingsReady = useReactiveVar(settings.cachedCollection.ready); + const userDataReady = useReactiveVar(isSyncReady); + + const ready = !uid || (userDataReady && subscriptionsReady && settingsReady); + + useEffect(() => { + mainReady.set(ready); + }, [ready]); + + useEffect(() => { + CachedChatSubscription.ready.set(true); + CachedChatRoom.ready.set(true); + }, [ready]); + + if (!ready) { + return ; + } + + return <>{children}; +}; + +export default EmbeddedPreload; diff --git a/apps/meteor/client/views/root/MainLayout/MainLayout.tsx b/apps/meteor/client/views/root/MainLayout/MainLayout.tsx index cd13000d4ec2..ddf9bfbac73b 100644 --- a/apps/meteor/client/views/root/MainLayout/MainLayout.tsx +++ b/apps/meteor/client/views/root/MainLayout/MainLayout.tsx @@ -2,8 +2,10 @@ import type { ReactElement, ReactNode } from 'react'; import React, { Suspense } from 'react'; import AuthenticationCheck from './AuthenticationCheck'; +import EmbeddedPreload from './EmbeddedPreload'; import Preload from './Preload'; import { useCustomScript } from './useCustomScript'; +import { useEmbeddedLayout } from '../../../hooks/useEmbeddedLayout'; type MainLayoutProps = { children?: ReactNode; @@ -12,6 +14,18 @@ type MainLayoutProps = { const MainLayout = ({ children = null }: MainLayoutProps): ReactElement => { useCustomScript(); + const isEmbeddedLayout = useEmbeddedLayout(); + + if (isEmbeddedLayout) { + return ( + + + {children} + + + ); + } + return ( diff --git a/apps/meteor/client/views/root/MainLayout/Preload.tsx b/apps/meteor/client/views/root/MainLayout/Preload.tsx index c1e233d8a8cc..a86d1d6d5414 100644 --- a/apps/meteor/client/views/root/MainLayout/Preload.tsx +++ b/apps/meteor/client/views/root/MainLayout/Preload.tsx @@ -2,7 +2,7 @@ import { useUserId } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; import React, { useEffect } from 'react'; -import { CachedChatSubscription } from '../../../../app/models/client'; +import { CachedChatRoom, CachedChatSubscription } from '../../../../app/models/client'; import { settings } from '../../../../app/settings/client'; import { mainReady } from '../../../../app/ui-utils/client'; import { useReactiveVar } from '../../../hooks/useReactiveVar'; @@ -21,6 +21,11 @@ const Preload = ({ children }: { children: ReactNode }): ReactElement => { mainReady.set(ready); }, [ready]); + useEffect(() => { + CachedChatSubscription.listen(); + CachedChatRoom.listen(); + }, []); + if (!ready) { return ; }