diff --git a/lib/tunnelbroker/peer-to-peer-message-handler.js b/lib/tunnelbroker/peer-to-peer-message-handler.js index 39f717a19..c2d916cfe 100644 --- a/lib/tunnelbroker/peer-to-peer-message-handler.js +++ b/lib/tunnelbroker/peer-to-peer-message-handler.js @@ -1,187 +1,191 @@ // @flow import * as React from 'react'; import { useTunnelbroker } from './tunnelbroker-context.js'; import { useHandleOlmMessageToDevice, usePeerToPeerMessageHandler, } from './use-peer-to-peer-message-handler.js'; import { useLoggedInUserInfo } from '../hooks/account-hooks.js'; import { useActionsQueue } from '../hooks/actions-queue.js'; import type { InboundP2PMessage } from '../types/sqlite-types.js'; import type { MessageReceiveConfirmation } from '../types/tunnelbroker/message-receive-confirmation-types.js'; import { deviceToTunnelbrokerMessageTypes, type TunnelbrokerToDeviceMessage, tunnelbrokerToDeviceMessageTypes, } from '../types/tunnelbroker/messages.js'; import { peerToPeerMessageValidator, type PeerToPeerMessage, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; import { getConfig } from '../utils/config.js'; +import { getMessageForException } from '../utils/errors.js'; type Props = { +socketSend: (message: string) => void, +getSessionCounter: () => number, +doesSocketExist: () => boolean, }; function PeerToPeerMessageHandler(props: Props): React.Node { const { socketSend, getSessionCounter, doesSocketExist } = props; const { addListener, removeListener } = useTunnelbroker(); const peerToPeerMessageHandler = usePeerToPeerMessageHandler(); const handleOlmMessageToDevice = useHandleOlmMessageToDevice(); const processItem = React.useCallback( async ( item: | { +type: 'persisted_message', +message: InboundP2PMessage, } | { +type: 'received_message', +message: { +peerToPeerMessage: PeerToPeerMessage, +messageID: string, +localSocketSessionCounter: number, }, }, ) => { if (item.type === 'persisted_message') { const { message } = item; try { await handleOlmMessageToDevice( message.plaintext, { deviceID: message.senderDeviceID, userID: message.senderUserID }, message.messageID, ); } catch (e) { console.log('Failed processing Olm P2P message:', e); } } else { const { peerToPeerMessage, messageID, localSocketSessionCounter } = item.message; // Since scheduling processing this message socket is closed // or was closed and reopened, we have to stop processing // because Tunnelbroker flushes the message again when opening // the socket, and we want to process this only once. if ( localSocketSessionCounter !== getSessionCounter() || !doesSocketExist() ) { return; } try { await peerToPeerMessageHandler(peerToPeerMessage, messageID); } catch (e) { - console.log(e.message); + console.log(getMessageForException(e)); } finally { if ( localSocketSessionCounter === getSessionCounter() && doesSocketExist() ) { const confirmation: MessageReceiveConfirmation = { type: deviceToTunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION, messageIDs: [messageID], }; socketSend(JSON.stringify(confirmation)); } } } }, [ doesSocketExist, getSessionCounter, handleOlmMessageToDevice, peerToPeerMessageHandler, socketSend, ], ); const { enqueue } = useActionsQueue(processItem); const tunnelbrokerMessageListener = React.useCallback( async (message: TunnelbrokerToDeviceMessage) => { if (message.type !== tunnelbrokerToDeviceMessageTypes.MESSAGE_TO_DEVICE) { return; } const confirmation: MessageReceiveConfirmation = { type: deviceToTunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION, messageIDs: [message.messageID], }; let rawPeerToPeerMessage; try { rawPeerToPeerMessage = JSON.parse(message.payload); } catch (e) { console.log( 'error while parsing Tunnelbroker peer-to-peer message:', - e.message, + getMessageForException(e), ); // Client received incorrect message, confirm to remove from // Tunnelbroker queue. socketSend(JSON.stringify(confirmation)); return; } if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) { console.log('invalid Tunnelbroker PeerToPeerMessage'); // The client received an invalid Tunnelbroker message, // and cannot process this type of request. socketSend(JSON.stringify(confirmation)); return; } const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage; enqueue([ { type: 'received_message', message: { peerToPeerMessage, messageID: message.messageID, localSocketSessionCounter: getSessionCounter(), }, }, ]); }, [enqueue, getSessionCounter, socketSend], ); React.useEffect(() => { addListener(tunnelbrokerMessageListener); return () => { removeListener(tunnelbrokerMessageListener); }; }, [addListener, removeListener, tunnelbrokerMessageListener]); const processPersistedInboundMessages = React.useCallback(async () => { try { const { sqliteAPI } = getConfig(); const messages = await sqliteAPI.getAllInboundP2PMessages(); enqueue( messages.map(message => ({ type: 'persisted_message', message, })), ); } catch (e) { - console.log('error while reading persisted inbound messages:', e.message); + console.log( + 'error while reading persisted inbound messages:', + getMessageForException(e), + ); } }, [enqueue]); const loggedInUserInfo = useLoggedInUserInfo(); const viewerID = loggedInUserInfo?.id; const processingInputMessagesStarted = React.useRef(false); React.useEffect(() => { if (!viewerID || processingInputMessagesStarted.current) { return; } processingInputMessagesStarted.current = true; void processPersistedInboundMessages(); }, [processPersistedInboundMessages, viewerID]); } export { PeerToPeerMessageHandler }; diff --git a/lib/tunnelbroker/use-peer-to-peer-message-handler.js b/lib/tunnelbroker/use-peer-to-peer-message-handler.js index 880f17a50..d5c22af12 100644 --- a/lib/tunnelbroker/use-peer-to-peer-message-handler.js +++ b/lib/tunnelbroker/use-peer-to-peer-message-handler.js @@ -1,435 +1,439 @@ // @flow import invariant from 'invariant'; import _isEqual from 'lodash/fp/isEqual.js'; import * as React from 'react'; import uuid from 'uuid'; import { useResendPeerToPeerMessages } from './use-resend-peer-to-peer-messages.js'; import { removePeerUsersActionType } from '../actions/aux-user-actions.js'; import { invalidateTunnelbrokerDeviceTokenActionType } from '../actions/tunnelbroker-actions.js'; import { logOutActionTypes, useLogOut } from '../actions/user-actions.js'; import { usePeerOlmSessionsCreatorContext } from '../components/peer-olm-session-creator-provider.react.js'; import { useBroadcastDeviceListUpdates, useBroadcastAccountDeletion, useGetAndUpdateDeviceListsForUsers, } from '../hooks/peer-list-hooks.js'; import { getAllPeerDevices, getForeignPeerDeviceIDs, } from '../selectors/user-selectors.js'; import { verifyAndGetDeviceList, removeDeviceFromDeviceList, } from '../shared/device-list-utils.js'; import { dmOperationSpecificationTypes } from '../shared/dm-ops/dm-op-utils.js'; import { useProcessDMOperation } from '../shared/dm-ops/process-dm-ops.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import type { DeviceOlmInboundKeys } from '../types/identity-service-types.js'; import { peerToPeerMessageTypes, type PeerToPeerMessage, type SenderInfo, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; import { userActionsP2PMessageTypes, userActionP2PMessageValidator, type UserActionP2PMessage, } from '../types/tunnelbroker/user-actions-peer-to-peer-message-types.js'; import { updateTypes } from '../types/update-types-enum.js'; import type { AccountDeletionUpdateInfo } from '../types/update-types.js'; import { getConfig } from '../utils/config.js'; import { getContentSigningKey } from '../utils/crypto-utils.js'; import { getMessageForException } from '../utils/errors.js'; import { hasHigherDeviceID, OLM_ERROR_FLAG, olmSessionErrors, } from '../utils/olm-utils.js'; import { getClientMessageIDFromTunnelbrokerMessageID } from '../utils/peer-to-peer-communication-utils.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useDispatch, useSelector } from '../utils/redux-utils.js'; // When logout is requested by primary device, logging out of Identity Service // is already handled by the primary device const primaryRequestLogoutOptions = Object.freeze({ skipIdentityLogOut: true }); // When re-broadcasting, we want to do it only to foreign peers // to avoid a vicious circle of deletion messages sent by own devices. const accountDeletionBroadcastOptions = Object.freeze({ includeOwnDevices: false, }); // handles `peerToPeerMessageTypes.ENCRYPTED_MESSAGE` function useHandleOlmMessageToDevice(): ( decryptedMessageContent: string, senderInfo: SenderInfo, messageID: string, ) => Promise { const identityContext = React.useContext(IdentityClientContext); invariant(identityContext, 'Identity context should be set'); const { identityClient, getAuthMetadata } = identityContext; const broadcastDeviceListUpdates = useBroadcastDeviceListUpdates(); const reBroadcastAccountDeletion = useBroadcastAccountDeletion( accountDeletionBroadcastOptions, ); const allPeerDevices = useSelector(getAllPeerDevices); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const primaryDeviceRequestedLogOut = useLogOut(primaryRequestLogoutOptions); const processDMOperation = useProcessDMOperation(); return React.useCallback( async ( decryptedMessageContent: string, senderInfo: SenderInfo, messageID: string, ) => { const { sqliteAPI } = getConfig(); const parsedMessageToDevice = JSON.parse(decryptedMessageContent); // Handle user-action messages if (!userActionP2PMessageValidator.is(parsedMessageToDevice)) { return; } const userActionMessage: UserActionP2PMessage = parsedMessageToDevice; if ( userActionMessage.type === userActionsP2PMessageTypes.LOG_OUT_DEVICE ) { // causes log out, there is no need to remove Inbound P2P message void dispatchActionPromise( logOutActionTypes, primaryDeviceRequestedLogOut(), ); } else if ( userActionMessage.type === userActionsP2PMessageTypes.LOG_OUT_SECONDARY_DEVICE ) { const { userID, deviceID: deviceIDToLogOut } = senderInfo; await removeDeviceFromDeviceList( identityClient, userID, deviceIDToLogOut, ); await broadcastDeviceListUpdates( allPeerDevices.filter(deviceID => deviceID !== deviceIDToLogOut), ); await sqliteAPI.removeInboundP2PMessages([messageID]); } else if ( userActionMessage.type === userActionsP2PMessageTypes.DM_OPERATION ) { // inbound P2P message is removed in DBOpsHandler after processing await processDMOperation({ type: dmOperationSpecificationTypes.INBOUND, op: userActionMessage.op, metadata: { messageID, senderDeviceID: senderInfo.deviceID, }, }); } else if ( userActionMessage.type === userActionsP2PMessageTypes.ACCOUNT_DELETION ) { const { userID: thisUserID } = await getAuthMetadata(); if (!thisUserID) { return; } // own devices re-broadcast account deletion to foreign peer devices if (senderInfo.userID === thisUserID) { await reBroadcastAccountDeletion(); // we treat account deletion the same way as primary-device-requested // logout, no need to remove Inbound P2P message void dispatchActionPromise( logOutActionTypes, primaryDeviceRequestedLogOut(), ); } else { const deleteUserUpdate: AccountDeletionUpdateInfo = { time: Date.now(), id: uuid.v4(), deletedUserID: senderInfo.userID, type: updateTypes.DELETE_ACCOUNT, }; dispatch({ type: removePeerUsersActionType, payload: { updatesResult: { newUpdates: [deleteUserUpdate] } }, }); await sqliteAPI.removeInboundP2PMessages([messageID]); } } else { console.warn( 'Unsupported P2P user action message:', userActionMessage.type, ); } }, [ allPeerDevices, broadcastDeviceListUpdates, dispatch, dispatchActionPromise, getAuthMetadata, identityClient, primaryDeviceRequestedLogOut, processDMOperation, reBroadcastAccountDeletion, ], ); } function usePeerToPeerMessageHandler(): ( message: PeerToPeerMessage, messageID: string, ) => Promise { const { olmAPI, sqliteAPI } = getConfig(); const identityContext = React.useContext(IdentityClientContext); invariant(identityContext, 'Identity context should be set'); const { identityClient, getAuthMetadata } = identityContext; const foreignPeerDevices = useSelector(getForeignPeerDeviceIDs); const broadcastDeviceListUpdates = useBroadcastDeviceListUpdates(); const getAndUpdateDeviceListsForUsers = useGetAndUpdateDeviceListsForUsers(); const dispatch = useDispatch(); const handleOlmMessageToDevice = useHandleOlmMessageToDevice(); const resendPeerToPeerMessages = useResendPeerToPeerMessages(); const { createOlmSessionsWithUser } = usePeerOlmSessionsCreatorContext(); return React.useCallback( async (message: PeerToPeerMessage, messageID: string) => { if (message.type === peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION) { const { senderInfo, encryptedData, sessionVersion } = message; const { userID: senderUserID, deviceID: senderDeviceID } = senderInfo; let deviceKeys: ?DeviceOlmInboundKeys = null; try { const { keys } = await identityClient.getInboundKeysForUser(senderUserID); deviceKeys = keys[senderDeviceID]; } catch (e) { - console.log(e.message); + console.log(getMessageForException(e)); } if (!deviceKeys) { console.log( 'Error creating inbound session with device ' + `${senderDeviceID}: No keys for the device, ` + `session version: ${sessionVersion}`, ); return; } try { await olmAPI.initializeCryptoAccount(); const result = await olmAPI.contentInboundSessionCreator( deviceKeys.identityKeysBlob.primaryIdentityPublicKeys, encryptedData, sessionVersion, false, ); await resendPeerToPeerMessages(senderDeviceID); console.log( 'Created inbound session with device ' + `${senderDeviceID}: ${result}, ` + `session version: ${sessionVersion}`, ); } catch (e) { - if (e.message?.includes(olmSessionErrors.alreadyCreated)) { + const errorMessage = getMessageForException(e) ?? ''; + if (errorMessage.includes(olmSessionErrors.alreadyCreated)) { console.log( 'Received session request with lower session version from ' + `${senderDeviceID}, session version: ${sessionVersion}`, ); - } else if (e.message?.includes(olmSessionErrors.raceCondition)) { + } else if (errorMessage.includes(olmSessionErrors.raceCondition)) { const currentDeviceID = await getContentSigningKey(); if (hasHigherDeviceID(currentDeviceID, senderDeviceID)) { console.log( 'Race condition while creating session with ' + `${senderDeviceID}, session version: ${sessionVersion}, ` + `this device has a higher deviceID and the session will be kept`, ); } else { const result = await olmAPI.contentInboundSessionCreator( deviceKeys.identityKeysBlob.primaryIdentityPublicKeys, encryptedData, sessionVersion, true, ); console.log( 'Overwrite session with device ' + `${senderDeviceID}: ${result}, ` + `session version: ${sessionVersion}`, ); await resendPeerToPeerMessages(senderDeviceID); } } else { console.log( 'Error creating inbound session with device ' + - `${senderDeviceID}: ${e.message}, ` + + `${senderDeviceID}: ${errorMessage}, ` + `session version: ${sessionVersion}`, ); } } } else if (message.type === peerToPeerMessageTypes.ENCRYPTED_MESSAGE) { try { await olmAPI.initializeCryptoAccount(); const decrypted = await olmAPI.decryptAndPersist( message.encryptedData, message.senderInfo.deviceID, message.senderInfo.userID, messageID, ); console.log( 'Decrypted message from device ' + `${message.senderInfo.deviceID}: ${decrypted}`, ); try { await handleOlmMessageToDevice( decrypted, message.senderInfo, messageID, ); } catch (e) { console.log('Failed processing Olm P2P message:', e); } } catch (e) { - if (e.message?.includes(olmSessionErrors.invalidSessionVersion)) { + const errorMessage = getMessageForException(e) ?? ''; + if (errorMessage.includes(olmSessionErrors.invalidSessionVersion)) { console.log( 'Received message decrypted with different session from ' + `${message.senderInfo.deviceID}.`, ); return; } console.log( 'Error decrypting message from device ' + - `${message.senderInfo.deviceID}: ${e.message}`, + `${message.senderInfo.deviceID}: ${errorMessage}`, ); if ( - !e.message?.includes(OLM_ERROR_FLAG) && - !e.message?.includes(olmSessionErrors.sessionDoesNotExist) + !errorMessage.includes(OLM_ERROR_FLAG) && + !errorMessage.includes(olmSessionErrors.sessionDoesNotExist) ) { throw e; } await createOlmSessionsWithUser(message.senderInfo.userID, [ { deviceID: message.senderInfo.deviceID, sessionCreationOptions: { overwriteContentSession: true }, }, ]); await resendPeerToPeerMessages(message.senderInfo.deviceID); } } else if (message.type === peerToPeerMessageTypes.REFRESH_KEY_REQUEST) { try { await olmAPI.initializeCryptoAccount(); const oneTimeKeys = await olmAPI.getOneTimeKeys(message.numberOfKeys); await identityClient.uploadOneTimeKeys(oneTimeKeys); } catch (e) { - console.log(`Error uploading one-time keys: ${e.message}`); + console.log( + `Error uploading one-time keys: ${getMessageForException(e) ?? ''}`, + ); } } else if (message.type === peerToPeerMessageTypes.DEVICE_LIST_UPDATED) { try { const result = await verifyAndGetDeviceList( identityClient, message.userID, null, ); if (!result.valid) { console.log( `Received invalid device list update for user ${message.userID}. Reason: ${result.reason}`, ); } else { console.log( `Received valid device list update for user ${message.userID}`, ); } await getAndUpdateDeviceListsForUsers([message.userID]); if (result.valid && message?.signedDeviceList?.rawDeviceList) { const receivedRawList = JSON.parse( message.signedDeviceList.rawDeviceList, ); // additional check for broadcasted and Identity device // list equality const listsAreEqual = _isEqual(result.deviceList)(receivedRawList); console.log( `Identity and received device lists are ${ listsAreEqual ? '' : 'not' } equal.`, ); } } catch (e) { console.log( `Error verifying device list for user ${message.userID}: ${e}`, ); } } else if ( message.type === peerToPeerMessageTypes.IDENTITY_DEVICE_LIST_UPDATED ) { try { const { userID } = await getAuthMetadata(); if (!userID) { return; } await Promise.all([ broadcastDeviceListUpdates(foreignPeerDevices), getAndUpdateDeviceListsForUsers([userID]), ]); } catch (e) { console.log( `Error updating device list after Identity request: ${ getMessageForException(e) ?? 'unknown error' }`, ); } } else if (message.type === peerToPeerMessageTypes.MESSAGE_PROCESSED) { try { const { deviceID, messageID: tunnelbrokerMessageID } = message; const clientMessageID = getClientMessageIDFromTunnelbrokerMessageID( tunnelbrokerMessageID, ); await sqliteAPI.removeOutboundP2PMessage(clientMessageID, deviceID); } catch (e) { console.log( `Error removing message after processing: ${ getMessageForException(e) ?? 'unknown error' }`, ); } } else if (message.type === peerToPeerMessageTypes.BAD_DEVICE_TOKEN) { dispatch({ type: invalidateTunnelbrokerDeviceTokenActionType, payload: { deviceToken: message.invalidatedToken, }, }); } }, [ broadcastDeviceListUpdates, createOlmSessionsWithUser, dispatch, foreignPeerDevices, getAndUpdateDeviceListsForUsers, getAuthMetadata, handleOlmMessageToDevice, identityClient, olmAPI, resendPeerToPeerMessages, sqliteAPI, ], ); } export { usePeerToPeerMessageHandler, useHandleOlmMessageToDevice }; diff --git a/lib/utils/peer-to-peer-communication-utils.js b/lib/utils/peer-to-peer-communication-utils.js index 819c8c0e3..047b46dee 100644 --- a/lib/utils/peer-to-peer-communication-utils.js +++ b/lib/utils/peer-to-peer-communication-utils.js @@ -1,195 +1,200 @@ // @flow import { getConfig } from './config.js'; +import { getMessageForException } from './errors.js'; import { olmSessionErrors } from './olm-utils.js'; import { type AuthMetadata } from '../shared/identity-client-context.js'; import { type P2PMessageRecipient } from '../tunnelbroker/peer-to-peer-context.js'; import type { TunnelbrokerClientMessageToDevice } from '../tunnelbroker/tunnelbroker-context.js'; import { outboundP2PMessageStatuses, type OutboundP2PMessage, } from '../types/sqlite-types.js'; import { peerToPeerMessageTypes, type EncryptedMessage, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; function getClientMessageIDFromTunnelbrokerMessageID( tunnelbrokerMessageID: string, ): string { const ids = tunnelbrokerMessageID.split('#'); if (ids.length !== 2) { throw new Error('Invalid tunnelbrokerMessageID'); } return ids[1]; } async function sendMessageToPeer( message: OutboundP2PMessage, authMetadata: ?AuthMetadata, sendMessage: ( message: TunnelbrokerClientMessageToDevice, messageID: ?string, ) => Promise, ): Promise<'success' | 'failure'> { const { sqliteAPI } = getConfig(); if (!authMetadata || !authMetadata.deviceID || !authMetadata.userID) { return 'failure'; } try { const encryptedMessage: EncryptedMessage = { type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, senderInfo: { deviceID: authMetadata.deviceID, userID: authMetadata.userID, }, encryptedData: JSON.parse(message.ciphertext), }; await sendMessage( { deviceID: message.deviceID, payload: JSON.stringify(encryptedMessage), }, message.messageID, ); await sqliteAPI.markOutboundP2PMessageAsSent( message.messageID, message.deviceID, ); return 'success'; } catch (e) { console.error(e); return 'failure'; } } async function encryptAndSendMessageToPeer( message: OutboundP2PMessage, authMetadata: ?AuthMetadata, sendMessage: ( message: TunnelbrokerClientMessageToDevice, messageID: ?string, ) => Promise, ): Promise<'success' | 'failure' | 'missing_session'> { const { olmAPI } = getConfig(); try { const result = await olmAPI.encryptAndPersist( message.plaintext, message.deviceID, message.messageID, ); const encryptedMessage: OutboundP2PMessage = { ...message, ciphertext: JSON.stringify(result), }; return await sendMessageToPeer(encryptedMessage, authMetadata, sendMessage); } catch (e) { - if (e.message?.includes(olmSessionErrors.sessionDoesNotExist)) { + if ( + getMessageForException(e)?.includes(olmSessionErrors.sessionDoesNotExist) + ) { return 'missing_session'; } console.log(`Error sending messages to peer ${message.deviceID}`, e); return 'failure'; } } export type HandleOutboundP2PMessageResult = { +status: 'success' | 'failure' | 'missing_session', +messageID: string, }; async function handleOutboundP2PMessage( message: OutboundP2PMessage, authMetadata: ?AuthMetadata, sendMessage: ( message: TunnelbrokerClientMessageToDevice, messageID: ?string, ) => Promise, ): Promise { if (message.status === outboundP2PMessageStatuses.persisted) { const status = await encryptAndSendMessageToPeer( message, authMetadata, sendMessage, ); return { status, messageID: message.messageID, }; } else if (message.status === outboundP2PMessageStatuses.encrypted) { const status = await sendMessageToPeer(message, authMetadata, sendMessage); return { status, messageID: message.messageID, }; } else if (message.status === outboundP2PMessageStatuses.sent) { // Handle edge-case when message was sent, but it wasn't updated // in the message store. return { status: 'success', messageID: message.messageID, }; } return { status: 'failure', messageID: message.messageID, }; } export type EphemeralEncryptAndSendMessageToPeerResult = { +status: 'success' | 'failure' | 'missing_session', +recipient: P2PMessageRecipient, }; async function ephemeralEncryptAndSendMessageToPeer( contentPayload: string, recipient: P2PMessageRecipient, authMetadata: ?AuthMetadata, sendMessage: ( message: TunnelbrokerClientMessageToDevice, messageID: ?string, ) => Promise, ): Promise { const { olmAPI } = getConfig(); if (!authMetadata || !authMetadata.deviceID || !authMetadata.userID) { return { status: 'failure', recipient }; } const senderInfo = { deviceID: authMetadata.deviceID, userID: authMetadata.userID, }; try { const encryptedData = await olmAPI.encrypt( contentPayload, recipient.deviceID, ); const encryptedMessage: EncryptedMessage = { type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, senderInfo, encryptedData, }; await sendMessage({ deviceID: recipient.deviceID, payload: JSON.stringify(encryptedMessage), }); return { status: 'success', recipient }; } catch (e) { - if (e.message?.includes(olmSessionErrors.sessionDoesNotExist)) { + if ( + getMessageForException(e)?.includes(olmSessionErrors.sessionDoesNotExist) + ) { return { status: 'missing_session', recipient }; } console.log(`Error sending messages to peer ${recipient.deviceID}`, e); return { status: 'failure', recipient }; } } export { getClientMessageIDFromTunnelbrokerMessageID, sendMessageToPeer, encryptAndSendMessageToPeer, ephemeralEncryptAndSendMessageToPeer, handleOutboundP2PMessage, };