diff --git a/lib/types/crypto-types.js b/lib/types/crypto-types.js index 8f765c433..82de2198b 100644 --- a/lib/types/crypto-types.js +++ b/lib/types/crypto-types.js @@ -1,97 +1,98 @@ // @flow import t, { type TInterface } from 'tcomb'; import type { OlmSessionInitializationInfo } from '../types/request-types.js'; import { tShape } from '../utils/validation-utils.js'; export type OLMIdentityKeys = { +ed25519: string, +curve25519: string, }; export type OLMPrekey = { +curve25519: { +id: string, +key: string, }, }; export type OLMOneTimeKeys = { +curve25519: { +[string]: string }, }; export type PickledOLMAccount = { +picklingKey: string, +pickledAccount: string, }; export type CryptoStore = { +primaryAccount: PickledOLMAccount, +primaryIdentityKeys: OLMIdentityKeys, +notificationAccount: PickledOLMAccount, +notificationIdentityKeys: OLMIdentityKeys, }; export type CryptoStoreContextType = { +getInitializedCryptoStore: () => Promise, }; export type NotificationsSessionCreatorContextType = { +notificationsSessionCreator: ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, ) => Promise, }; export type NotificationsOlmDataType = { +mainSession: string, +picklingKey: string, +pendingSessionUpdate: string, +updateCreationTimestamp: number, }; export type IdentityKeysBlob = { +primaryIdentityPublicKeys: OLMIdentityKeys, +notificationIdentityPublicKeys: OLMIdentityKeys, }; export type SignedIdentityKeysBlob = { +payload: string, +signature: string, }; export const signedIdentityKeysBlobValidator: TInterface = tShape({ payload: t.String, signature: t.String, }); export type UserDetail = { +username: string, +userID: string, }; // This type should not be changed without making equivalent changes to // `Message` in Identity service's `reserved_users` module export type ReservedUsernameMessage = | { +statement: 'Add the following usernames to reserved list', +payload: $ReadOnlyArray, +issuedAt: string, } | { +statement: 'Remove the following username from reserved list', +payload: string, +issuedAt: string, } | { +statement: 'This user is the owner of the following username and user ID', +payload: UserDetail, +issuedAt: string, }; export const olmEncryptedMessageTypes = Object.freeze({ PREKEY: 0, TEXT: 1, }); diff --git a/web/account/account-hooks.js b/web/account/account-hooks.js index b5da368a7..62d1c0005 100644 --- a/web/account/account-hooks.js +++ b/web/account/account-hooks.js @@ -1,330 +1,337 @@ // @flow import olm from '@commapp/olm'; import invariant from 'invariant'; import localforage from 'localforage'; import * as React from 'react'; import uuid from 'uuid'; import { initialEncryptedMessageContent, getOneTimeKeyValuesFromBlob, getPrekeyValueFromBlob, } from 'lib/shared/crypto-utils.js'; import type { SignedIdentityKeysBlob, CryptoStore, IdentityKeysBlob, CryptoStoreContextType, OLMIdentityKeys, NotificationsSessionCreatorContextType, NotificationsOlmDataType, } from 'lib/types/crypto-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { generateCryptoKey, encryptData, exportKeyToJWK, } from '../crypto/aes-gcm-crypto-utils.js'; import { isDesktopSafari } from '../database/utils/db-utils.js'; import { initOlm } from '../olm/olm-utils.js'; import { getOlmDataContentKeyForCookie, getOlmEncryptionKeyDBLabelForCookie, } from '../push-notif/notif-crypto-utils.js'; import { setCryptoStore } from '../redux/crypto-store-reducer.js'; import { useSelector } from '../redux/redux-utils.js'; const CryptoStoreContext: React.Context = React.createContext(null); const WebNotificationsSessionCreatorContext: React.Context = React.createContext(null); type Props = { +children: React.Node, }; function GetOrCreateCryptoStoreProvider(props: Props): React.Node { const dispatch = useDispatch(); const createCryptoStore = React.useCallback(async () => { await initOlm(); const identityAccount = new olm.Account(); identityAccount.create(); const { ed25519: identityED25519, curve25519: identityCurve25519 } = JSON.parse(identityAccount.identity_keys()); const identityAccountPicklingKey = uuid.v4(); const pickledIdentityAccount = identityAccount.pickle( identityAccountPicklingKey, ); const notificationAccount = new olm.Account(); notificationAccount.create(); const { ed25519: notificationED25519, curve25519: notificationCurve25519 } = JSON.parse(notificationAccount.identity_keys()); const notificationAccountPicklingKey = uuid.v4(); const pickledNotificationAccount = notificationAccount.pickle( notificationAccountPicklingKey, ); const newCryptoStore = { primaryAccount: { picklingKey: identityAccountPicklingKey, pickledAccount: pickledIdentityAccount, }, primaryIdentityKeys: { ed25519: identityED25519, curve25519: identityCurve25519, }, notificationAccount: { picklingKey: notificationAccountPicklingKey, pickledAccount: pickledNotificationAccount, }, notificationIdentityKeys: { ed25519: notificationED25519, curve25519: notificationCurve25519, }, }; dispatch({ type: setCryptoStore, payload: newCryptoStore }); return newCryptoStore; }, [dispatch]); const currentCryptoStore = useSelector(state => state.cryptoStore); const createCryptoStorePromiseRef = React.useRef>(null); const getCryptoStorePromise = React.useCallback(() => { if (currentCryptoStore) { return Promise.resolve(currentCryptoStore); } const currentCreateCryptoStorePromiseRef = createCryptoStorePromiseRef.current; if (currentCreateCryptoStorePromiseRef) { return currentCreateCryptoStorePromiseRef; } const newCreateCryptoStorePromise = (async () => { try { return await createCryptoStore(); } catch (e) { createCryptoStorePromiseRef.current = undefined; throw e; } })(); createCryptoStorePromiseRef.current = newCreateCryptoStorePromise; return newCreateCryptoStorePromise; }, [createCryptoStore, currentCryptoStore]); const isCryptoStoreSet = !!currentCryptoStore; React.useEffect(() => { if (!isCryptoStoreSet) { createCryptoStorePromiseRef.current = undefined; } }, [isCryptoStoreSet]); const contextValue = React.useMemo( () => ({ getInitializedCryptoStore: getCryptoStorePromise, }), [getCryptoStorePromise], ); return ( {props.children} ); } function useGetOrCreateCryptoStore(): () => Promise { const context = React.useContext(CryptoStoreContext); invariant(context, 'CryptoStoreContext not found'); return context.getInitializedCryptoStore; } function useGetSignedIdentityKeysBlob(): () => Promise { const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); return React.useCallback(async () => { const { primaryAccount, primaryIdentityKeys, notificationIdentityKeys } = await getOrCreateCryptoStore(); await initOlm(); const primaryOLMAccount = new olm.Account(); primaryOLMAccount.unpickle( primaryAccount.picklingKey, primaryAccount.pickledAccount, ); const identityKeysBlob: IdentityKeysBlob = { primaryIdentityPublicKeys: primaryIdentityKeys, notificationIdentityPublicKeys: notificationIdentityKeys, }; const payloadToBeSigned: string = JSON.stringify(identityKeysBlob); const signedIdentityKeysBlob: SignedIdentityKeysBlob = { payload: payloadToBeSigned, signature: primaryOLMAccount.sign(payloadToBeSigned), }; return signedIdentityKeysBlob; }, [getOrCreateCryptoStore]); } function WebNotificationsSessionCreatorProvider(props: Props): React.Node { const getOrCreateCryptoStore = useGetOrCreateCryptoStore(); const currentCryptoStore = useSelector(state => state.cryptoStore); const createNewNotificationsSession = React.useCallback( async ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, ) => { const [{ notificationAccount }, encryptionKey] = await Promise.all([ getOrCreateCryptoStore(), generateCryptoKey({ extractable: isDesktopSafari }), initOlm(), ]); const account = new olm.Account(); const { picklingKey, pickledAccount } = notificationAccount; account.unpickle(picklingKey, pickledAccount); const notificationsPrekey = getPrekeyValueFromBlob( notificationsInitializationInfo.prekey, ); const [notificationsOneTimeKey] = getOneTimeKeyValuesFromBlob( notificationsInitializationInfo.oneTimeKey, ); const session = new olm.Session(); session.create_outbound( account, notificationsIdentityKeys.curve25519, notificationsIdentityKeys.ed25519, notificationsPrekey, notificationsInitializationInfo.prekeySignature, notificationsOneTimeKey, ); const { body: initialNotificationsEncryptedMessage } = session.encrypt( JSON.stringify(initialEncryptedMessageContent), ); const mainSession = session.pickle(picklingKey); const notificationsOlmData: NotificationsOlmDataType = { mainSession, pendingSessionUpdate: mainSession, updateCreationTimestamp: Date.now(), picklingKey, }; const encryptedOlmData = await encryptData( new TextEncoder().encode(JSON.stringify(notificationsOlmData)), encryptionKey, ); const notifsOlmDataEncryptionKeyDBLabel = getOlmEncryptionKeyDBLabelForCookie(cookie); - const notifsOlmDataContentKey = getOlmDataContentKeyForCookie(cookie); + const notifsOlmDataContentKey = getOlmDataContentKeyForCookie( + cookie, + keyserverID, + ); const persistEncryptionKeyPromise = (async () => { let cryptoKeyPersistentForm; if (isDesktopSafari) { // Safari doesn't support structured clone algorithm in service // worker context so we have to store CryptoKey as JSON cryptoKeyPersistentForm = await exportKeyToJWK(encryptionKey); } else { cryptoKeyPersistentForm = encryptionKey; } await localforage.setItem( notifsOlmDataEncryptionKeyDBLabel, cryptoKeyPersistentForm, ); })(); await Promise.all([ localforage.setItem(notifsOlmDataContentKey, encryptedOlmData), persistEncryptionKeyPromise, ]); return initialNotificationsEncryptedMessage; }, [getOrCreateCryptoStore], ); const notificationsSessionPromise = React.useRef>(null); const createNotificationsSession = React.useCallback( async ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, ) => { if (notificationsSessionPromise.current) { return notificationsSessionPromise.current; } const newNotificationsSessionPromise = (async () => { try { return await createNewNotificationsSession( cookie, notificationsIdentityKeys, notificationsInitializationInfo, + keyserverID, ); } catch (e) { notificationsSessionPromise.current = undefined; throw e; } })(); notificationsSessionPromise.current = newNotificationsSessionPromise; return newNotificationsSessionPromise; }, [createNewNotificationsSession], ); const isCryptoStoreSet = !!currentCryptoStore; React.useEffect(() => { if (!isCryptoStoreSet) { notificationsSessionPromise.current = undefined; } }, [isCryptoStoreSet]); const contextValue = React.useMemo( () => ({ notificationsSessionCreator: createNotificationsSession, }), [createNotificationsSession], ); return ( {props.children} ); } function useWebNotificationsSessionCreator(): ( cookie: ?string, notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, + keyserverID: string, ) => Promise { const context = React.useContext(WebNotificationsSessionCreatorContext); invariant(context, 'WebNotificationsSessionCreator not found.'); return context.notificationsSessionCreator; } export { useGetSignedIdentityKeysBlob, useGetOrCreateCryptoStore, WebNotificationsSessionCreatorProvider, useWebNotificationsSessionCreator, GetOrCreateCryptoStoreProvider, }; diff --git a/web/push-notif/notif-crypto-utils.js b/web/push-notif/notif-crypto-utils.js index 3bdd009aa..a93b80e1b 100644 --- a/web/push-notif/notif-crypto-utils.js +++ b/web/push-notif/notif-crypto-utils.js @@ -1,374 +1,378 @@ // @flow import olm from '@commapp/olm'; import localforage from 'localforage'; import { olmEncryptedMessageTypes, type NotificationsOlmDataType, } from 'lib/types/crypto-types.js'; import type { PlainTextWebNotification, EncryptedWebNotification, } from 'lib/types/notif-types.js'; import { getCookieIDFromCookie } from 'lib/utils/cookie-utils.js'; import { type EncryptedData, decryptData, encryptData, importJWKKey, } from '../crypto/aes-gcm-crypto-utils.js'; import { NOTIFICATIONS_OLM_DATA_CONTENT, NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY, } from '../database/utils/constants.js'; import { isDesktopSafari } from '../database/utils/db-utils.js'; import { initOlm } from '../olm/olm-utils.js'; export type WebNotifDecryptionError = { +id: string, +error: string, +displayErrorMessage?: boolean, }; export type WebNotifsServiceUtilsData = { +olmWasmPath: string, +staffCanSee: boolean, }; type DecryptionResult = { +newPendingSessionUpdate: string, +newUpdateCreationTimestamp: number, +decryptedNotification: T, }; export const WEB_NOTIFS_SERVICE_UTILS_KEY = 'webNotifsServiceUtils'; const SESSION_UPDATE_MAX_PENDING_TIME = 10 * 1000; async function decryptWebNotification( encryptedNotification: EncryptedWebNotification, ): Promise { const { id, encryptedPayload } = encryptedNotification; const utilsData = await localforage.getItem( WEB_NOTIFS_SERVICE_UTILS_KEY, ); if (!utilsData) { return { id, error: 'Necessary data not found in IndexedDB' }; } const { olmWasmPath, staffCanSee } = (utilsData: WebNotifsServiceUtilsData); let olmDBKeys; try { olmDBKeys = await getNotifsOlmSessionDBKeys(); } catch (e) { return { id, error: e.message, displayErrorMessage: staffCanSee, }; } const { olmDataContentKey, encryptionKeyDBKey } = olmDBKeys; const [encryptedOlmData, encryptionKey] = await Promise.all([ localforage.getItem(olmDataContentKey), retrieveEncryptionKey(encryptionKeyDBKey), ]); if (!encryptionKey || !encryptedOlmData) { return { id, error: 'Received encrypted notification but olm session was not created', displayErrorMessage: staffCanSee, }; } try { await olm.init({ locateFile: () => olmWasmPath }); const decryptedNotification = await commonDecrypt( encryptedOlmData, olmDataContentKey, encryptionKey, encryptedPayload, ); return { id, ...decryptedNotification }; } catch (e) { return { id, error: e.message, displayErrorMessage: staffCanSee, }; } } async function decryptDesktopNotification( encryptedPayload: string, staffCanSee: boolean, ): Promise<{ +[string]: mixed }> { let encryptedOlmData, encryptionKey, olmDataContentKey; try { const { olmDataContentKey: olmDataContentKeyValue, encryptionKeyDBKey } = await getNotifsOlmSessionDBKeys(); olmDataContentKey = olmDataContentKeyValue; [encryptedOlmData, encryptionKey] = await Promise.all([ localforage.getItem(olmDataContentKey), retrieveEncryptionKey(encryptionKeyDBKey), initOlm(), ]); } catch (e) { return { error: e.message, displayErrorMessage: staffCanSee, }; } if (!encryptionKey || !encryptedOlmData) { return { error: 'Received encrypted notification but olm session was not created', displayErrorMessage: staffCanSee, }; } try { return await commonDecrypt( encryptedOlmData, olmDataContentKey, encryptionKey, encryptedPayload, ); } catch (e) { return { error: e.message, staffCanSee, }; } } async function commonDecrypt( encryptedOlmData: EncryptedData, olmDataContentKey: string, encryptionKey: CryptoKey, encryptedPayload: string, ): Promise { const serializedOlmData = await decryptData(encryptedOlmData, encryptionKey); const { mainSession, picklingKey, pendingSessionUpdate, updateCreationTimestamp, }: NotificationsOlmDataType = JSON.parse( new TextDecoder().decode(serializedOlmData), ); let updatedOlmData: NotificationsOlmDataType; let decryptedNotification: T; const shouldUpdateMainSession = Date.now() - updateCreationTimestamp > SESSION_UPDATE_MAX_PENDING_TIME; const decryptionWithPendingSessionResult = decryptWithPendingSession( pendingSessionUpdate, picklingKey, encryptedPayload, ); if (decryptionWithPendingSessionResult.decryptedNotification) { const { decryptedNotification: notifDecryptedWithPendingSession, newPendingSessionUpdate, newUpdateCreationTimestamp, } = decryptionWithPendingSessionResult; decryptedNotification = notifDecryptedWithPendingSession; updatedOlmData = { mainSession: shouldUpdateMainSession ? pendingSessionUpdate : mainSession, pendingSessionUpdate: newPendingSessionUpdate, updateCreationTimestamp: newUpdateCreationTimestamp, picklingKey, }; } else { const { newUpdateCreationTimestamp, decryptedNotification: notifDecryptedWithMainSession, } = decryptWithSession(mainSession, picklingKey, encryptedPayload); decryptedNotification = notifDecryptedWithMainSession; updatedOlmData = { mainSession: mainSession, pendingSessionUpdate, updateCreationTimestamp: newUpdateCreationTimestamp, picklingKey, }; } const updatedEncryptedSession = await encryptData( new TextEncoder().encode(JSON.stringify(updatedOlmData)), encryptionKey, ); await localforage.setItem(olmDataContentKey, updatedEncryptedSession); return decryptedNotification; } function decryptWithSession( pickledSession: string, picklingKey: string, encryptedPayload: string, ): DecryptionResult { const session = new olm.Session(); session.unpickle(picklingKey, pickledSession); const decryptedNotification: T = JSON.parse( session.decrypt(olmEncryptedMessageTypes.TEXT, encryptedPayload), ); const newPendingSessionUpdate = session.pickle(picklingKey); const newUpdateCreationTimestamp = Date.now(); return { decryptedNotification, newUpdateCreationTimestamp, newPendingSessionUpdate, }; } function decryptWithPendingSession( pendingSessionUpdate: string, picklingKey: string, encryptedPayload: string, ): DecryptionResult | { +error: string } { try { const { decryptedNotification, newPendingSessionUpdate, newUpdateCreationTimestamp, } = decryptWithSession( pendingSessionUpdate, picklingKey, encryptedPayload, ); return { newPendingSessionUpdate, newUpdateCreationTimestamp, decryptedNotification, }; } catch (e) { return { error: e.message }; } } async function retrieveEncryptionKey( encryptionKeyDBLabel: string, ): Promise { if (!isDesktopSafari) { return await localforage.getItem(encryptionKeyDBLabel); } // Safari doesn't support structured clone algorithm in service // worker context so we have to store CryptoKey as JSON const persistedCryptoKey = await localforage.getItem(encryptionKeyDBLabel); if (!persistedCryptoKey) { return null; } return await importJWKKey(persistedCryptoKey); } async function getNotifsOlmSessionDBKeys(): Promise<{ +olmDataContentKey: string, +encryptionKeyDBKey: string, }> { const dbKeys = await localforage.keys(); const olmDataContentKeys = sortOlmDBKeysArray( dbKeys.filter(key => key.startsWith(NOTIFICATIONS_OLM_DATA_CONTENT)), ); const encryptionKeyDBLabels = sortOlmDBKeysArray( dbKeys.filter(key => key.startsWith(NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY)), ); if (olmDataContentKeys.length === 0 || encryptionKeyDBLabels.length === 0) { throw new Error( 'Received encrypted notification but olm session was not created', ); } const latestDataContentKey = olmDataContentKeys[olmDataContentKeys.length - 1]; const latestEncryptionKeyDBKey = encryptionKeyDBLabels[encryptionKeyDBLabels.length - 1]; const latestDataContentCookieID = getCookieIDFromOlmDBKey(latestDataContentKey); const latestEncryptionKeyCookieID = getCookieIDFromOlmDBKey( latestEncryptionKeyDBKey, ); if (latestDataContentCookieID !== latestEncryptionKeyCookieID) { throw new Error( 'Olm sessions and their encryption keys out of sync. Latest cookie ' + `id for olm sessions ${latestDataContentCookieID}. Latest cookie ` + `id for olm session encryption keys ${latestEncryptionKeyCookieID}`, ); } const olmDBKeys = { olmDataContentKey: latestDataContentKey, encryptionKeyDBKey: latestEncryptionKeyDBKey, }; const keysToDelete: $ReadOnlyArray = [ ...olmDataContentKeys.slice(0, olmDataContentKeys.length - 1), ...encryptionKeyDBLabels.slice(0, encryptionKeyDBLabels.length - 1), ]; await Promise.all(keysToDelete.map(key => localforage.removeItem(key))); return olmDBKeys; } -function getOlmDataContentKeyForCookie(cookie: ?string): string { +function getOlmDataContentKeyForCookie( + cookie: ?string, + // eslint-disable-next-line no-unused-vars + keyserverID: string, +): string { if (!cookie) { return NOTIFICATIONS_OLM_DATA_CONTENT; } const cookieID = getCookieIDFromCookie(cookie); return `${NOTIFICATIONS_OLM_DATA_CONTENT}:${cookieID}`; } function getOlmEncryptionKeyDBLabelForCookie(cookie: ?string): string { if (!cookie) { return NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY; } const cookieID = getCookieIDFromCookie(cookie); return `${NOTIFICATIONS_OLM_DATA_ENCRYPTION_KEY}:${cookieID}`; } function getCookieIDFromOlmDBKey(olmDBKey: string): string | '0' { const cookieID = olmDBKey.split(':')[1]; return cookieID ?? '0'; } function sortOlmDBKeysArray( olmDBKeysArray: $ReadOnlyArray, ): $ReadOnlyArray { return olmDBKeysArray .map(key => ({ cookieID: Number(getCookieIDFromOlmDBKey(key)), key, })) .sort( ({ cookieID: cookieID1 }, { cookieID: cookieID2 }) => cookieID1 - cookieID2, ) .map(({ key }) => key); } export { decryptWebNotification, decryptDesktopNotification, getOlmDataContentKeyForCookie, getOlmEncryptionKeyDBLabelForCookie, }; diff --git a/web/socket.react.js b/web/socket.react.js index 31a0ad198..284011490 100644 --- a/web/socket.react.js +++ b/web/socket.react.js @@ -1,128 +1,129 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { preRequestUserStateForSingleKeyserverSelector } from 'lib/selectors/account-selectors.js'; import { cookieSelector, urlPrefixSelector, connectionSelector, lastCommunicatedPlatformDetailsSelector, } from 'lib/selectors/keyserver-selectors.js'; import { openSocketSelector } from 'lib/selectors/socket-selectors.js'; import { useInitialNotificationsEncryptedMessage } from 'lib/shared/crypto-utils.js'; import Socket, { type BaseSocketProps } from 'lib/socket/socket.react.js'; import type { OLMIdentityKeys } from 'lib/types/crypto-types.js'; import type { OlmSessionInitializationInfo } from 'lib/types/request-types.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { ashoatKeyserverID } from 'lib/utils/validation-utils.js'; import { useGetSignedIdentityKeysBlob, useWebNotificationsSessionCreator, } from './account/account-hooks.js'; import { useSelector } from './redux/redux-utils.js'; import { activeThreadSelector, webCalendarQuery, } from './selectors/nav-selectors.js'; import { sessionIdentificationSelector, webGetClientResponsesSelector, webSessionStateFuncSelector, } from './selectors/socket-selectors.js'; import { decompressMessage } from './utils/decompress.js'; const WebSocket: React.ComponentType = React.memo(function WebSocket(props) { const cookie = useSelector(cookieSelector(ashoatKeyserverID)); const urlPrefix = useSelector(urlPrefixSelector(ashoatKeyserverID)); invariant(urlPrefix, 'missing urlPrefix for given keyserver id'); const connection = useSelector(connectionSelector(ashoatKeyserverID)); invariant(connection, 'keyserver missing from keyserverStore'); const active = useSelector( state => !!state.currentUserInfo && !state.currentUserInfo.anonymous && state.lifecycleState !== 'background', ); const openSocket = useSelector(openSocketSelector(ashoatKeyserverID)); invariant(openSocket, 'openSocket failed to be created'); const sessionIdentification = useSelector( sessionIdentificationSelector(ashoatKeyserverID), ); const preRequestUserState = useSelector( preRequestUserStateForSingleKeyserverSelector(ashoatKeyserverID), ); const getSignedIdentityKeysBlob = useGetSignedIdentityKeysBlob(); const webNotificationsSessionCreator = useWebNotificationsSessionCreator(); const webNotifsSessionCreatorForCookie = React.useCallback( async ( notificationsIdentityKeys: OLMIdentityKeys, notificationsInitializationInfo: OlmSessionInitializationInfo, ) => webNotificationsSessionCreator( cookie, notificationsIdentityKeys, notificationsInitializationInfo, + ashoatKeyserverID, ), [webNotificationsSessionCreator, cookie], ); const getInitialNotificationsEncryptedMessage = useInitialNotificationsEncryptedMessage(webNotifsSessionCreatorForCookie); const getClientResponses = useSelector(state => webGetClientResponsesSelector({ state, getSignedIdentityKeysBlob, getInitialNotificationsEncryptedMessage, }), ); const sessionStateFunc = useSelector( webSessionStateFuncSelector(ashoatKeyserverID), ); const currentCalendarQuery = useSelector(webCalendarQuery); const reduxActiveThread = useSelector(activeThreadSelector); const windowActive = useSelector(state => state.windowActive); const activeThread = React.useMemo(() => { if (!active || !windowActive) { return null; } return reduxActiveThread; }, [active, windowActive, reduxActiveThread]); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const lastCommunicatedPlatformDetails = useSelector( lastCommunicatedPlatformDetailsSelector(ashoatKeyserverID), ); return ( ); }); export default WebSocket;