diff --git a/lib/components/peer-olm-session-creator-provider.react.js b/lib/components/peer-olm-session-creator-provider.react.js new file mode 100644 --- /dev/null +++ b/lib/components/peer-olm-session-creator-provider.react.js @@ -0,0 +1,80 @@ +// @flow + +import invariant from 'invariant'; +import * as React from 'react'; + +import { IdentityClientContext } from '../shared/identity-client-context.js'; +import { useTunnelbroker } from '../tunnelbroker/tunnelbroker-context.js'; +import { createOlmSessionWithPeer } from '../utils/crypto-utils.js'; + +export type PeerOlmSessionCreatorContextType = { + +createOlmSessionsWithPeer: ( + userID: string, + deviceID: string, + ) => Promise, +}; + +const PeerOlmSessionCreatorContext: React.Context = + React.createContext(); + +type Props = { + +children: React.Node, +}; +function PeerOlmSessionCreatorProvider(props: Props): React.Node { + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'Identity context should be set'); + const { identityClient, getAuthMetadata } = identityContext; + + const { sendMessage } = useTunnelbroker(); + + const runningPromises = React.useRef<{ [deviceID: string]: ?Promise }>( + {}, + ); + + const createOlmSessionsWithPeer = React.useCallback( + (userID: string, deviceID: string) => { + if (runningPromises.current[deviceID]) { + return runningPromises.current[deviceID]; + } + + const promise = (async () => { + const authMetadata = await getAuthMetadata(); + await createOlmSessionWithPeer( + authMetadata, + identityClient, + sendMessage, + userID, + deviceID, + ); + + runningPromises.current[deviceID] = null; + })(); + + runningPromises.current[deviceID] = promise; + return promise; + }, + [identityClient, sendMessage, getAuthMetadata], + ); + + const peerOlmSessionCreatorContextValue: PeerOlmSessionCreatorContextType = + React.useMemo( + () => ({ createOlmSessionsWithPeer }), + [createOlmSessionsWithPeer], + ); + + return ( + + {props.children} + + ); +} + +function usePeerOlmSessionsCreatorContext(): PeerOlmSessionCreatorContextType { + const context = React.useContext(PeerOlmSessionCreatorContext); + invariant(context, 'PeerOlmSessionsCreatorContext should be set'); + return context; +} + +export { PeerOlmSessionCreatorProvider, usePeerOlmSessionsCreatorContext }; diff --git a/lib/push/send-hooks.react.js b/lib/push/send-hooks.react.js --- a/lib/push/send-hooks.react.js +++ b/lib/push/send-hooks.react.js @@ -8,6 +8,7 @@ } from './send-utils.js'; import { ENSCacheContext } from '../components/ens-cache-provider.react.js'; import { NeynarClientContext } from '../components/neynar-client-provider.react.js'; +import { usePeerOlmSessionsCreatorContext } from '../components/peer-olm-session-creator-provider.react.js'; import { thickRawThreadInfosSelector } from '../selectors/thread-selectors.js'; import type { MessageData } from '../types/message-types.js'; import type { @@ -29,6 +30,9 @@ const { getENSNames } = React.useContext(ENSCacheContext); const getFCNames = React.useContext(NeynarClientContext)?.getFCNames; + const { createOlmSessionsWithPeer: olmSessionCreator } = + usePeerOlmSessionsCreatorContext(); + return React.useCallback( ( encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, @@ -38,6 +42,7 @@ return preparePushNotifs({ encryptedNotifUtilsAPI, senderDeviceDescriptor, + olmSessionCreator, messageInfos: rawMessageInfos, thickRawThreadInfos, auxUserInfos, @@ -48,6 +53,7 @@ }); }, [ + olmSessionCreator, rawMessageInfos, thickRawThreadInfos, auxUserInfos, diff --git a/lib/push/send-utils.js b/lib/push/send-utils.js --- a/lib/push/send-utils.js +++ b/lib/push/send-utils.js @@ -654,6 +654,7 @@ type PreparePushNotifsInputData = { +encryptedNotifUtilsAPI: EncryptedNotifUtilsAPI, +senderDeviceDescriptor: SenderDeviceDescriptor, + +olmSessionCreator: (userID: string, deviceID: string) => Promise, +messageInfos: { +[id: string]: RawMessageInfo }, +thickRawThreadInfos: ThickRawThreadInfos, +auxUserInfos: AuxUserInfos, @@ -669,6 +670,7 @@ const { encryptedNotifUtilsAPI, senderDeviceDescriptor, + olmSessionCreator, messageDatas, messageInfos, auxUserInfos, @@ -689,6 +691,24 @@ return null; } + const olmSessionCreationPromises = []; + for (const userID in pushInfos) { + for (const device of pushInfos[userID].devices) { + olmSessionCreationPromises.push( + olmSessionCreator(userID, device.cryptoID), + ); + } + } + + try { + await Promise.all(olmSessionCreationPromises); + } catch (e) { + // session creation may fail for some devices + // but we should still pursue notification + // delivery for others + console.log(e); + } + return await buildNotifsFromPushInfo({ encryptedNotifUtilsAPI, senderDeviceDescriptor, diff --git a/lib/utils/crypto-utils.js b/lib/utils/crypto-utils.js --- a/lib/utils/crypto-utils.js +++ b/lib/utils/crypto-utils.js @@ -11,8 +11,9 @@ import type { IdentityKeysBlob, OLMIdentityKeys, + OutboundSessionCreationResult, SignedIdentityKeysBlob, -} from '../types/crypto-types'; +} from '../types/crypto-types.js'; import type { IdentityServiceClient } from '../types/identity-service-types'; import { type OutboundSessionCreation, @@ -115,6 +116,14 @@ ): Promise { const { olmAPI } = getConfig(); await olmAPI.initializeCryptoAccount(); + const [hasContentSession, hasNotifsSession] = await Promise.all([ + olmAPI.isContentSessionInitialized(deviceID), + olmAPI.isPeerNotificationsSessionInitialized(deviceID), + ]); + + if (hasContentSession && hasNotifsSession) { + return; + } const { userID: authUserID, @@ -134,14 +143,40 @@ } const { keys } = deviceKeysResponse; - const { primaryIdentityPublicKeys } = keys.identityKeysBlob; + const { primaryIdentityPublicKeys, notificationIdentityPublicKeys } = + keys.identityKeysBlob; const recipientDeviceID = primaryIdentityPublicKeys.ed25519; - const { sessionVersion, encryptedData } = - await olmAPI.contentOutboundSessionCreator( + if (hasContentSession) { + await olmAPI.notificationsOutboundSessionCreator( + notificationIdentityPublicKeys, + primaryIdentityPublicKeys, + keys.notifInitializationInfo, + ); + return; + } + + let outboundSessionCreationResult: OutboundSessionCreationResult; + if (hasNotifsSession) { + outboundSessionCreationResult = await olmAPI.contentOutboundSessionCreator( primaryIdentityPublicKeys, keys.contentInitializationInfo, ); + } else { + [outboundSessionCreationResult] = await Promise.all([ + await olmAPI.contentOutboundSessionCreator( + primaryIdentityPublicKeys, + keys.contentInitializationInfo, + ), + olmAPI.notificationsOutboundSessionCreator( + notificationIdentityPublicKeys, + primaryIdentityPublicKeys, + keys.notifInitializationInfo, + ), + ]); + } + + const { sessionVersion, encryptedData } = outboundSessionCreationResult; const sessionCreationMessage: OutboundSessionCreation = { type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, diff --git a/native/root.react.js b/native/root.react.js --- a/native/root.react.js +++ b/native/root.react.js @@ -30,6 +30,7 @@ import IntegrityHandler from 'lib/components/integrity-handler.react.js'; import { MediaCacheProvider } from 'lib/components/media-cache-provider.react.js'; import { NeynarClientProvider } from 'lib/components/neynar-client-provider.react.js'; +import { PeerOlmSessionCreatorProvider } from 'lib/components/peer-olm-session-creator-provider.react.js'; import PlatformDetailsSynchronizer from 'lib/components/platform-details-synchronizer.react.js'; import PrekeysHandler from 'lib/components/prekeys-handler.react.js'; import { QRAuthProvider } from 'lib/components/qr-auth-provider.react.js'; @@ -322,76 +323,80 @@ - - - - - - - - - - - - - - - - - - - - - - {gated} - - - - - - - - - - - {navigation} - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + {gated} + + + + + + + + + + + {navigation} + + + + + + + + + + + + + + + + + + + diff --git a/web/app.react.js b/web/app.react.js --- a/web/app.react.js +++ b/web/app.react.js @@ -21,6 +21,7 @@ useModalContext, } from 'lib/components/modal-provider.react.js'; import { NeynarClientProvider } from 'lib/components/neynar-client-provider.react.js'; +import { PeerOlmSessionCreatorProvider } from 'lib/components/peer-olm-session-creator-provider.react.js'; import PlatformDetailsSynchronizer from 'lib/components/platform-details-synchronizer.react.js'; import { QRAuthProvider } from 'lib/components/qr-auth-provider.react.js'; import { StaffContextProvider } from 'lib/components/staff-provider.react.js'; @@ -559,27 +560,29 @@ onClose={releaseLockOrAbortRequest} secondaryTunnelbrokerConnection={secondaryTunnelbrokerConnection} > - - - - - - + + + + + + + + );