diff --git a/lib/tunnelbroker/peer-to-peer-context.js b/lib/tunnelbroker/peer-to-peer-context.js --- a/lib/tunnelbroker/peer-to-peer-context.js +++ b/lib/tunnelbroker/peer-to-peer-context.js @@ -14,6 +14,7 @@ type DMOperationSpecification, } from '../shared/dm-ops/dm-op-utils.js'; import { + type AuthMetadata, IdentityClientContext, type IdentityClientContextType, } from '../shared/identity-client-context.js'; @@ -36,6 +37,11 @@ dmOpID: ?string, ) => void, +sendDMOperation: (op: DMOperationSpecification) => Promise, + +broadcastEphemeralMessage: ( + contentPayload: string, + recipients: $ReadOnlyArray<{ +userID: string, +deviceID: string }>, + authMetadata: AuthMetadata, + ) => Promise, }; const PeerToPeerContext: React.Context = @@ -246,6 +252,68 @@ [peerOlmSessionsCreator, identityContext, sendMessageToDevice], ); + const broadcastEphemeralMessage = React.useCallback( + async ( + contentPayload: string, + recipients: $ReadOnlyArray<{ +userID: string, +deviceID: string }>, + authMetadata: AuthMetadata, + ) => { + const { userID: thisUserID, deviceID: thisDeviceID } = authMetadata; + if (!thisDeviceID || !thisUserID) { + throw new Error('No auth metadata'); + } + const { olmAPI } = getConfig(); + await olmAPI.initializeCryptoAccount(); + + // We want it distinct by device ID to avoid potentially creating + // multiple Olm sessions with the same device simultaneously. + const recipientsDistinctByDeviceID = [ + ...new Map(recipients.map(item => [item.deviceID, item])).values(), + ]; + const promises = recipientsDistinctByDeviceID.map(async recipient => { + try { + const encryptedData = await olmAPI.encrypt( + contentPayload, + recipient.deviceID, + ); + const encryptedMessage: EncryptedMessage = { + type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, + senderInfo: { deviceID: thisDeviceID, userID: thisUserID }, + encryptedData, + }; + await sendMessageToDevice({ + deviceID: recipient.deviceID, + payload: JSON.stringify(encryptedMessage), + }); + } catch { + try { + await peerOlmSessionsCreator(recipient.userID, recipient.deviceID); + const encryptedData = await olmAPI.encrypt( + contentPayload, + recipient.deviceID, + ); + const encryptedMessage: EncryptedMessage = { + type: peerToPeerMessageTypes.ENCRYPTED_MESSAGE, + senderInfo: { deviceID: thisDeviceID, userID: thisUserID }, + encryptedData, + }; + await sendMessageToDevice({ + deviceID: recipient.deviceID, + payload: JSON.stringify(encryptedMessage), + }); + } catch (err) { + console.warn( + `Error sending Olm-encrypted message to device ${recipient.deviceID}:`, + err, + ); + } + } + }); + await Promise.all(promises); + }, + [peerOlmSessionsCreator, sendMessageToDevice], + ); + React.useEffect(() => { const intervalID = setInterval( processOutboundMessages, @@ -258,8 +326,9 @@ () => ({ processOutboundMessages, sendDMOperation, + broadcastEphemeralMessage, }), - [processOutboundMessages, sendDMOperation], + [broadcastEphemeralMessage, processOutboundMessages, sendDMOperation], ); return (