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/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, @@ -106,6 +107,9 @@ } } +// TODO: This must be exposed via context to make sure +// there are no two promises running at the same time +// the same device async function createOlmSessionWithPeer( authMetadata: AuthMetadata, identityClient: IdentityServiceClient, @@ -115,6 +119,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 +146,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} > - - - - - - + + + + + + + + );