diff --git a/.changeset/forty-stars-bathe.md b/.changeset/forty-stars-bathe.md new file mode 100644 index 0000000000000..00f21597c8a50 --- /dev/null +++ b/.changeset/forty-stars-bathe.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixes error message being shown when logging out current device via Device Management despite successful logout. diff --git a/apps/meteor/client/hooks/useDeviceLogout.tsx b/apps/meteor/client/hooks/useDeviceLogout.tsx index c13528d4d4c80..215ee191c5ea0 100644 --- a/apps/meteor/client/hooks/useDeviceLogout.tsx +++ b/apps/meteor/client/hooks/useDeviceLogout.tsx @@ -1,41 +1,61 @@ import { GenericModal } from '@rocket.chat/ui-client'; -import { useSetModal, useToastMessageDispatch, useRoute, useRouteParameter } from '@rocket.chat/ui-contexts'; -import { useQueryClient } from '@tanstack/react-query'; -import { useCallback } from 'react'; +import { + useSetModal, + useToastMessageDispatch, + useRoute, + useRouteParameter, + useEndpoint, + useSessionDispatch, + UserContext, +} from '@rocket.chat/ui-contexts'; +import { useQueryClient, useMutation } from '@tanstack/react-query'; +import { useCallback, useContext } from 'react'; import { useTranslation } from 'react-i18next'; -import { useEndpointMutation } from './useEndpointMutation'; import { deviceManagementQueryKeys } from '../lib/queryKeys'; -export const useDeviceLogout = (sessionId: string, endpoint: '/v1/sessions/logout' | '/v1/sessions/logout.me'): (() => void) => { +export const useDeviceLogout = ( + sessionId: string, + endpoint: '/v1/sessions/logout' | '/v1/sessions/logout.me', + isCurrentSession?: boolean, +): (() => void) => { const { t } = useTranslation(); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const deviceManagementRouter = useRoute('device-management'); const routeId = useRouteParameter('id'); + const setForceLogout = useSessionDispatch('forceLogout'); + const { logout } = useContext(UserContext); const queryClient = useQueryClient(); + const logoutEndpoint = useEndpoint('POST', endpoint); - const { mutateAsync: logoutDevice } = useEndpointMutation('POST', endpoint, { - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: deviceManagementQueryKeys.all }); - isContextualBarOpen && handleCloseContextualBar(); - dispatchToastMessage({ type: 'success', message: t('Device_Logged_Out') }); - }, + const handleCloseContextualBar = useCallback(() => deviceManagementRouter.push({}), [deviceManagementRouter]); + const isContextualBarOpen = routeId === sessionId; + + const { mutate: logoutDevice } = useMutation({ + mutationFn: logoutEndpoint, onSettled: () => { - setModal(null); + if (isCurrentSession) { + setModal(null); + logout(); + } else { + queryClient.invalidateQueries({ queryKey: deviceManagementQueryKeys.all }); + if (isContextualBarOpen) { + handleCloseContextualBar(); + } + dispatchToastMessage({ type: 'success', message: t('Device_Logged_Out') }); + setModal(null); + } }, + throwOnError: false, }); - const handleCloseContextualBar = useCallback(() => deviceManagementRouter.push({}), [deviceManagementRouter]); - - const isContextualBarOpen = routeId === sessionId; - return useCallback(() => { const closeModal = () => setModal(null); - const handleLogoutDevice = async () => { - await logoutDevice({ sessionId }); + const handleLogoutDevice = () => { + logoutDevice({ sessionId }); }; setModal( @@ -44,12 +64,15 @@ export const useDeviceLogout = (sessionId: string, endpoint: '/v1/sessions/logou variant='danger' confirmText={t('Logout_Device')} cancelText={t('Cancel')} - onConfirm={handleLogoutDevice} + onConfirm={() => { + setForceLogout(true); + handleLogoutDevice(); + }} onCancel={closeModal} onClose={closeModal} > {t('Device_Logout_Text')} , ); - }, [setModal, t, logoutDevice, sessionId]); + }, [setModal, t, logoutDevice, sessionId, setForceLogout]); }; diff --git a/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountRow.tsx b/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountRow.tsx index 892c750af1dfd..653f38ddd7748 100644 --- a/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountRow.tsx +++ b/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountRow.tsx @@ -13,14 +13,15 @@ type DevicesRowProps = { deviceType?: string; deviceOSName?: string; loginAt: string; + current?: boolean; }; -const DeviceManagementAccountRow = ({ _id, deviceName, deviceType = 'browser', deviceOSName, loginAt }: DevicesRowProps) => { +const DeviceManagementAccountRow = ({ _id, deviceName, deviceType = 'browser', deviceOSName, loginAt, current }: DevicesRowProps) => { const { t } = useTranslation(); const formatDateAndTime = useFormatDateAndTime(); const mediaQuery = useMediaQuery('(min-width: 1024px)'); - const handleDeviceLogout = useDeviceLogout(_id, '/v1/sessions/logout.me'); + const handleDeviceLogout = useDeviceLogout(_id, '/v1/sessions/logout.me', current); return ( diff --git a/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountTable.tsx b/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountTable.tsx index 9c8757b45edbd..5eaa5560824ca 100644 --- a/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountTable.tsx +++ b/apps/meteor/client/views/account/deviceManagement/DeviceManagementAccountTable/DeviceManagementAccountTable.tsx @@ -66,6 +66,7 @@ const DeviceManagementAccountTable = () => { deviceType={session.device?.type} deviceOSName={session.device?.os.name} loginAt={session.loginAt} + current={session.current} /> )} current={current} diff --git a/apps/meteor/ee/server/api/sessions.ts b/apps/meteor/ee/server/api/sessions.ts index 5c091864f3d5d..f4ea149c18a8c 100644 --- a/apps/meteor/ee/server/api/sessions.ts +++ b/apps/meteor/ee/server/api/sessions.ts @@ -95,7 +95,14 @@ API.v1.addRoute( return API.v1.failure('error-invalid-sort-keys'); } - const sessions = await Sessions.aggregateSessionsByUserId({ uid: this.userId, search, sort, offset, count }); + const sessions = await Sessions.aggregateSessionsByUserId({ + uid: this.userId, + search, + sort, + offset, + count, + currentLoginToken: this.token, + }); return API.v1.success(sessions); }, }, diff --git a/packages/core-typings/src/ISession.ts b/packages/core-typings/src/ISession.ts index f280b4fe817b0..f0a57f9fac5be 100644 --- a/packages/core-typings/src/ISession.ts +++ b/packages/core-typings/src/ISession.ts @@ -75,7 +75,9 @@ export type OSSessionAggregation = Pick & { time: number; }; -export type DeviceManagementSession = Pick; +export type DeviceManagementSession = Pick & { + current?: boolean; +}; export type DeviceManagementPopulatedSession = DeviceManagementSession & { _user: Pick; diff --git a/packages/model-typings/src/models/ISessionsModel.ts b/packages/model-typings/src/models/ISessionsModel.ts index 654658a209b26..1a068da3da6c9 100644 --- a/packages/model-typings/src/models/ISessionsModel.ts +++ b/packages/model-typings/src/models/ISessionsModel.ts @@ -43,12 +43,14 @@ export interface ISessionsModel extends IBaseModel { search, offset, count, + currentLoginToken, }: { uid: string; sort?: Record; search?: string | null; offset?: number; count?: number; + currentLoginToken?: string; }): Promise<{ sessions: Array; count: number; offset: number; total: number }>; getActiveUsersBetweenDates({ start, end }: DestructuredRange): Promise; diff --git a/packages/models/src/models/Sessions.ts b/packages/models/src/models/Sessions.ts index 3763bf38b457a..52bce4ac135cb 100644 --- a/packages/models/src/models/Sessions.ts +++ b/packages/models/src/models/Sessions.ts @@ -748,12 +748,14 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { search, offset = 0, count = 10, + currentLoginToken, }: { uid: string; sort?: Record; search?: string | null; offset?: number; count?: number; + currentLoginToken?: string; }): Promise> { const searchQuery = search ? [{ searchTerm: { $regex: search, $options: 'i' } }] : []; @@ -824,6 +826,15 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { host: 1, ip: 1, loginAt: 1, + ...(currentLoginToken && { + current: { + $cond: { + if: { $eq: ['$_id', currentLoginToken] }, + then: true, + else: false, + }, + }, + }), }, };