diff --git a/apps/meteor/client/startup/index.ts b/apps/meteor/client/startup/index.ts index 6300a43799f..76398b85189 100644 --- a/apps/meteor/client/startup/index.ts +++ b/apps/meteor/client/startup/index.ts @@ -14,7 +14,6 @@ import './loginViaQuery'; import './messageObserve'; import './messageTypes'; import './notifications'; -import './otr'; import './reloadRoomAfterLogin'; import './roles'; import './rootUrlChange'; diff --git a/apps/meteor/client/startup/otr.ts b/apps/meteor/client/startup/otr.ts deleted file mode 100644 index 084f4311c49..00000000000 --- a/apps/meteor/client/startup/otr.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { isOTRMessage } from '@rocket.chat/core-typings'; -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; - -import OTR from '../../app/otr/client/OTR'; -import { OtrRoomState } from '../../app/otr/lib/OtrRoomState'; -import { sdk } from '../../app/utils/client/lib/SDKClient'; -import { t } from '../../app/utils/lib/i18n'; -import { onClientBeforeSendMessage } from '../lib/onClientBeforeSendMessage'; -import { onClientMessageReceived } from '../lib/onClientMessageReceived'; - -Meteor.startup(() => { - Tracker.autorun(() => { - const uid = Meteor.userId(); - - if (!uid) { - return; - } - - sdk.stream('notify-user', [`${uid}/otr`], (type, data) => { - if (!data.roomId || !data.userId || data.userId === uid) { - return; - } - - const otrRoom = OTR.getInstanceByRoomId(uid, data.roomId); - otrRoom?.onUserStream(type, data); - }); - }); - - onClientBeforeSendMessage.use(async (message) => { - const uid = Meteor.userId(); - - if (!uid) { - return message; - } - - const otrRoom = OTR.getInstanceByRoomId(uid, message.rid); - - if (otrRoom && otrRoom.getState() === OtrRoomState.ESTABLISHED) { - const msg = await otrRoom.encrypt(message); - return { ...message, msg, t: 'otr' }; - } - return message; - }); - - onClientMessageReceived.use(async (message) => { - const uid = Meteor.userId(); - - if (!uid) { - return message; - } - - if (!isOTRMessage(message)) { - return message; - } - - if ('notification' in message) { - return { ...message, msg: t('Encrypted_message') }; - } - - const otrRoom = OTR.getInstanceByRoomId(uid, message.rid); - - if (otrRoom && otrRoom.getState() === OtrRoomState.ESTABLISHED) { - const decrypted = await otrRoom.decrypt(message.msg); - if (typeof decrypted === 'string') { - return { ...message, msg: decrypted }; - } - const { _id, text: msg, ack, ts, userId } = decrypted; - - if (ts) message.ts = ts; - - if (message.otrAck) { - const otrAck = await otrRoom.decrypt(message.otrAck); - if (typeof otrAck === 'string') { - return { ...message, msg: otrAck }; - } - - if (ack === otrAck.text) { - return { ...message, _id, t: 'otr-ack', msg }; - } - } else if (userId !== Meteor.userId()) { - const encryptedAck = await otrRoom.encryptText(ack); - - void sdk.call('updateOTRAck', { message, ack: encryptedAck }); - } - - return { ...message, _id, msg }; - } - if (message.t === 'otr') message.msg = ''; - - return message; - }); -}); diff --git a/apps/meteor/client/views/root/AppLayout.tsx b/apps/meteor/client/views/root/AppLayout.tsx index ad3cc55b11e..36fabf5d7f5 100644 --- a/apps/meteor/client/views/root/AppLayout.tsx +++ b/apps/meteor/client/views/root/AppLayout.tsx @@ -5,6 +5,7 @@ import PageLoading from './PageLoading'; import { useEscapeKeyStroke } from './hooks/useEscapeKeyStroke'; import { useGoogleTagManager } from './hooks/useGoogleTagManager'; import { useMessageLinkClicks } from './hooks/useMessageLinkClicks'; +import { useOTRMessaging } from './hooks/useOTRMessaging'; import { useSettingsOnLoadSiteUrl } from './hooks/useSettingsOnLoadSiteUrl'; import { useAnalytics } from '../../../app/analytics/client/loadScript'; import { useCorsSSLConfig } from '../../../app/cors/client/useCorsSSLConfig'; @@ -50,6 +51,8 @@ const AppLayout = () => { useCustomOAuth(); useCorsSSLConfig(); + useOTRMessaging(); + const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot); return ( diff --git a/apps/meteor/client/views/root/hooks/useOTRMessaging.ts b/apps/meteor/client/views/root/hooks/useOTRMessaging.ts new file mode 100644 index 00000000000..3b09e84b866 --- /dev/null +++ b/apps/meteor/client/views/root/hooks/useOTRMessaging.ts @@ -0,0 +1,104 @@ +import type { AtLeast, IMessage } from '@rocket.chat/core-typings'; +import { isOTRMessage } from '@rocket.chat/core-typings'; +import { useMethod, useStream, useUserId } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +import OTR from '../../../../app/otr/client/OTR'; +import { OtrRoomState } from '../../../../app/otr/lib/OtrRoomState'; +import { t } from '../../../../app/utils/lib/i18n'; +import { onClientBeforeSendMessage } from '../../../lib/onClientBeforeSendMessage'; +import { onClientMessageReceived } from '../../../lib/onClientMessageReceived'; + +export const useOTRMessaging = () => { + const uid = useUserId(); + const updateOTRAck = useMethod('updateOTRAck'); + const notifyUser = useStream('notify-user'); + + useEffect(() => { + if (!uid) { + return; + } + + const handleNotifyUser = (type: 'handshake' | 'acknowledge' | 'deny' | 'end', data: { roomId: string; userId: string }) => { + if (!data.roomId || !data.userId || data.userId === uid) { + return; + } + + const otrRoom = OTR.getInstanceByRoomId(uid, data.roomId); + otrRoom?.onUserStream(type, data); + }; + + const handleBeforeSendMessage = async ( + message: AtLeast, + ): Promise> => { + if (!uid) { + return message; + } + + const otrRoom = OTR.getInstanceByRoomId(uid, message.rid); + + if (otrRoom && otrRoom.getState() === OtrRoomState.ESTABLISHED) { + const msg = await otrRoom.encrypt(message); + return { ...message, msg, t: 'otr' }; + } + return message; + }; + + const handleMessageReceived = async (message: IMessage): Promise => { + if (!uid) { + return message; + } + + if (!isOTRMessage(message)) { + return message; + } + + if ('notification' in message) { + return { ...message, msg: t('Encrypted_message') }; + } + + const otrRoom = OTR.getInstanceByRoomId(uid, message.rid); + if (otrRoom && otrRoom.getState() === OtrRoomState.ESTABLISHED) { + const decrypted = await otrRoom.decrypt(message.msg); + if (typeof decrypted === 'string') { + return { ...message, msg: decrypted }; + } + + const { _id, text: msg, ack, ts, userId } = decrypted; + + if (ts) message.ts = ts; + + if (message.otrAck) { + const otrAck = await otrRoom.decrypt(message.otrAck); + if (typeof otrAck === 'string') { + return { ...message, msg: otrAck }; + } + + if (ack === otrAck.text) { + return { ...message, _id, t: 'otr-ack', msg }; + } + } else if (userId !== uid) { + const encryptedAck = await otrRoom.encryptText(ack); + + void updateOTRAck({ message, ack: encryptedAck }); + } + + return { ...message, _id, msg }; + } + + if (message.t === 'otr') message.msg = ''; + + return message; + }; + + const handleStopNotifyUser = notifyUser(`${uid}/otr`, handleNotifyUser); + const unregisterBeforeSendMessage = onClientBeforeSendMessage.use(handleBeforeSendMessage); + const unregisterMessageReceived = onClientMessageReceived.use(handleMessageReceived); + + return () => { + handleStopNotifyUser(); + unregisterBeforeSendMessage(); + unregisterMessageReceived(); + }; + }, [uid, notifyUser, updateOTRAck]); +};