diff --git a/native/handlers/peer-to-peer-message-handler.js b/lib/handlers/peer-to-peer-message-handler.js similarity index 51% rename from native/handlers/peer-to-peer-message-handler.js rename to lib/handlers/peer-to-peer-message-handler.js index 643d40a1a..f5f4422da 100644 --- a/native/handlers/peer-to-peer-message-handler.js +++ b/lib/handlers/peer-to-peer-message-handler.js @@ -1,76 +1,76 @@ // @flow -import { getOneTimeKeyValues } from 'lib/shared/crypto-utils.js'; +import type { + IdentityServiceClient, + DeviceOlmInboundKeys, +} from '../types/identity-service-types.js'; import { - type PeerToPeerMessage, peerToPeerMessageTypes, -} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; - -import { commCoreModule, commRustModule } from '../native-modules.js'; -import { nativeInboundContentSessionCreator } from '../utils/crypto-utils.js'; + type PeerToPeerMessage, +} from '../types/tunnelbroker/peer-to-peer-message-types.js'; +import { getConfig } from '../utils/config.js'; async function peerToPeerMessageHandler( message: PeerToPeerMessage, + identityClient: IdentityServiceClient, ): Promise { + const { olmAPI } = getConfig(); if (message.type === peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION) { try { - const result = await nativeInboundContentSessionCreator(message); + const { senderInfo, encryptedContent } = message; + const { keys } = await identityClient.getInboundKeysForUser( + senderInfo.userID, + ); + + const deviceKeys: ?DeviceOlmInboundKeys = keys[senderInfo.deviceID]; + if (!deviceKeys) { + throw new Error( + 'No keys for the device that requested creating a session, ' + + `deviceID: ${senderInfo.deviceID}`, + ); + } + + await olmAPI.initializeCryptoAccount(); + const result = await olmAPI.contentInboundSessionCreator( + deviceKeys.identityKeysBlob.primaryIdentityPublicKeys, + encryptedContent, + ); console.log( 'Created inbound session with device ' + `${message.senderInfo.deviceID}: ${result}`, ); } catch (e) { console.log( 'Error creating inbound session with device ' + `${message.senderInfo.deviceID}: ${e.message}`, ); } } else if (message.type === peerToPeerMessageTypes.ENCRYPTED_MESSAGE) { try { - await commCoreModule.initializeCryptoAccount(); - const decrypted = await commCoreModule.decrypt( + await olmAPI.initializeCryptoAccount(); + const decrypted = await olmAPI.decrypt( message.encryptedContent, message.senderInfo.deviceID, ); console.log( 'Decrypted message from device ' + `${message.senderInfo.deviceID}: ${decrypted}`, ); } catch (e) { console.log( 'Error decrypting message from device ' + `${message.senderInfo.deviceID}: ${e.message}`, ); } } else if (message.type === peerToPeerMessageTypes.REFRESH_KEY_REQUEST) { - await commCoreModule.initializeCryptoAccount(); - const [ - { userID, deviceID, accessToken }, - { contentOneTimeKeys, notificationsOneTimeKeys }, - ] = await Promise.all([ - commCoreModule.getCommServicesAuthMetadata(), - commCoreModule.getOneTimeKeys(message.numberOfKeys), - ]); - - if (!userID || !deviceID || !accessToken) { - console.log( - 'CommServicesAuthMetadata is missing when uploading one-time keys', - ); - return; - } - try { - await commRustModule.uploadOneTimeKeys( - userID, - deviceID, - accessToken, - getOneTimeKeyValues(contentOneTimeKeys), - getOneTimeKeyValues(notificationsOneTimeKeys), - ); + 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}`); } } } export { peerToPeerMessageHandler }; diff --git a/lib/tunnelbroker/tunnelbroker-context.js b/lib/tunnelbroker/tunnelbroker-context.js index 75f6e1d29..6f34531ed 100644 --- a/lib/tunnelbroker/tunnelbroker-context.js +++ b/lib/tunnelbroker/tunnelbroker-context.js @@ -1,327 +1,324 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import uuid from 'uuid'; import { tunnnelbrokerURL } from '../facts/tunnelbroker.js'; +import { peerToPeerMessageHandler } from '../handlers/peer-to-peer-message-handler.js'; +import { IdentityClientContext } from '../shared/identity-client-context.js'; import { tunnelbrokerHeartbeatTimeout } from '../shared/timeouts.js'; import type { MessageReceiveConfirmation } from '../types/tunnelbroker/message-receive-confirmation-types.js'; import type { MessageSentStatus } from '../types/tunnelbroker/message-to-device-request-status-types.js'; import type { MessageToDeviceRequest } from '../types/tunnelbroker/message-to-device-request-types.js'; import { type TunnelbrokerMessage, tunnelbrokerMessageTypes, tunnelbrokerMessageValidator, } from '../types/tunnelbroker/messages.js'; import { type PeerToPeerMessage, peerToPeerMessageValidator, } from '../types/tunnelbroker/peer-to-peer-message-types.js'; import type { AnonymousInitializationMessage, TunnelbrokerInitializationMessage, ConnectionInitializationMessage, } from '../types/tunnelbroker/session-types.js'; import type { Heartbeat } from '../types/websocket/heartbeat-types.js'; export type ClientMessageToDevice = { +deviceID: string, +payload: string, }; export type TunnelbrokerSocketListener = ( message: TunnelbrokerMessage, ) => mixed; type PromiseCallbacks = { +resolve: () => void, +reject: (error: string) => void, }; type Promises = { [clientMessageID: string]: PromiseCallbacks }; type TunnelbrokerContextType = { +sendMessage: (message: ClientMessageToDevice) => Promise, +addListener: (listener: TunnelbrokerSocketListener) => void, +removeListener: (listener: TunnelbrokerSocketListener) => void, +connected: boolean, +setUnauthorizedDeviceID: (unauthorizedDeviceID: ?string) => void, }; const TunnelbrokerContext: React.Context = React.createContext(); type Props = { +children: React.Node, +initMessage: ?ConnectionInitializationMessage, - +peerToPeerMessageHandler?: (message: PeerToPeerMessage) => mixed, }; function createAnonymousInitMessage( deviceID: string, ): AnonymousInitializationMessage { return ({ type: 'AnonymousInitializationMessage', deviceID, deviceType: 'mobile', }: AnonymousInitializationMessage); } function TunnelbrokerProvider(props: Props): React.Node { - const { - children, - initMessage: initMessageProp, - peerToPeerMessageHandler, - } = props; + const { children, initMessage: initMessageProp } = props; const [connected, setConnected] = React.useState(false); const listeners = React.useRef>(new Set()); const socket = React.useRef(null); const promises = React.useRef({}); const heartbeatTimeoutID = React.useRef(); const [unauthorizedDeviceID, setUnauthorizedDeviceID] = React.useState(null); const isAuthorized = !unauthorizedDeviceID; + const identityContext = React.useContext(IdentityClientContext); + invariant(identityContext, 'Identity context should be set'); + const { identityClient } = identityContext; + const initMessage = React.useMemo(() => { if (!unauthorizedDeviceID) { return initMessageProp; } return createAnonymousInitMessage(unauthorizedDeviceID); }, [unauthorizedDeviceID, initMessageProp]); const previousInitMessage = React.useRef(initMessage); const initMessageChanged = initMessage !== previousInitMessage.current; previousInitMessage.current = initMessage; const stopHeartbeatTimeout = React.useCallback(() => { if (heartbeatTimeoutID.current) { clearTimeout(heartbeatTimeoutID.current); heartbeatTimeoutID.current = null; } }, []); const resetHeartbeatTimeout = React.useCallback(() => { stopHeartbeatTimeout(); heartbeatTimeoutID.current = setTimeout(() => { socket.current?.close(); setConnected(false); }, tunnelbrokerHeartbeatTimeout); }, [stopHeartbeatTimeout]); // determine if the socket is active (not closed or closing) const isSocketActive = socket.current?.readyState === WebSocket.OPEN || socket.current?.readyState === WebSocket.CONNECTING; // The Tunnelbroker connection can have 4 states: // - DISCONNECTED: isSocketActive = false, connected = false // Should be in this state when initMessage is null // - CONNECTING: isSocketActive = true, connected = false // This lasts until Tunnelbroker sends ConnectionInitializationResponse // - CONNECTED: isSocketActive = true, connected = true // - DISCONNECTING: isSocketActive = false, connected = true // This lasts between socket.close() and socket.onclose() React.useEffect(() => { // when initMessage changes, we need to close the socket and open a new one if ((!initMessage || initMessageChanged) && isSocketActive) { socket.current?.close(); return; } // when we're already connected (or pending disconnection), // or there's no init message to start with, we don't need to do anything if (connected || !initMessage) { return; } const tunnelbrokerSocket = new WebSocket(tunnnelbrokerURL); tunnelbrokerSocket.onopen = () => { tunnelbrokerSocket.send(JSON.stringify(initMessage)); }; tunnelbrokerSocket.onclose = () => { // this triggers the effect hook again and reconnect setConnected(false); console.log('Connection to Tunnelbroker closed'); }; tunnelbrokerSocket.onerror = e => { console.log('Tunnelbroker socket error:', e.message); }; tunnelbrokerSocket.onmessage = (event: MessageEvent) => { if (typeof event.data !== 'string') { console.log('socket received a non-string message'); return; } let rawMessage; try { rawMessage = JSON.parse(event.data); } catch (e) { console.log('error while parsing Tunnelbroker message:', e.message); return; } if (!tunnelbrokerMessageValidator.is(rawMessage)) { console.log('invalid TunnelbrokerMessage'); return; } const message: TunnelbrokerMessage = rawMessage; resetHeartbeatTimeout(); for (const listener of listeners.current) { listener(message); } if ( message.type === tunnelbrokerMessageTypes.CONNECTION_INITIALIZATION_RESPONSE ) { if (message.status.type === 'Success' && !connected) { setConnected(true); console.log( 'session with Tunnelbroker created. isAuthorized:', isAuthorized, ); } else if (message.status.type === 'Success' && connected) { console.log( 'received ConnectionInitializationResponse with status: Success for already connected socket', ); } else { setConnected(false); console.log( 'creating session with Tunnelbroker error:', message.status.data, ); } } else if (message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE) { const confirmation: MessageReceiveConfirmation = { type: tunnelbrokerMessageTypes.MESSAGE_RECEIVE_CONFIRMATION, messageIDs: [message.messageID], }; socket.current?.send(JSON.stringify(confirmation)); - if (!peerToPeerMessageHandler) { - return; - } - let rawPeerToPeerMessage; try { rawPeerToPeerMessage = JSON.parse(message.payload); } catch (e) { console.log( 'error while parsing Tunnelbroker peer-to-peer message:', e.message, ); return; } if (!peerToPeerMessageValidator.is(rawPeerToPeerMessage)) { console.log('invalid Tunnelbroker PeerToPeerMessage'); return; } const peerToPeerMessage: PeerToPeerMessage = rawPeerToPeerMessage; - peerToPeerMessageHandler(peerToPeerMessage); + void peerToPeerMessageHandler(peerToPeerMessage, identityClient); } else if ( message.type === tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST_STATUS ) { for (const status: MessageSentStatus of message.clientMessageIDs) { if (status.type === 'Success') { promises.current[status.data]?.resolve(); delete promises.current[status.data]; } else if (status.type === 'Error') { promises.current[status.data.id]?.reject(status.data.error); delete promises.current[status.data.id]; } else if (status.type === 'SerializationError') { console.log('SerializationError for message: ', status.data); } else if (status.type === 'InvalidRequest') { console.log('Tunnelbroker recorded InvalidRequest'); } } } else if (message.type === tunnelbrokerMessageTypes.HEARTBEAT) { const heartbeat: Heartbeat = { type: tunnelbrokerMessageTypes.HEARTBEAT, }; socket.current?.send(JSON.stringify(heartbeat)); } }; socket.current = tunnelbrokerSocket; }, [ connected, initMessage, initMessageChanged, isSocketActive, isAuthorized, resetHeartbeatTimeout, stopHeartbeatTimeout, - peerToPeerMessageHandler, + identityClient, ]); const sendMessage: (message: ClientMessageToDevice) => Promise = React.useCallback( (message: ClientMessageToDevice) => { if (!connected || !socket.current) { throw new Error('Tunnelbroker not connected'); } const clientMessageID = uuid.v4(); const messageToDevice: MessageToDeviceRequest = { type: tunnelbrokerMessageTypes.MESSAGE_TO_DEVICE_REQUEST, clientMessageID, deviceID: message.deviceID, payload: message.payload, }; return new Promise((resolve, reject) => { promises.current[clientMessageID] = { resolve, reject, }; socket.current?.send(JSON.stringify(messageToDevice)); }); }, [connected], ); const addListener = React.useCallback( (listener: TunnelbrokerSocketListener) => { listeners.current.add(listener); }, [], ); const removeListener = React.useCallback( (listener: TunnelbrokerSocketListener) => { listeners.current.delete(listener); }, [], ); const value: TunnelbrokerContextType = React.useMemo( () => ({ sendMessage, connected, addListener, removeListener, setUnauthorizedDeviceID, }), [addListener, connected, removeListener, sendMessage], ); return ( {children} ); } function useTunnelbroker(): TunnelbrokerContextType { const context = React.useContext(TunnelbrokerContext); invariant(context, 'TunnelbrokerContext not found'); return context; } export { TunnelbrokerProvider, useTunnelbroker }; diff --git a/native/root.react.js b/native/root.react.js index 5b4ccbc2b..b91ca19ed 100644 --- a/native/root.react.js +++ b/native/root.react.js @@ -1,387 +1,383 @@ // @flow import { ActionSheetProvider } from '@expo/react-native-action-sheet'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import AsyncStorage from '@react-native-async-storage/async-storage'; import type { PossiblyStaleNavigationState, UnsafeContainerActionEvent, GenericNavigationAction, } from '@react-navigation/core'; import { useReduxDevToolsExtension } from '@react-navigation/devtools'; import { NavigationContainer } from '@react-navigation/native'; import * as SplashScreen from 'expo-splash-screen'; import invariant from 'invariant'; import * as React from 'react'; import { Platform, UIManager, StyleSheet } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import Orientation from 'react-native-orientation-locker'; import { SafeAreaProvider, initialWindowMetrics, } from 'react-native-safe-area-context'; import { Provider } from 'react-redux'; import { PersistGate as ReduxPersistGate } from 'redux-persist/es/integration/react.js'; import { ChatMentionContextProvider } from 'lib/components/chat-mention-provider.react.js'; import { EditUserAvatarProvider } from 'lib/components/edit-user-avatar-provider.react.js'; import { ENSCacheProvider } from 'lib/components/ens-cache-provider.react.js'; import IntegrityHandler from 'lib/components/integrity-handler.react.js'; import KeyserverConnectionsHandler from 'lib/components/keyserver-connections-handler.js'; import { MediaCacheProvider } from 'lib/components/media-cache-provider.react.js'; import { StaffContextProvider } from 'lib/components/staff-provider.react.js'; import { CallKeyserverEndpointProvider } from 'lib/keyserver-conn/call-keyserver-endpoint-provider.react.js'; import { TunnelbrokerProvider } from 'lib/tunnelbroker/tunnelbroker-context.js'; import { actionLogger } from 'lib/utils/action-logger.js'; import { OlmSessionCreatorProvider } from './account/account-hooks.js'; import { RegistrationContextProvider } from './account/registration/registration-context-provider.react.js'; import NativeEditThreadAvatarProvider from './avatars/native-edit-thread-avatar-provider.react.js'; import BackupHandler from './backup/backup-handler.js'; import { BottomSheetProvider } from './bottom-sheet/bottom-sheet-provider.react.js'; import ChatContextProvider from './chat/chat-context-provider.react.js'; import MessageEditingContextProvider from './chat/message-editing-context-provider.react.js'; import AccessTokenHandler from './components/access-token-handler.react.js'; import { FeatureFlagsProvider } from './components/feature-flags-provider.react.js'; import PersistedStateGate from './components/persisted-state-gate.js'; import PrekeysHandler from './components/prekeys-handler.react.js'; import VersionSupportedChecker from './components/version-supported.react.js'; import ConnectedStatusBar from './connected-status-bar.react.js'; import { SQLiteDataHandler } from './data/sqlite-data-handler.js'; import ErrorBoundary from './error-boundary.react.js'; -import { peerToPeerMessageHandler } from './handlers/peer-to-peer-message-handler.js'; import IdentityServiceContextProvider from './identity-service/identity-service-context-provider.react.js'; import InputStateContainer from './input/input-state-container.react.js'; import LifecycleHandler from './lifecycle/lifecycle-handler.react.js'; import MarkdownContextProvider from './markdown/markdown-context-provider.react.js'; import { filesystemMediaCache } from './media/media-cache.js'; import { DeepLinksContextProvider } from './navigation/deep-links-context-provider.react.js'; import { defaultNavigationState } from './navigation/default-state.js'; import { setGlobalNavContext } from './navigation/icky-global.js'; import KeyserverReachabilityHandler from './navigation/keyserver-reachability-handler.js'; import { NavContext, type NavContextType, } from './navigation/navigation-context.js'; import NavigationHandler from './navigation/navigation-handler.react.js'; import { validNavState } from './navigation/navigation-utils.js'; import OrientationHandler from './navigation/orientation-handler.react.js'; import { navStateAsyncStorageKey } from './navigation/persistance.js'; import RootNavigator from './navigation/root-navigator.react.js'; import ConnectivityUpdater from './redux/connectivity-updater.react.js'; import { DimensionsUpdater } from './redux/dimensions-updater.react.js'; import { getPersistor } from './redux/persist.js'; import { store } from './redux/redux-setup.js'; import { useSelector } from './redux/redux-utils.js'; import { RootContext } from './root-context.js'; import { MessageSearchProvider } from './search/search-provider.react.js'; import Socket from './socket.react.js'; import { useLoadCommFonts } from './themes/fonts.js'; import { DarkTheme, LightTheme } from './themes/navigation.js'; import ThemeHandler from './themes/theme-handler.react.js'; import { provider } from './utils/ethers-utils.js'; import { useTunnelbrokerInitMessage } from './utils/tunnelbroker-utils.js'; // Add custom items to expo-dev-menu import './dev-menu.js'; import './types/message-types-validator.js'; if (Platform.OS === 'android') { UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); } const navInitAction = Object.freeze({ type: 'NAV/@@INIT' }); const navUnknownAction = Object.freeze({ type: 'NAV/@@UNKNOWN' }); SplashScreen.preventAutoHideAsync().catch(console.log); function Root() { const navStateRef = React.useRef(); const navDispatchRef = React.useRef GenericNavigationAction), ) => void>(); const navStateInitializedRef = React.useRef(false); // We call this here to start the loading process // We gate the UI on the fonts loading in AppNavigator useLoadCommFonts(); const [navContext, setNavContext] = React.useState(null); const updateNavContext = React.useCallback(() => { if ( !navStateRef.current || !navDispatchRef.current || !navStateInitializedRef.current ) { return; } const updatedNavContext = { state: navStateRef.current, dispatch: navDispatchRef.current, }; setNavContext(updatedNavContext); setGlobalNavContext(updatedNavContext); }, []); const [initialState, setInitialState] = React.useState( __DEV__ ? undefined : defaultNavigationState, ); React.useEffect(() => { Orientation.lockToPortrait(); void (async () => { let loadedState = initialState; if (__DEV__) { try { const navStateString = await AsyncStorage.getItem( navStateAsyncStorageKey, ); if (navStateString) { const savedState = JSON.parse(navStateString); if (validNavState(savedState)) { loadedState = savedState; } } } catch {} } if (!loadedState) { loadedState = defaultNavigationState; } if (loadedState !== initialState) { setInitialState(loadedState); } navStateRef.current = loadedState; updateNavContext(); actionLogger.addOtherAction('navState', navInitAction, null, loadedState); })(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [updateNavContext]); const setNavStateInitialized = React.useCallback(() => { navStateInitializedRef.current = true; updateNavContext(); }, [updateNavContext]); const [rootContext, setRootContext] = React.useState(() => ({ setNavStateInitialized, })); const detectUnsupervisedBackgroundRef = React.useCallback( (detectUnsupervisedBackground: ?(alreadyClosed: boolean) => boolean) => { setRootContext(prevRootContext => ({ ...prevRootContext, detectUnsupervisedBackground, })); }, [], ); const frozen = useSelector(state => state.frozen); const queuedActionsRef = React.useRef>([]); const onNavigationStateChange = React.useCallback( (state: ?PossiblyStaleNavigationState) => { invariant(state, 'nav state should be non-null'); const prevState = navStateRef.current; navStateRef.current = state; updateNavContext(); const queuedActions = queuedActionsRef.current; queuedActionsRef.current = []; if (queuedActions.length === 0) { queuedActions.push(navUnknownAction); } for (const action of queuedActions) { actionLogger.addOtherAction('navState', action, prevState, state); } if (!__DEV__ || frozen) { return; } void (async () => { try { await AsyncStorage.setItem( navStateAsyncStorageKey, JSON.stringify(state), ); } catch (e) { console.log('AsyncStorage threw while trying to persist navState', e); } })(); }, [updateNavContext, frozen], ); const navContainerRef = React.useRef>(); const containerRef = React.useCallback( (navContainer: ?React.ElementRef) => { navContainerRef.current = navContainer; if (navContainer && !navDispatchRef.current) { navDispatchRef.current = navContainer.dispatch; updateNavContext(); } }, [updateNavContext], ); useReduxDevToolsExtension(navContainerRef); const navContainer = navContainerRef.current; React.useEffect(() => { if (!navContainer) { return undefined; } return navContainer.addListener( '__unsafe_action__', (event: { +data: UnsafeContainerActionEvent, ... }) => { const { action, noop } = event.data; const navState = navStateRef.current; if (noop) { actionLogger.addOtherAction('navState', action, navState, navState); return; } queuedActionsRef.current.push({ ...action, type: `NAV/${action.type}`, }); }, ); }, [navContainer]); const activeTheme = useSelector(state => state.globalThemeInfo.activeTheme); const theme = (() => { if (activeTheme === 'light') { return LightTheme; } else if (activeTheme === 'dark') { return DarkTheme; } return undefined; })(); const tunnelbrokerInitMessage = useTunnelbrokerInitMessage(); const gated: React.Node = ( <> ); let navigation; if (initialState) { navigation = ( ); } return ( - + {gated} {navigation} ); } const styles = StyleSheet.create({ app: { flex: 1, }, }); function AppRoot(): React.Node { return ( ); } export default AppRoot; diff --git a/native/utils/crypto-utils.js b/native/utils/crypto-utils.js index 5afc44873..1d2f0c2e7 100644 --- a/native/utils/crypto-utils.js +++ b/native/utils/crypto-utils.js @@ -1,181 +1,132 @@ // @flow import { type ClientMessageToDevice } from 'lib/tunnelbroker/tunnelbroker-context.js'; import type { IdentityKeysBlob, OLMIdentityKeys, } from 'lib/types/crypto-types.js'; -import type { - OutboundKeyInfoResponse, - InboundKeyInfoResponse, -} from 'lib/types/identity-service-types'; +import type { OutboundKeyInfoResponse } from 'lib/types/identity-service-types'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; import { type OutboundSessionCreation, peerToPeerMessageTypes, } from 'lib/types/tunnelbroker/peer-to-peer-message-types.js'; import { commCoreModule, commRustModule } from '../native-modules.js'; function nativeNotificationsSessionCreator( notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, keyserverID: string, ): Promise { const { prekey, prekeySignature, oneTimeKey } = notificationsInitializationInfo; return commCoreModule.initializeNotificationsSession( JSON.stringify(notificationsIdentityKeys), prekey, prekeySignature, oneTimeKey, keyserverID, ); } async function getContentSigningKey(): Promise { await commCoreModule.initializeCryptoAccount(); const { primaryIdentityPublicKeys: { ed25519 }, } = await commCoreModule.getUserPublicKey(); return ed25519; } -async function nativeInboundContentSessionCreator( - message: OutboundSessionCreation, -): Promise { - const { senderInfo, encryptedContent } = message; - - const authMetadata = await commCoreModule.getCommServicesAuthMetadata(); - const { userID, deviceID, accessToken } = authMetadata; - if (!userID || !deviceID || !accessToken) { - throw new Error('CommServicesAuthMetadata is missing'); - } - - await commCoreModule.initializeCryptoAccount(); - const keysResponse = await commRustModule.getInboundKeysForUser( - userID, - deviceID, - accessToken, - senderInfo.userID, - ); - - const inboundKeys: InboundKeyInfoResponse[] = JSON.parse(keysResponse); - const deviceKeys: ?InboundKeyInfoResponse = inboundKeys.find(keys => { - const keysPayload: IdentityKeysBlob = JSON.parse(keys.payload); - return ( - keysPayload.primaryIdentityPublicKeys.ed25519 === senderInfo.deviceID - ); - }); - - if (!deviceKeys) { - throw new Error( - 'No keys for the device that requested creating a session, ' + - `deviceID: ${senderInfo.deviceID}`, - ); - } - const keysPayload: IdentityKeysBlob = JSON.parse(deviceKeys.payload); - const identityKeys = JSON.stringify({ - curve25519: keysPayload.primaryIdentityPublicKeys.curve25519, - ed25519: keysPayload.primaryIdentityPublicKeys.ed25519, - }); - return commCoreModule.initializeContentInboundSession( - identityKeys, - encryptedContent, - keysPayload.primaryIdentityPublicKeys.ed25519, - ); -} - function nativeOutboundContentSessionCreator( contentIdentityKeys: OLMIdentityKeys, contentInitializationInfo: OlmSessionInitializationInfo, deviceID: string, ): Promise { const { prekey, prekeySignature, oneTimeKey } = contentInitializationInfo; const identityKeys = JSON.stringify({ curve25519: contentIdentityKeys.curve25519, ed25519: contentIdentityKeys.ed25519, }); return commCoreModule.initializeContentOutboundSession( identityKeys, prekey, prekeySignature, oneTimeKey, deviceID, ); } async function createOlmSessionsWithOwnDevices( sendMessage: (message: ClientMessageToDevice) => Promise, ): Promise { const authMetadata = await commCoreModule.getCommServicesAuthMetadata(); const { userID, deviceID, accessToken } = authMetadata; if (!userID || !deviceID || !accessToken) { throw new Error('CommServicesAuthMetadata is missing'); } await commCoreModule.initializeCryptoAccount(); const keysResponse = await commRustModule.getOutboundKeysForUser( userID, deviceID, accessToken, userID, ); const outboundKeys: OutboundKeyInfoResponse[] = JSON.parse(keysResponse); for (const deviceKeys: OutboundKeyInfoResponse of outboundKeys) { const keysPayload: IdentityKeysBlob = JSON.parse(deviceKeys.payload); if (keysPayload.primaryIdentityPublicKeys.ed25519 === deviceID) { continue; } const recipientDeviceID = keysPayload.primaryIdentityPublicKeys.ed25519; if (!deviceKeys.oneTimeContentPrekey) { console.log(`One-time key is missing for device ${recipientDeviceID}`); continue; } try { const encryptedContent = await nativeOutboundContentSessionCreator( keysPayload.primaryIdentityPublicKeys, { prekey: deviceKeys.contentPrekey, prekeySignature: deviceKeys.contentPrekeySignature, oneTimeKey: deviceKeys.oneTimeContentPrekey, }, recipientDeviceID, ); const sessionCreationMessage: OutboundSessionCreation = { type: peerToPeerMessageTypes.OUTBOUND_SESSION_CREATION, senderInfo: { userID, deviceID, }, encryptedContent, }; await sendMessage({ deviceID: recipientDeviceID, payload: JSON.stringify(sessionCreationMessage), }); console.log( `Request to create a session with device ${recipientDeviceID} sent.`, ); } catch (e) { console.log( 'Error creating outbound session with ' + `device ${recipientDeviceID}: ${e.message}`, ); } } } export { getContentSigningKey, nativeNotificationsSessionCreator, - nativeInboundContentSessionCreator, createOlmSessionsWithOwnDevices, nativeOutboundContentSessionCreator, };